LCOV - code coverage report
Current view: top level - frmts/zarr - vsikerchunk_json_ref.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 708 816 86.8 %
Date: 2025-10-22 21:46:17 Functions: 40 41 97.6 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver. Virtual file system for
       5             :  *           https://fsspec.github.io/kerchunk/spec.html#version-1
       6             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #undef _REENTRANT
      15             : 
      16             : #include "vsikerchunk.h"
      17             : #include "vsikerchunk_inline.hpp"
      18             : 
      19             : #include "cpl_conv.h"
      20             : #include "cpl_json.h"
      21             : #include "cpl_json_streaming_parser.h"
      22             : #include "cpl_json_streaming_writer.h"
      23             : #include "cpl_mem_cache.h"
      24             : #include "cpl_multiproc.h"  // CPLSleep()
      25             : #include "cpl_vsi_error.h"
      26             : #include "cpl_vsi_virtual.h"
      27             : 
      28             : #include "gdal_priv.h"
      29             : #include "ogrsf_frmts.h"
      30             : 
      31             : #include <cerrno>
      32             : #include <cinttypes>
      33             : #include <limits>
      34             : #include <mutex>
      35             : #include <set>
      36             : #include <utility>
      37             : 
      38             : /************************************************************************/
      39             : /*                         VSIKerchunkKeyInfo                           */
      40             : /************************************************************************/
      41             : 
      42             : struct VSIKerchunkKeyInfo
      43             : {
      44             :     // points to an element in VSIKerchunkRefFile::m_oSetURI
      45             :     const std::string *posURI = nullptr;
      46             : 
      47             :     uint64_t nOffset = 0;
      48             :     uint32_t nSize = 0;
      49             :     std::vector<GByte> abyValue{};
      50             : };
      51             : 
      52             : /************************************************************************/
      53             : /*                         VSIKerchunkRefFile                           */
      54             : /************************************************************************/
      55             : 
      56             : class VSIKerchunkRefFile
      57             : {
      58             :   private:
      59             :     std::set<std::string> m_oSetURI{};
      60             :     std::map<std::string, VSIKerchunkKeyInfo> m_oMapKeys{};
      61             : 
      62             :   public:
      63         578 :     const std::map<std::string, VSIKerchunkKeyInfo> &GetMapKeys() const
      64             :     {
      65         578 :         return m_oMapKeys;
      66             :     }
      67             : 
      68         125 :     void AddInlineContent(const std::string &key, std::vector<GByte> &&abyValue)
      69             :     {
      70         250 :         VSIKerchunkKeyInfo info;
      71         125 :         info.abyValue = std::move(abyValue);
      72         125 :         m_oMapKeys[key] = std::move(info);
      73         125 :     }
      74             : 
      75         128 :     bool AddInlineContent(const std::string &key, const std::string_view &str)
      76             :     {
      77         256 :         std::vector<GByte> abyValue;
      78         128 :         if (cpl::starts_with(str, "base64:"))
      79             :         {
      80             :             abyValue.insert(
      81           0 :                 abyValue.end(),
      82           4 :                 reinterpret_cast<const GByte *>(str.data()) + strlen("base64:"),
      83           4 :                 reinterpret_cast<const GByte *>(str.data()) + str.size());
      84           4 :             abyValue.push_back(0);
      85           4 :             const int nSize = CPLBase64DecodeInPlace(abyValue.data());
      86           4 :             if (nSize == 0)
      87             :             {
      88           3 :                 CPLError(CE_Failure, CPLE_AppDefined,
      89             :                          "VSIKerchunkJSONRefFileSystem: Base64 decoding "
      90             :                          "failed for key '%s'",
      91             :                          key.c_str());
      92           3 :                 return false;
      93             :             }
      94           1 :             abyValue.resize(nSize);
      95             :         }
      96             :         else
      97             :         {
      98             :             abyValue.insert(
      99         124 :                 abyValue.end(), reinterpret_cast<const GByte *>(str.data()),
     100         248 :                 reinterpret_cast<const GByte *>(str.data()) + str.size());
     101             :         }
     102             : 
     103         125 :         AddInlineContent(key, std::move(abyValue));
     104         125 :         return true;
     105             :     }
     106             : 
     107          20 :     void AddReferencedContent(const std::string &key, const std::string &osURI,
     108             :                               uint64_t nOffset, uint32_t nSize)
     109             :     {
     110          20 :         auto oPair = m_oSetURI.insert(osURI);
     111             : 
     112          40 :         VSIKerchunkKeyInfo info;
     113          20 :         info.posURI = &(*(oPair.first));
     114          20 :         info.nOffset = nOffset;
     115          20 :         info.nSize = nSize;
     116          20 :         m_oMapKeys[key] = std::move(info);
     117          20 :     }
     118             : 
     119             :     bool ConvertToParquetRef(const std::string &osCacheDir,
     120             :                              GDALProgressFunc pfnProgress, void *pProgressData);
     121             : };
     122             : 
     123             : /************************************************************************/
     124             : /*                    VSIKerchunkJSONRefFileSystem                      */
     125             : /************************************************************************/
     126             : 
     127             : class VSIKerchunkJSONRefFileSystem final : public VSIFilesystemHandler
     128             : {
     129             :   public:
     130        1755 :     VSIKerchunkJSONRefFileSystem()
     131        1755 :     {
     132        1755 :         IsFileSystemInstantiated() = true;
     133        1755 :     }
     134             : 
     135        2246 :     ~VSIKerchunkJSONRefFileSystem() override
     136        1123 :     {
     137        1123 :         IsFileSystemInstantiated() = false;
     138        2246 :     }
     139             : 
     140        4633 :     static bool &IsFileSystemInstantiated()
     141             :     {
     142             :         static bool bIsFileSystemInstantiated = false;
     143        4633 :         return bIsFileSystemInstantiated;
     144             :     }
     145             : 
     146             :     VSIVirtualHandleUniquePtr Open(const char *pszFilename,
     147             :                                    const char *pszAccess, bool bSetError,
     148             :                                    CSLConstList papszOptions) override;
     149             : 
     150             :     int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
     151             :              int nFlags) override;
     152             : 
     153             :     char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
     154             : 
     155             :     char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
     156             :                            CSLConstList papszOptions) override;
     157             : 
     158             :   private:
     159             :     friend bool VSIKerchunkConvertJSONToParquet(const char *pszSrcJSONFilename,
     160             :                                                 const char *pszDstDirname,
     161             :                                                 GDALProgressFunc pfnProgress,
     162             :                                                 void *pProgressData);
     163             : 
     164             :     lru11::Cache<std::string, std::shared_ptr<VSIKerchunkRefFile>, std::mutex>
     165             :         m_oCache{};
     166             : 
     167             :     static std::pair<std::string, std::string>
     168             :     SplitFilename(const char *pszFilename);
     169             : 
     170             :     std::pair<std::shared_ptr<VSIKerchunkRefFile>, std::string>
     171             :     Load(const std::string &osJSONFilename, bool bUseCache);
     172             :     std::shared_ptr<VSIKerchunkRefFile>
     173             :     LoadInternal(const std::string &osJSONFilename,
     174             :                  GDALProgressFunc pfnProgress, void *pProgressData);
     175             :     std::shared_ptr<VSIKerchunkRefFile>
     176             :     LoadStreaming(const std::string &osJSONFilename,
     177             :                   GDALProgressFunc pfnProgress, void *pProgressData);
     178             : };
     179             : 
     180             : /************************************************************************/
     181             : /*            VSIKerchunkJSONRefFileSystem::SplitFilename()             */
     182             : /************************************************************************/
     183             : 
     184             : /*static*/
     185             : std::pair<std::string, std::string>
     186         375 : VSIKerchunkJSONRefFileSystem::SplitFilename(const char *pszFilename)
     187             : {
     188         375 :     if (STARTS_WITH(pszFilename, JSON_REF_FS_PREFIX))
     189         319 :         pszFilename += strlen(JSON_REF_FS_PREFIX);
     190          56 :     else if (STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX))
     191          56 :         pszFilename += strlen(JSON_REF_CACHED_FS_PREFIX);
     192             :     else
     193           0 :         return {std::string(), std::string()};
     194             : 
     195         750 :     std::string osJSONFilename;
     196             : 
     197         375 :     if (*pszFilename == '{')
     198             :     {
     199             :         // Parse /vsikerchunk_json_ref/{/path/to/some.json}[key]
     200         345 :         int nLevel = 1;
     201         345 :         ++pszFilename;
     202       23875 :         for (; *pszFilename; ++pszFilename)
     203             :         {
     204       23863 :             if (*pszFilename == '{')
     205             :             {
     206           0 :                 ++nLevel;
     207             :             }
     208       23863 :             else if (*pszFilename == '}')
     209             :             {
     210         333 :                 --nLevel;
     211         333 :                 if (nLevel == 0)
     212             :                 {
     213         333 :                     ++pszFilename;
     214         333 :                     break;
     215             :                 }
     216             :             }
     217       23530 :             osJSONFilename += *pszFilename;
     218             :         }
     219         345 :         if (nLevel != 0)
     220             :         {
     221          12 :             CPLError(CE_Failure, CPLE_AppDefined,
     222             :                      "Invalid %s syntax: should be "
     223             :                      "%s{/path/to/some/file}[/optional_key]",
     224             :                      JSON_REF_FS_PREFIX, JSON_REF_FS_PREFIX);
     225          12 :             return {std::string(), std::string()};
     226             :         }
     227             : 
     228             :         return {osJSONFilename,
     229         666 :                 *pszFilename == '/' ? pszFilename + 1 : pszFilename};
     230             :     }
     231             :     else
     232             :     {
     233          30 :         int nCountDotJson = 0;
     234          30 :         const char *pszIter = pszFilename;
     235          30 :         const char *pszAfterJSON = nullptr;
     236          48 :         while ((pszIter = strstr(pszIter, ".json")) != nullptr)
     237             :         {
     238          18 :             ++nCountDotJson;
     239          18 :             if (nCountDotJson == 1)
     240          18 :                 pszAfterJSON = pszIter + strlen(".json");
     241             :             else
     242           0 :                 pszAfterJSON = nullptr;
     243          18 :             pszIter += strlen(".json");
     244             :         }
     245          30 :         if (!pszAfterJSON)
     246             :         {
     247          12 :             if (nCountDotJson >= 2)
     248             :             {
     249           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     250             :                          "Ambiguous %s syntax: should be "
     251             :                          "%s{/path/to/some/file}[/optional_key]",
     252             :                          JSON_REF_FS_PREFIX, JSON_REF_FS_PREFIX);
     253             :             }
     254             :             else
     255             :             {
     256          12 :                 CPLError(CE_Failure, CPLE_AppDefined,
     257             :                          "Invalid %s syntax: should be "
     258             :                          "%s/path/to/some.json[/optional_key] or "
     259             :                          "%s{/path/to/some/file}[/optional_key]",
     260             :                          JSON_REF_FS_PREFIX, JSON_REF_FS_PREFIX,
     261             :                          JSON_REF_FS_PREFIX);
     262             :             }
     263          12 :             return {std::string(), std::string()};
     264             :         }
     265          36 :         return {std::string(pszFilename, pszAfterJSON - pszFilename),
     266          36 :                 *pszAfterJSON == '/' ? pszAfterJSON + 1 : pszAfterJSON};
     267             :     }
     268             : }
     269             : 
     270             : /************************************************************************/
     271             : /*                  class VSIKerchunkJSONRefParser                      */
     272             : /************************************************************************/
     273             : 
     274             : namespace
     275             : {
     276             : class VSIKerchunkJSONRefParser final : public CPLJSonStreamingParser
     277             : {
     278             :   public:
     279          71 :     explicit VSIKerchunkJSONRefParser(
     280             :         const std::shared_ptr<VSIKerchunkRefFile> &refFile)
     281          71 :         : m_refFile(refFile)
     282             :     {
     283          71 :         m_oWriter.SetPrettyFormatting(false);
     284          71 :     }
     285             : 
     286          71 :     ~VSIKerchunkJSONRefParser() override
     287          71 :     {
     288             :         // In case the parsing would be stopped, the writer may be in
     289             :         // an inconsistent state. This avoids assertion in debug mode.
     290          71 :         m_oWriter.clear();
     291          71 :     }
     292             : 
     293             :   protected:
     294          78 :     void String(std::string_view sValue) override
     295             :     {
     296          78 :         if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 0)
     297             :         {
     298           9 :             size_t nLength = sValue.size();
     299           9 :             if (nLength > 0 && sValue[nLength - 1] == 0)
     300           2 :                 --nLength;
     301             : 
     302           9 :             if (!m_refFile->AddInlineContent(
     303           9 :                     m_osCurKey, std::string_view(sValue.data(), nLength)))
     304             :             {
     305           2 :                 StopParsing();
     306             :             }
     307             : 
     308           9 :             m_oWriter.clear();
     309             : 
     310           9 :             m_osCurKey.clear();
     311             :         }
     312          69 :         else if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 1)
     313             :         {
     314          42 :             if (m_iArrayMemberIdx == 0)
     315             :             {
     316          36 :                 m_osURI = sValue;
     317             :             }
     318             :             else
     319             :             {
     320           6 :                 UnexpectedContentInArray();
     321             :             }
     322             :         }
     323          27 :         else if (m_nLevel > m_nKeyLevel)
     324             :         {
     325          25 :             m_oWriter.Add(sValue);
     326             :         }
     327          78 :     }
     328             : 
     329         161 :     void Number(std::string_view sValue) override
     330             :     {
     331         161 :         if (m_nLevel == m_nKeyLevel)
     332             :         {
     333          52 :             if (m_nArrayLevel == 1)
     334             :             {
     335          48 :                 if (m_iArrayMemberIdx == 1)
     336             :                 {
     337          27 :                     m_osTmpForNumber = sValue;
     338          27 :                     errno = 0;
     339          27 :                     m_nOffset =
     340          27 :                         std::strtoull(m_osTmpForNumber.c_str(), nullptr, 10);
     341          50 :                     if (errno != 0 || m_osTmpForNumber[0] == '-' ||
     342          23 :                         m_osTmpForNumber.find('.') != std::string::npos)
     343             :                     {
     344           6 :                         CPLError(
     345             :                             CE_Failure, CPLE_AppDefined,
     346             :                             "VSIKerchunkJSONRefFileSystem: array value at "
     347             :                             "index 1 for key '%s' is not an unsigned 64 bit "
     348             :                             "integer",
     349             :                             m_osCurKey.c_str());
     350           6 :                         StopParsing();
     351             :                     }
     352             :                 }
     353          21 :                 else if (m_iArrayMemberIdx == 2)
     354             :                 {
     355          19 :                     m_osTmpForNumber = sValue;
     356          19 :                     errno = 0;
     357             :                     const uint64_t nSize =
     358          19 :                         std::strtoull(m_osTmpForNumber.c_str(), nullptr, 10);
     359          19 :                     if (errno != 0 || m_osTmpForNumber[0] == '-' ||
     360          53 :                         nSize > std::numeric_limits<uint32_t>::max() ||
     361          15 :                         m_osTmpForNumber.find('.') != std::string::npos)
     362             :                     {
     363           6 :                         CPLError(
     364             :                             CE_Failure, CPLE_AppDefined,
     365             :                             "VSIKerchunkJSONRefFileSystem: array value at "
     366             :                             "index 2 for key '%s' is not an unsigned 32 bit "
     367             :                             "integer",
     368             :                             m_osCurKey.c_str());
     369           6 :                         StopParsing();
     370             :                     }
     371             :                     else
     372             :                     {
     373          13 :                         m_nSize = static_cast<uint32_t>(nSize);
     374             :                     }
     375             :                 }
     376             :                 else
     377             :                 {
     378           2 :                     UnexpectedContentInArray();
     379             :                 }
     380             :             }
     381             :             else
     382             :             {
     383           4 :                 UnexpectedContent();
     384             :             }
     385             :         }
     386         109 :         else if (m_nLevel > m_nKeyLevel)
     387             :         {
     388         108 :             m_oWriter.AddSerializedValue(sValue);
     389             :         }
     390         161 :     }
     391             : 
     392           4 :     void Boolean(bool b) override
     393             :     {
     394           4 :         if (m_nLevel == m_nKeyLevel)
     395             :         {
     396           4 :             UnexpectedContent();
     397             :         }
     398           0 :         else if (m_nLevel > m_nKeyLevel)
     399             :         {
     400           0 :             m_oWriter.Add(b);
     401             :         }
     402           4 :     }
     403             : 
     404          28 :     void Null() override
     405             :     {
     406          28 :         if (m_nLevel == m_nKeyLevel)
     407             :         {
     408           4 :             UnexpectedContent();
     409             :         }
     410          24 :         else if (m_nLevel > m_nKeyLevel)
     411             :         {
     412          24 :             m_oWriter.AddNull();
     413             :         }
     414          28 :     }
     415             : 
     416         168 :     void StartObject() override
     417             :     {
     418         168 :         if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 1)
     419             :         {
     420           2 :             UnexpectedContentInArray();
     421             :         }
     422             :         else
     423             :         {
     424         166 :             if (m_nLevel >= m_nKeyLevel)
     425             :             {
     426          95 :                 m_oWriter.StartObj();
     427             :             }
     428         166 :             ++m_nLevel;
     429         166 :             m_bFirstMember = true;
     430             :         }
     431         168 :     }
     432             : 
     433         124 :     void EndObject() override
     434             :     {
     435         124 :         if (m_nLevel == m_nKeyLevel)
     436             :         {
     437          28 :             FinishObjectValueProcessing();
     438             :         }
     439         124 :         --m_nLevel;
     440         124 :         if (m_nLevel >= m_nKeyLevel)
     441             :         {
     442          95 :             m_oWriter.EndObj();
     443             :         }
     444         124 :     }
     445             : 
     446         330 :     void StartObjectMember(std::string_view sKey) override
     447             :     {
     448         330 :         if (m_nLevel == 1 && m_bFirstMember)
     449             :         {
     450          81 :             if (sKey == "version")
     451             :             {
     452           3 :                 m_nKeyLevel = 2;
     453             :             }
     454             :             else
     455             :             {
     456          78 :                 m_nKeyLevel = 1;
     457             :             }
     458             :         }
     459         249 :         else if (m_nLevel == 1 && m_nKeyLevel == 2 && sKey == "templates")
     460             :         {
     461           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     462             :                      "VSIKerchunkJSONRefFileSystem: 'templates' key found, but "
     463             :                      "not supported");
     464           1 :             StopParsing();
     465             :         }
     466         248 :         else if (m_nLevel == 1 && m_nKeyLevel == 2 && sKey == "gen")
     467             :         {
     468           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     469             :                      "VSIKerchunkJSONRefFileSystem: 'gen' key found, but not "
     470             :                      "supported");
     471           1 :             StopParsing();
     472             :         }
     473             : 
     474         330 :         if (m_nLevel == m_nKeyLevel)
     475             :         {
     476         158 :             FinishObjectValueProcessing();
     477         158 :             m_osCurKey = sKey;
     478             :         }
     479         172 :         else if (m_nLevel > m_nKeyLevel)
     480             :         {
     481         164 :             m_oWriter.AddObjKey(sKey);
     482             :         }
     483         330 :         m_bFirstMember = false;
     484         330 :     }
     485             : 
     486          83 :     void StartArray() override
     487             :     {
     488          83 :         if (m_nLevel == m_nKeyLevel)
     489             :         {
     490          46 :             if (m_nArrayLevel == 0)
     491             :             {
     492          46 :                 m_iArrayMemberIdx = -1;
     493          46 :                 m_osURI.clear();
     494          46 :                 m_nOffset = 0;
     495          46 :                 m_nSize = 0;
     496          46 :                 m_nArrayLevel = 1;
     497             :             }
     498             :             else
     499             :             {
     500           0 :                 UnexpectedContentInArray();
     501             :             }
     502             :         }
     503          37 :         else if (m_nLevel > m_nKeyLevel)
     504             :         {
     505          37 :             m_oWriter.StartArray();
     506          37 :             ++m_nArrayLevel;
     507             :         }
     508          83 :     }
     509             : 
     510          57 :     void EndArray() override
     511             :     {
     512          57 :         if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 1)
     513             :         {
     514          20 :             if (m_iArrayMemberIdx == -1)
     515             :             {
     516           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
     517             :                          "VSIKerchunkJSONRefFileSystem: array value for key "
     518             :                          "'%s' is not of size 1 or 3",
     519             :                          m_osCurKey.c_str());
     520           2 :                 StopParsing();
     521             :             }
     522             :             else
     523             :             {
     524          18 :                 m_refFile->AddReferencedContent(m_osCurKey, m_osURI, m_nOffset,
     525             :                                                 m_nSize);
     526          18 :                 --m_nArrayLevel;
     527          18 :                 m_oWriter.clear();
     528          18 :                 m_osCurKey.clear();
     529             :             }
     530             :         }
     531          37 :         else if (m_nLevel >= m_nKeyLevel)
     532             :         {
     533          37 :             --m_nArrayLevel;
     534          37 :             if (m_nLevel > m_nKeyLevel)
     535          37 :                 m_oWriter.EndArray();
     536             :         }
     537          57 :     }
     538             : 
     539         126 :     void StartArrayMember() override
     540             :     {
     541         126 :         if (m_nLevel >= m_nKeyLevel)
     542         126 :             ++m_iArrayMemberIdx;
     543         126 :     }
     544             : 
     545           2 :     void Exception(const char *pszMessage) override
     546             :     {
     547           2 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", pszMessage);
     548           2 :     }
     549             : 
     550             :   private:
     551             :     std::shared_ptr<VSIKerchunkRefFile> m_refFile{};
     552             :     int m_nLevel = 0;
     553             :     int m_nArrayLevel = 0;
     554             :     int m_iArrayMemberIdx = -1;
     555             :     bool m_bFirstMember = false;
     556             :     int m_nKeyLevel = std::numeric_limits<int>::max();
     557             :     std::string m_osCurKey{};
     558             :     std::string m_osURI{};
     559             :     std::string m_osTmpForNumber{};
     560             :     uint64_t m_nOffset = 0;
     561             :     uint32_t m_nSize = 0;
     562             : 
     563             :     CPLJSonStreamingWriter m_oWriter{nullptr, nullptr};
     564             : 
     565         186 :     void FinishObjectValueProcessing()
     566             :     {
     567         186 :         if (!m_osCurKey.empty())
     568             :         {
     569          93 :             const std::string &osStr = m_oWriter.GetString();
     570          93 :             CPL_IGNORE_RET_VAL(m_refFile->AddInlineContent(m_osCurKey, osStr));
     571             : 
     572          93 :             m_oWriter.clear();
     573             : 
     574          93 :             m_osCurKey.clear();
     575             :         }
     576         186 :     }
     577             : 
     578          12 :     void UnexpectedContent()
     579             :     {
     580          12 :         CPLError(CE_Failure, CPLE_AppDefined, "Unexpected content");
     581          12 :         StopParsing();
     582          12 :     }
     583             : 
     584          10 :     void UnexpectedContentInArray()
     585             :     {
     586          10 :         CPLError(CE_Failure, CPLE_AppDefined,
     587             :                  "Unexpected content at position %d of array",
     588             :                  m_iArrayMemberIdx);
     589          10 :         StopParsing();
     590          10 :     }
     591             : };
     592             : }  // namespace
     593             : 
     594             : /************************************************************************/
     595             : /*           VSIKerchunkJSONRefFileSystem::LoadStreaming()              */
     596             : /************************************************************************/
     597             : 
     598             : std::shared_ptr<VSIKerchunkRefFile>
     599          71 : VSIKerchunkJSONRefFileSystem::LoadStreaming(const std::string &osJSONFilename,
     600             :                                             GDALProgressFunc pfnProgress,
     601             :                                             void *pProgressData)
     602             : {
     603         142 :     auto refFile = std::make_shared<VSIKerchunkRefFile>();
     604         142 :     VSIKerchunkJSONRefParser parser(refFile);
     605             : 
     606          71 :     CPLDebugOnly("VSIKerchunkJSONRefFileSystem",
     607             :                  "Using streaming parser for %s", osJSONFilename.c_str());
     608             : 
     609             :     // For network file systems, get the streaming version of the filename,
     610             :     // as we don't need arbitrary seeking in the file
     611             :     const std::string osFilename =
     612          71 :         VSIFileManager::GetHandler(osJSONFilename.c_str())
     613         142 :             ->GetStreamingFilename(osJSONFilename);
     614             : 
     615         142 :     auto f = VSIVirtualHandleUniquePtr(VSIFOpenL(osFilename.c_str(), "rb"));
     616          71 :     if (!f)
     617             :     {
     618           2 :         CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
     619             :                  osJSONFilename.c_str());
     620           2 :         return nullptr;
     621             :     }
     622          69 :     uint64_t nTotalSize = 0;
     623          69 :     if (!cpl::starts_with(osFilename, "/vsigzip/"))
     624             :     {
     625          69 :         f->Seek(0, SEEK_END);
     626          69 :         nTotalSize = f->Tell();
     627          69 :         f->Seek(0, SEEK_SET);
     628             :     }
     629         138 :     std::string sBuffer;
     630          69 :     constexpr size_t BUFFER_SIZE = 10 * 1024 * 1024;  // Arbitrary
     631          69 :     sBuffer.resize(BUFFER_SIZE);
     632             :     while (true)
     633             :     {
     634          70 :         const size_t nRead = f->Read(sBuffer.data(), 1, sBuffer.size());
     635          70 :         const bool bFinished = nRead < sBuffer.size();
     636             :         try
     637             :         {
     638          70 :             if (!parser.Parse(std::string_view(sBuffer.data(), nRead),
     639             :                               bFinished))
     640             :             {
     641             :                 // The parser will have emitted an error
     642          42 :                 return nullptr;
     643             :             }
     644             :         }
     645           0 :         catch (const std::exception &e)
     646             :         {
     647             :             // Out-of-memory typically
     648           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     649             :                      "Exception occurred while parsing %s: %s",
     650           0 :                      osJSONFilename.c_str(), e.what());
     651           0 :             return nullptr;
     652             :         }
     653          28 :         if (nTotalSize)
     654             :         {
     655          27 :             const double dfProgressRatio = static_cast<double>(f->Tell()) /
     656          27 :                                            static_cast<double>(nTotalSize);
     657          27 :             CPLDebug("VSIKerchunkJSONRefFileSystem", "%02.1f %% of %s read",
     658             :                      100 * dfProgressRatio, osJSONFilename.c_str());
     659          28 :             if (pfnProgress &&
     660           1 :                 !pfnProgress(dfProgressRatio, "Parsing of JSON file",
     661             :                              pProgressData))
     662             :             {
     663           0 :                 return nullptr;
     664             :             }
     665             :         }
     666             :         else
     667             :         {
     668           1 :             CPLDebug("VSIKerchunkJSONRefFileSystem",
     669             :                      "%" PRIu64 " bytes read in %s",
     670           1 :                      static_cast<uint64_t>(f->Tell()), osJSONFilename.c_str());
     671             :         }
     672          28 :         if (nRead < sBuffer.size())
     673             :         {
     674          27 :             break;
     675             :         }
     676           1 :     }
     677          27 :     if (f->Tell() == 0)
     678             :     {
     679           1 :         CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
     680             :                  osJSONFilename.c_str());
     681           1 :         return nullptr;
     682             :     }
     683          26 :     if (pfnProgress)
     684           1 :         pfnProgress(1.0, "Parsing of JSON file", pProgressData);
     685             : 
     686          26 :     return refFile;
     687             : }
     688             : 
     689             : /************************************************************************/
     690             : /*             VSIKerchunkJSONRefFileSystem::LoadInternal()             */
     691             : /************************************************************************/
     692             : 
     693             : std::shared_ptr<VSIKerchunkRefFile>
     694         109 : VSIKerchunkJSONRefFileSystem::LoadInternal(const std::string &osJSONFilename,
     695             :                                            GDALProgressFunc pfnProgress,
     696             :                                            void *pProgressData)
     697             : {
     698         109 :     const char *pszUseStreamingParser = VSIGetPathSpecificOption(
     699             :         osJSONFilename.c_str(), "VSIKERCHUNK_USE_STREAMING_PARSER", "AUTO");
     700         109 :     if (EQUAL(pszUseStreamingParser, "AUTO"))
     701             :     {
     702             :         auto f =
     703          50 :             VSIVirtualHandleUniquePtr(VSIFOpenL(osJSONFilename.c_str(), "rb"));
     704          50 :         if (!f)
     705             :         {
     706           6 :             CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
     707             :                      osJSONFilename.c_str());
     708           6 :             return nullptr;
     709             :         }
     710          44 :         std::string sBuffer;
     711          44 :         constexpr size_t HEADER_SIZE = 1024;  // Arbitrary
     712          44 :         sBuffer.resize(HEADER_SIZE);
     713          44 :         const size_t nRead = f->Read(sBuffer.data(), 1, sBuffer.size());
     714          44 :         sBuffer.resize(nRead);
     715          44 :         if (ZARRIsLikelyStreamableKerchunkJSONRefContent(sBuffer))
     716             :         {
     717          41 :             return LoadStreaming(osJSONFilename, pfnProgress, pProgressData);
     718             :         }
     719             :     }
     720          59 :     else if (CPLTestBool(pszUseStreamingParser))
     721             :     {
     722          30 :         return LoadStreaming(osJSONFilename, pfnProgress, pProgressData);
     723             :     }
     724             : 
     725          64 :     CPLJSONDocument oDoc;
     726             :     {
     727             : #if SIZEOF_VOIDP > 4
     728          32 :         CPLConfigOptionSetter oSetter("CPL_JSON_MAX_SIZE", "1GB", true);
     729             : #endif
     730          32 :         if (!oDoc.Load(osJSONFilename))
     731             :         {
     732           5 :             CPLError(CE_Failure, CPLE_AppDefined,
     733             :                      "VSIKerchunkJSONRefFileSystem: cannot open %s",
     734             :                      osJSONFilename.c_str());
     735           5 :             return nullptr;
     736             :         }
     737             :     }
     738             : 
     739          54 :     auto oRoot = oDoc.GetRoot();
     740          81 :     const auto oVersion = oRoot.GetObj("version");
     741          54 :     CPLJSONObject oRefs;
     742          27 :     if (!oVersion.IsValid())
     743             :     {
     744             :         // Cf https://fsspec.github.io/kerchunk/spec.html#version-0
     745             : 
     746          23 :         CPLDebugOnly("VSIKerchunkJSONRefFileSystem",
     747             :                      "'version' key not found. Assuming version 0");
     748          23 :         oRefs = std::move(oRoot);
     749          23 :         if (!oRefs.GetObj(".zgroup").IsValid())
     750             :         {
     751           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     752             :                      "VSIKerchunkJSONRefFileSystem: '.zgroup' key not found");
     753           0 :             return nullptr;
     754             :         }
     755             :     }
     756           4 :     else if (oVersion.GetType() != CPLJSONObject::Type::Integer)
     757             :     {
     758           4 :         CPLError(CE_Failure, CPLE_AppDefined,
     759             :                  "VSIKerchunkJSONRefFileSystem: 'version' key not integer");
     760           4 :         return nullptr;
     761             :     }
     762           0 :     else if (oVersion.ToInteger() != 1)
     763             :     {
     764           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     765             :                  "VSIKerchunkJSONRefFileSystem: 'version' = %d not handled",
     766             :                  oVersion.ToInteger());
     767           0 :         return nullptr;
     768             :     }
     769             :     else
     770             :     {
     771             :         // Cf https://fsspec.github.io/kerchunk/spec.html#version-1
     772             : 
     773           0 :         if (oRoot.GetObj("templates").IsValid())
     774             :         {
     775           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     776             :                      "VSIKerchunkJSONRefFileSystem: 'templates' key found, but "
     777             :                      "not supported");
     778           0 :             return nullptr;
     779             :         }
     780             : 
     781           0 :         if (oRoot.GetObj("gen").IsValid())
     782             :         {
     783           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     784             :                      "VSIKerchunkJSONRefFileSystem: 'gen' key found, but not "
     785             :                      "supported");
     786           0 :             return nullptr;
     787             :         }
     788             : 
     789           0 :         oRefs = oRoot.GetObj("refs");
     790           0 :         if (!oRefs.IsValid())
     791             :         {
     792           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     793             :                      "VSIKerchunkJSONRefFileSystem: 'refs' key not found");
     794           0 :             return nullptr;
     795             :         }
     796             : 
     797           0 :         if (oRoot.GetObj("templates").IsValid())
     798             :         {
     799           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     800             :                      "VSIKerchunkJSONRefFileSystem: 'templates' key found but "
     801             :                      "not supported");
     802           0 :             return nullptr;
     803             :         }
     804             : 
     805           0 :         if (oRoot.GetObj("templates").IsValid())
     806             :         {
     807           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     808             :                      "VSIKerchunkJSONRefFileSystem: 'templates' key found but "
     809             :                      "not supported");
     810           0 :             return nullptr;
     811             :         }
     812             :     }
     813             : 
     814          23 :     if (oRefs.GetType() != CPLJSONObject::Type::Object)
     815             :     {
     816           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     817             :                  "VSIKerchunkJSONRefFileSystem: value of 'refs' is not a dict");
     818           0 :         return nullptr;
     819             :     }
     820             : 
     821          46 :     auto refFile = std::make_shared<VSIKerchunkRefFile>();
     822          50 :     for (const auto &oEntry : oRefs.GetChildren())
     823             :     {
     824          45 :         const std::string osKeyName = oEntry.GetName();
     825          45 :         if (oEntry.GetType() == CPLJSONObject::Type::String)
     826             :         {
     827           2 :             if (!refFile->AddInlineContent(osKeyName, oEntry.ToString()))
     828             :             {
     829           1 :                 return nullptr;
     830             :             }
     831             :         }
     832          43 :         else if (oEntry.GetType() == CPLJSONObject::Type::Object)
     833             :         {
     834             :             const std::string osSerialized =
     835          24 :                 oEntry.Format(CPLJSONObject::PrettyFormat::Plain);
     836          24 :             CPL_IGNORE_RET_VAL(
     837          24 :                 refFile->AddInlineContent(osKeyName, osSerialized));
     838             :         }
     839          19 :         else if (oEntry.GetType() == CPLJSONObject::Type::Array)
     840             :         {
     841          15 :             const auto oArray = oEntry.ToArray();
     842             :             // Some files such as https://ncsa.osn.xsede.org/Pangeo/pangeo-forge/pangeo-forge/aws-noaa-oisst-feedstock/aws-noaa-oisst-avhrr-only.zarr/reference.json
     843             :             // (pointed by https://guide.cloudnativegeo.org/kerchunk/kerchunk-in-practice.html)
     844             :             // have array entries with just the URL, and no offset/size
     845             :             // This is when the whole file needs to be read
     846          15 :             if (oArray.Size() != 1 && oArray.Size() != 3)
     847             :             {
     848           3 :                 CPLError(CE_Failure, CPLE_AppDefined,
     849             :                          "VSIKerchunkJSONRefFileSystem: array value for key "
     850             :                          "'%s' is not of size 1 or 3",
     851             :                          osKeyName.c_str());
     852           3 :                 return nullptr;
     853             :             }
     854          12 :             if (oArray[0].GetType() != CPLJSONObject::Type::String)
     855             :             {
     856           4 :                 CPLError(CE_Failure, CPLE_AppDefined,
     857             :                          "VSIKerchunkJSONRefFileSystem: array value at index 0 "
     858             :                          "for key '%s' is not a string",
     859             :                          osKeyName.c_str());
     860           4 :                 return nullptr;
     861             :             }
     862           8 :             if (oArray.Size() == 3)
     863             :             {
     864           8 :                 if ((oArray[1].GetType() != CPLJSONObject::Type::Integer &&
     865          23 :                      oArray[1].GetType() != CPLJSONObject::Type::Long) ||
     866          15 :                     !(oArray[1].ToLong() >= 0))
     867             :                 {
     868           2 :                     CPLError(
     869             :                         CE_Failure, CPLE_AppDefined,
     870             :                         "VSIKerchunkJSONRefFileSystem: array value at index 1 "
     871             :                         "for key '%s' is not an unsigned 64 bit integer",
     872             :                         osKeyName.c_str());
     873           2 :                     return nullptr;
     874             :                 }
     875           6 :                 if ((oArray[2].GetType() != CPLJSONObject::Type::Integer &&
     876          16 :                      oArray[2].GetType() != CPLJSONObject::Type::Long) ||
     877          10 :                     !(oArray[2].ToLong() >= 0 &&
     878           9 :                       static_cast<uint64_t>(oArray[2].ToLong()) <=
     879           3 :                           std::numeric_limits<uint32_t>::max()))
     880             :                 {
     881           4 :                     CPLError(
     882             :                         CE_Failure, CPLE_AppDefined,
     883             :                         "VSIKerchunkJSONRefFileSystem: array value at index 2 "
     884             :                         "for key '%s' is not an unsigned 32 bit integer",
     885             :                         osKeyName.c_str());
     886           4 :                     return nullptr;
     887             :                 }
     888             :             }
     889             : 
     890           4 :             refFile->AddReferencedContent(
     891           4 :                 osKeyName, oArray[0].ToString(),
     892           4 :                 oArray.Size() == 3 ? oArray[1].ToLong() : 0,
     893           4 :                 oArray.Size() == 3 ? static_cast<uint32_t>(oArray[2].ToLong())
     894             :                                    : 0);
     895             :         }
     896             :         else
     897             :         {
     898           4 :             CPLError(
     899             :                 CE_Failure, CPLE_AppDefined,
     900             :                 "VSIKerchunkJSONRefFileSystem: invalid value type for key '%s'",
     901             :                 osKeyName.c_str());
     902           4 :             return nullptr;
     903             :         }
     904             :     }
     905             : 
     906           5 :     return refFile;
     907             : }
     908             : 
     909             : /************************************************************************/
     910             : /*               VSIKerchunkJSONRefFileSystem::Load()                   */
     911             : /************************************************************************/
     912             : 
     913             : std::pair<std::shared_ptr<VSIKerchunkRefFile>, std::string>
     914         351 : VSIKerchunkJSONRefFileSystem::Load(const std::string &osJSONFilename,
     915             :                                    bool bUseCache)
     916             : {
     917         351 :     std::shared_ptr<VSIKerchunkRefFile> refFile;
     918         351 :     if (m_oCache.tryGet(osJSONFilename, refFile))
     919         227 :         return {refFile, std::string()};
     920             : 
     921             :     // Deal with local file cache
     922         124 :     const char *pszUseCache = VSIGetPathSpecificOption(
     923             :         osJSONFilename.c_str(), "VSIKERCHUNK_USE_CACHE", "NO");
     924         124 :     if (bUseCache || CPLTestBool(pszUseCache))
     925             :     {
     926          26 :         if (GDALGetDriverByName("PARQUET") == nullptr)
     927             :         {
     928           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     929             :                      "VSIKERCHUNK_USE_CACHE=YES only enabled if PARQUET driver "
     930             :                      "is available");
     931           0 :             return {nullptr, std::string()};
     932             :         }
     933             : 
     934             :         VSIStatBufL sStat;
     935          52 :         if (VSIStatL(osJSONFilename.c_str(), &sStat) != 0 ||
     936          26 :             VSI_ISDIR(sStat.st_mode))
     937             :         {
     938           0 :             CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
     939             :                      osJSONFilename.c_str());
     940           0 :             return {nullptr, std::string()};
     941             :         }
     942             : 
     943          26 :         std::string osCacheSubDir = CPLGetBasenameSafe(osJSONFilename.c_str());
     944             :         osCacheSubDir += CPLSPrintf("_%" PRIu64 "_%" PRIu64,
     945          26 :                                     static_cast<uint64_t>(sStat.st_size),
     946          26 :                                     static_cast<uint64_t>(sStat.st_mtime));
     947             : 
     948          26 :         const std::string osRootCacheDir = GDALGetCacheDirectory();
     949          26 :         if (!osRootCacheDir.empty())
     950             :         {
     951             :             const std::string osKerchunkCacheDir = VSIGetPathSpecificOption(
     952             :                 osJSONFilename.c_str(), "VSIKERCHUNK_CACHE_DIR",
     953          52 :                 CPLFormFilenameSafe(osRootCacheDir.c_str(),
     954             :                                     "zarr_kerchunk_cache", nullptr)
     955          78 :                     .c_str());
     956             :             const std::string osCacheDir = CPLFormFilenameSafe(
     957          52 :                 osKerchunkCacheDir.c_str(), osCacheSubDir.c_str(), "zarr");
     958          26 :             CPLDebug("VSIKerchunkJSONRefFileSystem", "Using cache dir %s",
     959             :                      osCacheDir.c_str());
     960             : 
     961          52 :             if (VSIStatL(CPLFormFilenameSafe(osCacheDir.c_str(), ".zmetadata",
     962             :                                              nullptr)
     963             :                              .c_str(),
     964          26 :                          &sStat) == 0)
     965             :             {
     966           9 :                 CPLDebug("VSIKerchunkJSONRefFileSystem",
     967             :                          "Using Kerchunk Parquet cache %s", osCacheDir.c_str());
     968           9 :                 return {nullptr, osCacheDir};
     969             :             }
     970             : 
     971          23 :             if (VSIMkdirRecursive(osCacheDir.c_str(), 0755) != 0 &&
     972           6 :                 !(VSIStatL(osCacheDir.c_str(), &sStat) == 0 &&
     973           0 :                   VSI_ISDIR(sStat.st_mode)))
     974             :             {
     975           6 :                 CPLError(CE_Failure, CPLE_AppDefined,
     976             :                          "Cannot create directory %s", osCacheDir.c_str());
     977           6 :                 return {nullptr, std::string()};
     978             :             }
     979             : 
     980             :             const std::string osLockFilename =
     981          22 :                 CPLFormFilenameSafe(osCacheDir.c_str(), ".lock", nullptr);
     982             : 
     983          11 :             CPLLockFileHandle hLockHandle = nullptr;
     984          22 :             CPLStringList aosOptions;
     985          11 :             aosOptions.SetNameValue("VERBOSE_WAIT_MESSAGE", "YES");
     986             :             const char *pszKerchunkDebug =
     987          11 :                 CPLGetConfigOption("VSIKERCHUNK_FOR_TESTS", nullptr);
     988          11 :             if (pszKerchunkDebug &&
     989           4 :                 strstr(pszKerchunkDebug, "SHORT_DELAY_STALLED_LOCK"))
     990             :             {
     991           2 :                 aosOptions.SetNameValue("STALLED_DELAY", "1");
     992             :             }
     993             : 
     994          11 :             CPLDebug("VSIKerchunkJSONRefFileSystem", "Acquiring lock");
     995          11 :             switch (CPLLockFileEx(osLockFilename.c_str(), &hLockHandle,
     996          11 :                                   aosOptions.List()))
     997             :             {
     998          10 :                 case CLFS_OK:
     999          10 :                     break;
    1000           1 :                 case CLFS_CANNOT_CREATE_LOCK:
    1001           1 :                     CPLError(CE_Failure, CPLE_FileIO, "Cannot create lock %s",
    1002             :                              osLockFilename.c_str());
    1003           1 :                     break;
    1004           0 :                 case CLFS_LOCK_BUSY:
    1005           0 :                     CPLAssert(false);  // cannot happen with infinite wait time
    1006             :                     break;
    1007           0 :                 case CLFS_API_MISUSE:
    1008           0 :                     CPLAssert(false);
    1009             :                     break;
    1010           0 :                 case CLFS_THREAD_CREATION_FAILED:
    1011           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1012             :                              "Thread creation failed for refresh of %s",
    1013             :                              osLockFilename.c_str());
    1014           0 :                     break;
    1015             :             }
    1016          11 :             if (!hLockHandle)
    1017             :             {
    1018           1 :                 return {nullptr, std::string()};
    1019             :             }
    1020             : 
    1021             :             struct LockFileHolder
    1022             :             {
    1023             :                 CPLLockFileHandle m_hLockHandle = nullptr;
    1024             : 
    1025          10 :                 explicit LockFileHolder(CPLLockFileHandle hLockHandleIn)
    1026          10 :                     : m_hLockHandle(hLockHandleIn)
    1027             :                 {
    1028          10 :                 }
    1029             : 
    1030          10 :                 ~LockFileHolder()
    1031          10 :                 {
    1032          10 :                     release();
    1033          10 :                 }
    1034             : 
    1035          20 :                 void release()
    1036             :                 {
    1037          20 :                     if (m_hLockHandle)
    1038             :                     {
    1039          10 :                         CPLDebug("VSIKerchunkJSONRefFileSystem",
    1040             :                                  "Releasing lock");
    1041          10 :                         CPLUnlockFileEx(m_hLockHandle);
    1042          10 :                         m_hLockHandle = nullptr;
    1043             :                     }
    1044          20 :                 }
    1045             : 
    1046             :                 CPL_DISALLOW_COPY_ASSIGN(LockFileHolder)
    1047             :             };
    1048             : 
    1049          20 :             LockFileHolder lockFileHolder(hLockHandle);
    1050             : 
    1051          20 :             if (VSIStatL(CPLFormFilenameSafe(osCacheDir.c_str(), ".zmetadata",
    1052             :                                              nullptr)
    1053             :                              .c_str(),
    1054          10 :                          &sStat) == 0)
    1055             :             {
    1056           0 :                 CPLDebug("VSIKerchunkJSONRefFileSystem",
    1057             :                          "Using Kerchunk Parquet cache %s (after lock taking)",
    1058             :                          osCacheDir.c_str());
    1059           0 :                 return {nullptr, osCacheDir};
    1060             :             }
    1061             : 
    1062          10 :             refFile = LoadInternal(osJSONFilename, nullptr, nullptr);
    1063             : 
    1064          10 :             if (refFile)
    1065             :             {
    1066          10 :                 CPLDebug("VSIKerchunkJSONRefFileSystem",
    1067             :                          "Generating Kerchunk Parquet cache %s...",
    1068             :                          osCacheDir.c_str());
    1069             : 
    1070          10 :                 if (pszKerchunkDebug &&
    1071           3 :                     strstr(pszKerchunkDebug,
    1072             :                            "WAIT_BEFORE_CONVERT_TO_PARQUET_REF"))
    1073             :                 {
    1074           1 :                     CPLSleep(0.5);
    1075             :                 }
    1076             : 
    1077          10 :                 if (refFile->ConvertToParquetRef(osCacheDir, nullptr, nullptr))
    1078             :                 {
    1079           4 :                     CPLDebug("VSIKerchunkJSONRefFileSystem",
    1080             :                              "Generation Kerchunk Parquet cache %s: OK",
    1081             :                              osCacheDir.c_str());
    1082             :                 }
    1083             :                 else
    1084             :                 {
    1085           6 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1086             :                              "Generation of Kerchunk Parquet cache %s failed",
    1087             :                              osCacheDir.c_str());
    1088           6 :                     refFile.reset();
    1089             :                 }
    1090             : 
    1091          10 :                 lockFileHolder.release();
    1092          10 :                 m_oCache.insert(osJSONFilename, refFile);
    1093             :             }
    1094             : 
    1095          10 :             return {refFile, std::string()};
    1096             :         }
    1097             :     }
    1098             : 
    1099          98 :     refFile = LoadInternal(osJSONFilename, nullptr, nullptr);
    1100          98 :     if (refFile)
    1101          20 :         m_oCache.insert(osJSONFilename, refFile);
    1102          98 :     return {refFile, std::string()};
    1103             : }
    1104             : 
    1105             : /************************************************************************/
    1106             : /*          VSIKerchunkRefFile::ConvertToParquetRef()                   */
    1107             : /************************************************************************/
    1108             : 
    1109          11 : bool VSIKerchunkRefFile::ConvertToParquetRef(const std::string &osCacheDir,
    1110             :                                              GDALProgressFunc pfnProgress,
    1111             :                                              void *pProgressData)
    1112             : {
    1113             :     struct Serializer
    1114             :     {
    1115             :         VSIVirtualHandle *m_poFile = nullptr;
    1116             : 
    1117          11 :         explicit Serializer(VSIVirtualHandle *poFile) : m_poFile(poFile)
    1118             :         {
    1119          11 :         }
    1120             : 
    1121         289 :         static void func(const char *pszTxt, void *pUserData)
    1122             :         {
    1123         289 :             Serializer *self = static_cast<Serializer *>(pUserData);
    1124         289 :             self->m_poFile->Write(pszTxt, 1, strlen(pszTxt));
    1125         289 :         }
    1126             :     };
    1127             : 
    1128             :     const std::string osZMetadataFilename =
    1129          22 :         CPLFormFilenameSafe(osCacheDir.c_str(), ".zmetadata", nullptr);
    1130          22 :     const std::string osZMetadataTmpFilename = osZMetadataFilename + ".tmp";
    1131             :     auto poFile = std::unique_ptr<VSIVirtualHandle>(
    1132          22 :         VSIFOpenL(osZMetadataTmpFilename.c_str(), "wb"));
    1133          11 :     if (!poFile)
    1134           0 :         return false;
    1135          11 :     Serializer serializer(poFile.get());
    1136             : 
    1137             :     struct ZarrArrayInfo
    1138             :     {
    1139             :         std::vector<uint64_t> anChunkCount{};
    1140             :         std::map<uint64_t, const VSIKerchunkKeyInfo *> chunkInfo{};
    1141             :     };
    1142             : 
    1143          22 :     std::map<std::string, ZarrArrayInfo> zarrArrays;
    1144             : 
    1145             :     // First pass on keys to write JSON objects in .zmetadata
    1146          22 :     CPLJSonStreamingWriter oWriter(Serializer::func, &serializer);
    1147          11 :     oWriter.StartObj();
    1148          11 :     oWriter.AddObjKey("metadata");
    1149          11 :     oWriter.StartObj();
    1150             : 
    1151          11 :     bool bOK = true;
    1152          11 :     size_t nCurObjectIter = 0;
    1153          11 :     const size_t nTotalObjects = m_oMapKeys.size();
    1154          44 :     for (const auto &[key, info] : m_oMapKeys)
    1155             :     {
    1156          54 :         if (cpl::ends_with(key, ".zarray") || cpl::ends_with(key, ".zgroup") ||
    1157          16 :             cpl::ends_with(key, ".zattrs"))
    1158             :         {
    1159          32 :             CPLJSONDocument oDoc;
    1160          32 :             std::string osStr;
    1161          32 :             osStr.append(reinterpret_cast<const char *>(info.abyValue.data()),
    1162          64 :                          info.abyValue.size());
    1163          32 :             if (!oDoc.LoadMemory(osStr.c_str()))
    1164             :             {
    1165           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1166             :                          "Cannot parse JSON content for %s", key.c_str());
    1167           1 :                 bOK = false;
    1168           1 :                 break;
    1169             :             }
    1170             : 
    1171          31 :             if (cpl::ends_with(key, ".zarray"))
    1172             :             {
    1173          10 :                 const std::string osArrayName = CPLGetDirnameSafe(key.c_str());
    1174             : 
    1175          20 :                 const auto oShape = oDoc.GetRoot().GetArray("shape");
    1176          20 :                 const auto oChunks = oDoc.GetRoot().GetArray("chunks");
    1177          20 :                 if (oShape.IsValid() && oChunks.IsValid() &&
    1178          10 :                     oShape.Size() == oChunks.Size())
    1179             :                 {
    1180          18 :                     std::vector<uint64_t> anChunkCount;
    1181           9 :                     uint64_t nTotalChunkCount = 1;
    1182          17 :                     for (int i = 0; i < oShape.Size(); ++i)
    1183             :                     {
    1184          11 :                         const auto nShape = oShape[i].ToLong();
    1185          11 :                         const auto nChunk = oChunks[i].ToLong();
    1186          11 :                         if (nShape == 0 || nChunk == 0)
    1187             :                         {
    1188           2 :                             bOK = false;
    1189           3 :                             break;
    1190             :                         }
    1191           9 :                         const uint64_t nChunkCount =
    1192           9 :                             DIV_ROUND_UP(nShape, nChunk);
    1193           9 :                         if (nChunkCount > std::numeric_limits<uint64_t>::max() /
    1194             :                                               nTotalChunkCount)
    1195             :                         {
    1196           1 :                             bOK = false;
    1197           1 :                             break;
    1198             :                         }
    1199           8 :                         anChunkCount.push_back(nChunkCount);
    1200           8 :                         nTotalChunkCount *= nChunkCount;
    1201             :                     }
    1202           9 :                     zarrArrays[osArrayName].anChunkCount =
    1203          18 :                         std::move(anChunkCount);
    1204             :                 }
    1205             :                 else
    1206             :                 {
    1207           1 :                     bOK = false;
    1208             :                 }
    1209          10 :                 if (!bOK)
    1210             :                 {
    1211           4 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1212             :                              "Invalid Zarr array definition for %s",
    1213             :                              osArrayName.c_str());
    1214           4 :                     oWriter.clear();
    1215           4 :                     break;
    1216             :                 }
    1217             :             }
    1218             : 
    1219          27 :             oWriter.AddObjKey(key);
    1220          27 :             oWriter.AddSerializedValue(osStr);
    1221             : 
    1222          27 :             ++nCurObjectIter;
    1223          31 :             if (pfnProgress &&
    1224           4 :                 !pfnProgress(static_cast<double>(nCurObjectIter) /
    1225           4 :                                  static_cast<double>(nTotalObjects),
    1226             :                              "", pProgressData))
    1227             :             {
    1228           0 :                 return false;
    1229             :             }
    1230             :         }
    1231             :     }
    1232             : 
    1233          11 :     constexpr int nRecordSize = 100000;
    1234             : 
    1235          11 :     if (bOK)
    1236             :     {
    1237           6 :         oWriter.EndObj();
    1238           6 :         oWriter.AddObjKey("record_size");
    1239           6 :         oWriter.Add(nRecordSize);
    1240           6 :         oWriter.EndObj();
    1241             :     }
    1242             : 
    1243          11 :     bOK = (poFile->Close() == 0) && bOK;
    1244          11 :     poFile.reset();
    1245             : 
    1246          11 :     if (!bOK)
    1247             :     {
    1248           5 :         oWriter.clear();
    1249           5 :         VSIUnlink(osZMetadataTmpFilename.c_str());
    1250           5 :         return false;
    1251             :     }
    1252             : 
    1253             :     // Second pass to retrieve chunks
    1254          33 :     for (const auto &[key, info] : m_oMapKeys)
    1255             :     {
    1256          44 :         if (cpl::ends_with(key, ".zarray") || cpl::ends_with(key, ".zgroup") ||
    1257          16 :             cpl::ends_with(key, ".zattrs"))
    1258             :         {
    1259             :             // already done
    1260             :         }
    1261             :         else
    1262             :         {
    1263           6 :             const std::string osArrayName = CPLGetDirnameSafe(key.c_str());
    1264           6 :             auto oIter = zarrArrays.find(osArrayName);
    1265           6 :             if (oIter != zarrArrays.end())
    1266             :             {
    1267           6 :                 auto &oArrayInfo = oIter->second;
    1268             :                 const CPLStringList aosIndices(
    1269           6 :                     CSLTokenizeString2(CPLGetFilename(key.c_str()), ".", 0));
    1270           6 :                 if ((static_cast<size_t>(aosIndices.size()) ==
    1271           6 :                      oArrayInfo.anChunkCount.size()) ||
    1272           0 :                     (aosIndices.size() == 1 &&
    1273           0 :                      strcmp(aosIndices[0], "0") == 0 &&
    1274           0 :                      oArrayInfo.anChunkCount.empty()))
    1275             :                 {
    1276           6 :                     std::vector<uint64_t> anIndices;
    1277          11 :                     for (size_t i = 0; i < oArrayInfo.anChunkCount.size(); ++i)
    1278             :                     {
    1279           6 :                         char *endptr = nullptr;
    1280           6 :                         anIndices.push_back(
    1281           6 :                             std::strtoull(aosIndices[i], &endptr, 10));
    1282           6 :                         if (aosIndices[i][0] == '-' ||
    1283          12 :                             endptr != aosIndices[i] + strlen(aosIndices[i]) ||
    1284           6 :                             anIndices[i] >= oArrayInfo.anChunkCount[i])
    1285             :                         {
    1286           1 :                             CPLError(CE_Failure, CPLE_AppDefined,
    1287             :                                      "Invalid key indices: %s", key.c_str());
    1288           1 :                             return false;
    1289             :                         }
    1290             :                     }
    1291             : 
    1292           5 :                     uint64_t nLinearIndex = 0;
    1293           5 :                     uint64_t nMulFactor = 1;
    1294          10 :                     for (size_t i = anIndices.size(); i > 0;)
    1295             :                     {
    1296           5 :                         --i;
    1297           5 :                         nLinearIndex += anIndices[i] * nMulFactor;
    1298           5 :                         nMulFactor *= oArrayInfo.anChunkCount[i];
    1299             :                     }
    1300             : 
    1301             : #ifdef DEBUG_VERBOSE
    1302             :                     CPLDebugOnly("VSIKerchunkJSONRefFileSystem",
    1303             :                                  "Chunk %" PRIu64 " of array %s found",
    1304             :                                  nLinearIndex, osArrayName.c_str());
    1305             : #endif
    1306           5 :                     oArrayInfo.chunkInfo[nLinearIndex] = &info;
    1307             :                 }
    1308             :             }
    1309             :         }
    1310             :     }
    1311             : 
    1312           5 :     auto poDrv = GetGDALDriverManager()->GetDriverByName("PARQUET");
    1313           5 :     if (!poDrv)
    1314             :     {
    1315             :         // shouldn't happen given earlier checks
    1316           0 :         CPLAssert(false);
    1317             :         return false;
    1318             :     }
    1319             : 
    1320             :     // Third pass to create Parquet files
    1321          10 :     CPLStringList aosLayerCreationOptions;
    1322             :     aosLayerCreationOptions.SetNameValue("ROW_GROUP_SIZE",
    1323           5 :                                          CPLSPrintf("%d", nRecordSize));
    1324             : 
    1325          10 :     for (const auto &[osArrayName, oArrayInfo] : zarrArrays)
    1326             :     {
    1327           5 :         uint64_t nChunkCount = 1;
    1328          10 :         for (size_t i = 0; i < oArrayInfo.anChunkCount.size(); ++i)
    1329             :         {
    1330           5 :             nChunkCount *= oArrayInfo.anChunkCount[i];
    1331             :         }
    1332             : 
    1333           0 :         std::unique_ptr<GDALDataset> poDS;
    1334           5 :         OGRLayer *poLayer = nullptr;
    1335             : 
    1336          10 :         for (uint64_t i = 0; i < nChunkCount; ++i)
    1337             :         {
    1338           5 :             bOK = poLayer != nullptr;
    1339           5 :             if ((i % nRecordSize) == 0)
    1340             :             {
    1341           5 :                 if (poDS)
    1342             :                 {
    1343           0 :                     if (poDS->Close() != CE_None)
    1344             :                     {
    1345           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1346             :                                  "Close() on %s failed",
    1347           0 :                                  poDS->GetDescription());
    1348           0 :                         return false;
    1349             :                     }
    1350           0 :                     poDS.reset();
    1351             :                 }
    1352           5 :                 if (CPLHasPathTraversal(osArrayName.c_str()))
    1353             :                 {
    1354           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1355             :                              "Path traversal detected in %s",
    1356             :                              osArrayName.c_str());
    1357           0 :                     return false;
    1358             :                 }
    1359             :                 const std::string osParqFilename = CPLFormFilenameSafe(
    1360           5 :                     CPLFormFilenameSafe(osCacheDir.c_str(), osArrayName.c_str(),
    1361             :                                         nullptr)
    1362             :                         .c_str(),
    1363             :                     CPLSPrintf("refs.%" PRIu64 ".parq", i / nRecordSize),
    1364          10 :                     nullptr);
    1365           5 :                 CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Creating %s",
    1366             :                              osParqFilename.c_str());
    1367           5 :                 VSIMkdirRecursive(
    1368          10 :                     CPLGetPathSafe(osParqFilename.c_str()).c_str(), 0755);
    1369           5 :                 poDS.reset(poDrv->Create(osParqFilename.c_str(), 0, 0, 0,
    1370             :                                          GDT_Unknown, nullptr));
    1371           5 :                 if (!poDS)
    1372           0 :                     return false;
    1373          10 :                 poLayer = poDS->CreateLayer(
    1374          10 :                     CPLGetBasenameSafe(osParqFilename.c_str()).c_str(), nullptr,
    1375           5 :                     wkbNone, aosLayerCreationOptions.List());
    1376           5 :                 bOK = false;
    1377           5 :                 if (poLayer)
    1378             :                 {
    1379             :                     {
    1380           5 :                         OGRFieldDefn oFieldDefn("path", OFTString);
    1381           5 :                         bOK = poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
    1382             :                     }
    1383             :                     {
    1384           5 :                         OGRFieldDefn oFieldDefn("offset", OFTInteger64);
    1385          10 :                         bOK = bOK &&
    1386           5 :                               poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
    1387             :                     }
    1388             :                     {
    1389           5 :                         OGRFieldDefn oFieldDefn("size", OFTInteger64);
    1390          10 :                         bOK = bOK &&
    1391           5 :                               poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
    1392             :                     }
    1393             :                     {
    1394           5 :                         OGRFieldDefn oFieldDefn("raw", OFTBinary);
    1395          10 :                         bOK = bOK &&
    1396           5 :                               poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
    1397             :                     }
    1398             :                 }
    1399             :             }
    1400           5 :             if (!bOK)
    1401           0 :                 return false;
    1402             : 
    1403             :             auto poFeature =
    1404           5 :                 std::make_unique<OGRFeature>(poLayer->GetLayerDefn());
    1405           5 :             auto oIter = oArrayInfo.chunkInfo.find(i);
    1406           5 :             if (oIter != oArrayInfo.chunkInfo.end())
    1407             :             {
    1408           5 :                 const auto *chunkInfo = oIter->second;
    1409           5 :                 if (chunkInfo->posURI)
    1410           5 :                     poFeature->SetField(0, chunkInfo->posURI->c_str());
    1411           5 :                 poFeature->SetField(1,
    1412           5 :                                     static_cast<GIntBig>(chunkInfo->nOffset));
    1413           5 :                 poFeature->SetField(2, static_cast<GIntBig>(chunkInfo->nSize));
    1414           5 :                 if (!chunkInfo->abyValue.empty())
    1415             :                 {
    1416           0 :                     if (chunkInfo->abyValue.size() >
    1417             :                         static_cast<size_t>(INT_MAX))
    1418             :                     {
    1419           0 :                         CPLError(CE_Failure, CPLE_NotSupported,
    1420             :                                  "Too big blob for chunk %" PRIu64
    1421             :                                  " of array %s",
    1422             :                                  i, osArrayName.c_str());
    1423           0 :                         return false;
    1424             :                     }
    1425           0 :                     poFeature->SetField(
    1426           0 :                         3, static_cast<int>(chunkInfo->abyValue.size()),
    1427           0 :                         static_cast<const void *>(chunkInfo->abyValue.data()));
    1428             :                 }
    1429             :             }
    1430             : 
    1431           5 :             if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
    1432             :             {
    1433           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1434             :                          "CreateFeature() on %s failed",
    1435           0 :                          poDS->GetDescription());
    1436           0 :                 return false;
    1437             :             }
    1438             : 
    1439           5 :             ++nCurObjectIter;
    1440           5 :             if (pfnProgress && (nCurObjectIter % 1000) == 0 &&
    1441           0 :                 !pfnProgress(static_cast<double>(nCurObjectIter) /
    1442           0 :                                  static_cast<double>(nTotalObjects),
    1443             :                              "", pProgressData))
    1444             :             {
    1445           0 :                 return false;
    1446             :             }
    1447             :         }
    1448             : 
    1449           5 :         if (poDS)
    1450             :         {
    1451           5 :             if (poDS->Close() != CE_None)
    1452             :             {
    1453           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Close() on %s failed",
    1454           0 :                          poDS->GetDescription());
    1455           0 :                 return false;
    1456             :             }
    1457           5 :             poDS.reset();
    1458             :         }
    1459             :     }
    1460             : 
    1461           5 :     if (VSIRename(osZMetadataTmpFilename.c_str(),
    1462           5 :                   osZMetadataFilename.c_str()) != 0)
    1463             :     {
    1464           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot rename %s to %s",
    1465             :                  osZMetadataTmpFilename.c_str(), osZMetadataFilename.c_str());
    1466           0 :         return false;
    1467             :     }
    1468             : 
    1469           5 :     if (pfnProgress)
    1470           1 :         pfnProgress(1.0, "", pProgressData);
    1471             : 
    1472           5 :     return true;
    1473             : }
    1474             : 
    1475             : /************************************************************************/
    1476             : /*                   VSIKerchunkConvertJSONToParquet()                  */
    1477             : /************************************************************************/
    1478             : 
    1479           1 : bool VSIKerchunkConvertJSONToParquet(const char *pszSrcJSONFilename,
    1480             :                                      const char *pszDstDirname,
    1481             :                                      GDALProgressFunc pfnProgress,
    1482             :                                      void *pProgressData)
    1483             : {
    1484           1 :     if (GDALGetDriverByName("PARQUET") == nullptr)
    1485             :     {
    1486           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1487             :                  "Conversion to a Parquet reference store is not possible "
    1488             :                  "because the PARQUET driver is not available.");
    1489           0 :         return false;
    1490             :     }
    1491             : 
    1492           1 :     auto poFS = cpl::down_cast<VSIKerchunkJSONRefFileSystem *>(
    1493             :         VSIFileManager::GetHandler(JSON_REF_FS_PREFIX));
    1494           1 :     std::shared_ptr<VSIKerchunkRefFile> refFile;
    1495           1 :     if (!poFS->m_oCache.tryGet(pszSrcJSONFilename, refFile))
    1496             :     {
    1497             :         void *pScaledProgressData =
    1498           1 :             GDALCreateScaledProgress(0.0, 0.5, pfnProgress, pProgressData);
    1499             :         try
    1500             :         {
    1501           2 :             refFile = poFS->LoadInternal(
    1502             :                 pszSrcJSONFilename,
    1503             :                 pScaledProgressData ? GDALScaledProgress : nullptr,
    1504           1 :                 pScaledProgressData);
    1505             :         }
    1506           0 :         catch (const std::exception &e)
    1507             :         {
    1508           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1509             :                      "VSIKerchunkJSONRefFileSystem::Load() failed: %s",
    1510           0 :                      e.what());
    1511           0 :             GDALDestroyScaledProgress(pScaledProgressData);
    1512           0 :             return false;
    1513             :         }
    1514           1 :         GDALDestroyScaledProgress(pScaledProgressData);
    1515             :     }
    1516           1 :     if (!refFile)
    1517             :     {
    1518           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1519             :                  "%s is not a Kerchunk JSON reference store",
    1520             :                  pszSrcJSONFilename);
    1521           0 :         return false;
    1522             :     }
    1523             : 
    1524           1 :     VSIMkdir(pszDstDirname, 0755);
    1525             : 
    1526             :     void *pScaledProgressData =
    1527           1 :         GDALCreateScaledProgress(0.5, 1.0, pfnProgress, pProgressData);
    1528           1 :     const bool bRet = refFile->ConvertToParquetRef(
    1529             :         pszDstDirname, pScaledProgressData ? GDALScaledProgress : nullptr,
    1530             :         pScaledProgressData);
    1531           1 :     GDALDestroyScaledProgress(pScaledProgressData);
    1532           1 :     return bRet;
    1533             : }
    1534             : 
    1535             : /************************************************************************/
    1536             : /*               VSIKerchunkJSONRefFileSystem::Open()                   */
    1537             : /************************************************************************/
    1538             : 
    1539             : VSIVirtualHandleUniquePtr
    1540          91 : VSIKerchunkJSONRefFileSystem::Open(const char *pszFilename,
    1541             :                                    const char *pszAccess, bool /* bSetError */,
    1542             :                                    CSLConstList /* papszOptions */)
    1543             : {
    1544          91 :     CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Open(%s)", pszFilename);
    1545          91 :     if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
    1546           0 :         return nullptr;
    1547             : 
    1548         182 :     const auto [osJSONFilename, osKey] = SplitFilename(pszFilename);
    1549          91 :     if (osJSONFilename.empty())
    1550           4 :         return nullptr;
    1551             : 
    1552          87 :     const auto [refFile, osParqFilename] = Load(
    1553         174 :         osJSONFilename, STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX));
    1554          87 :     if (!refFile)
    1555             :     {
    1556           6 :         if (osParqFilename.empty())
    1557           2 :             return nullptr;
    1558             : 
    1559             :         return VSIFilesystemHandler::OpenStatic(
    1560           8 :             CPLFormFilenameSafe(CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
    1561             :                                            osParqFilename.c_str()),
    1562             :                                 osKey.c_str(), nullptr)
    1563             :                 .c_str(),
    1564           4 :             pszAccess);
    1565             :     }
    1566             : 
    1567          81 :     const auto oIter = refFile->GetMapKeys().find(osKey);
    1568          81 :     if (oIter == refFile->GetMapKeys().end())
    1569           6 :         return nullptr;
    1570             : 
    1571          75 :     const auto &keyInfo = oIter->second;
    1572          75 :     if (!keyInfo.posURI)
    1573             :     {
    1574             :         return VSIVirtualHandleUniquePtr(VSIFileFromMemBuffer(
    1575          55 :             nullptr, const_cast<GByte *>(keyInfo.abyValue.data()),
    1576         110 :             keyInfo.abyValue.size(), /* bTakeOwnership = */ false));
    1577             :     }
    1578             :     else
    1579             :     {
    1580             :         std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
    1581          40 :             *(keyInfo.posURI), CPLGetPathSafe(osJSONFilename.c_str()));
    1582          20 :         if (osVSIPath.empty())
    1583           2 :             return nullptr;
    1584             :         const std::string osPath =
    1585          18 :             keyInfo.nSize
    1586           7 :                 ? CPLSPrintf("/vsisubfile/%" PRIu64 "_%u,%s", keyInfo.nOffset,
    1587           7 :                              keyInfo.nSize, osVSIPath.c_str())
    1588          36 :                 : std::move(osVSIPath);
    1589          18 :         CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Opening %s",
    1590             :                      osPath.c_str());
    1591             :         CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
    1592          36 :                                       "EMPTY_DIR", false);
    1593          36 :         auto fp = VSIFilesystemHandler::OpenStatic(osPath.c_str(), "rb", true);
    1594          18 :         if (!fp)
    1595             :         {
    1596           1 :             if (!VSIToCPLError(CE_Failure, CPLE_FileIO))
    1597           0 :                 CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
    1598             :                          osPath.c_str());
    1599             :         }
    1600          18 :         return fp;
    1601             :     }
    1602             : }
    1603             : 
    1604             : /************************************************************************/
    1605             : /*               VSIKerchunkJSONRefFileSystem::Stat()                   */
    1606             : /************************************************************************/
    1607             : 
    1608         258 : int VSIKerchunkJSONRefFileSystem::Stat(const char *pszFilename,
    1609             :                                        VSIStatBufL *pStatBuf, int nFlags)
    1610             : {
    1611         258 :     CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Stat(%s)", pszFilename);
    1612         258 :     memset(pStatBuf, 0, sizeof(VSIStatBufL));
    1613             : 
    1614         516 :     const auto [osJSONFilename, osKey] = SplitFilename(pszFilename);
    1615         258 :     if (osJSONFilename.empty())
    1616          12 :         return -1;
    1617             : 
    1618         246 :     const auto [refFile, osParqFilename] = Load(
    1619         492 :         osJSONFilename, STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX));
    1620         246 :     if (!refFile)
    1621             :     {
    1622          91 :         if (osParqFilename.empty())
    1623          86 :             return -1;
    1624             : 
    1625           5 :         return VSIStatExL(
    1626          10 :             CPLFormFilenameSafe(CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
    1627             :                                            osParqFilename.c_str()),
    1628             :                                 osKey.c_str(), nullptr)
    1629             :                 .c_str(),
    1630           5 :             pStatBuf, nFlags);
    1631             :     }
    1632             : 
    1633         155 :     if (osKey.empty())
    1634             :     {
    1635          27 :         pStatBuf->st_mode = S_IFDIR;
    1636          27 :         return 0;
    1637             :     }
    1638             : 
    1639         128 :     const auto oIter = refFile->GetMapKeys().find(osKey);
    1640         128 :     if (oIter == refFile->GetMapKeys().end())
    1641             :     {
    1642         210 :         if (cpl::contains(refFile->GetMapKeys(), osKey + "/.zgroup") ||
    1643         140 :             cpl::contains(refFile->GetMapKeys(), osKey + "/.zarray"))
    1644             :         {
    1645           8 :             pStatBuf->st_mode = S_IFDIR;
    1646           8 :             return 0;
    1647             :         }
    1648             : 
    1649          62 :         return -1;
    1650             :     }
    1651             : 
    1652          58 :     const auto &keyInfo = oIter->second;
    1653          58 :     if (!(keyInfo.posURI))
    1654             :     {
    1655          50 :         pStatBuf->st_size = keyInfo.abyValue.size();
    1656             :     }
    1657             :     else
    1658             :     {
    1659           8 :         if (keyInfo.nSize)
    1660             :         {
    1661           4 :             pStatBuf->st_size = keyInfo.nSize;
    1662             :         }
    1663             :         else
    1664             :         {
    1665             :             const std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
    1666           8 :                 *(keyInfo.posURI), CPLGetPathSafe(osJSONFilename.c_str()));
    1667           4 :             if (osVSIPath.empty())
    1668           0 :                 return -1;
    1669           4 :             return VSIStatExL(osVSIPath.c_str(), pStatBuf, nFlags);
    1670             :         }
    1671             :     }
    1672          54 :     pStatBuf->st_mode = S_IFREG;
    1673             : 
    1674          54 :     return 0;
    1675             : }
    1676             : 
    1677             : /************************************************************************/
    1678             : /*           VSIKerchunkJSONRefFileSystem::GetFileMetadata()            */
    1679             : /************************************************************************/
    1680             : 
    1681             : char **
    1682          11 : VSIKerchunkJSONRefFileSystem::GetFileMetadata(const char *pszFilename,
    1683             :                                               const char *pszDomain,
    1684             :                                               CSLConstList /* papszOptions */)
    1685             : {
    1686          11 :     if (!pszDomain || !EQUAL(pszDomain, "CHUNK_INFO"))
    1687           2 :         return nullptr;
    1688             : 
    1689          18 :     const auto [osJSONFilename, osKey] = SplitFilename(pszFilename);
    1690           9 :     if (osJSONFilename.empty() || osKey.empty())
    1691           4 :         return nullptr;
    1692             : 
    1693           5 :     const auto [refFile, osParqFilename] = Load(
    1694          10 :         osJSONFilename, STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX));
    1695           5 :     if (!refFile)
    1696           0 :         return nullptr;
    1697             : 
    1698           5 :     const auto oIter = refFile->GetMapKeys().find(osKey);
    1699           5 :     if (oIter == refFile->GetMapKeys().end())
    1700           2 :         return nullptr;
    1701             : 
    1702           3 :     const auto &keyInfo = oIter->second;
    1703           6 :     CPLStringList aosMetadata;
    1704           3 :     if (!(keyInfo.posURI))
    1705             :     {
    1706             :         aosMetadata.SetNameValue(
    1707             :             "SIZE", CPLSPrintf(CPL_FRMT_GUIB,
    1708           0 :                                static_cast<GUIntBig>(keyInfo.abyValue.size())));
    1709           0 :         if (keyInfo.abyValue.size() <
    1710           0 :             static_cast<size_t>(std::numeric_limits<int>::max() - 1))
    1711             :         {
    1712             :             char *pszBase64 =
    1713           0 :                 CPLBase64Encode(static_cast<int>(keyInfo.abyValue.size()),
    1714             :                                 keyInfo.abyValue.data());
    1715           0 :             aosMetadata.SetNameValue("BASE64", pszBase64);
    1716           0 :             CPLFree(pszBase64);
    1717             :         }
    1718             :     }
    1719             :     else
    1720             :     {
    1721             :         const std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
    1722           3 :             *(keyInfo.posURI), CPLGetPathSafe(osJSONFilename.c_str()));
    1723           3 :         if (osVSIPath.empty())
    1724           0 :             return nullptr;
    1725           3 :         if (keyInfo.nSize)
    1726             :         {
    1727           1 :             aosMetadata.SetNameValue("SIZE", CPLSPrintf("%u", keyInfo.nSize));
    1728             :         }
    1729             :         else
    1730             :         {
    1731             :             VSIStatBufL sStatBuf;
    1732           2 :             if (VSIStatL(osVSIPath.c_str(), &sStatBuf) != 0)
    1733           0 :                 return nullptr;
    1734             :             aosMetadata.SetNameValue(
    1735             :                 "SIZE", CPLSPrintf(CPL_FRMT_GUIB,
    1736           2 :                                    static_cast<GUIntBig>(sStatBuf.st_size)));
    1737             :         }
    1738             :         aosMetadata.SetNameValue(
    1739             :             "OFFSET",
    1740           3 :             CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(keyInfo.nOffset)));
    1741           3 :         aosMetadata.SetNameValue("FILENAME", osVSIPath.c_str());
    1742             :     }
    1743             : 
    1744           3 :     return aosMetadata.StealList();
    1745             : }
    1746             : 
    1747             : /************************************************************************/
    1748             : /*             VSIKerchunkJSONRefFileSystem::ReadDirEx()                */
    1749             : /************************************************************************/
    1750             : 
    1751          17 : char **VSIKerchunkJSONRefFileSystem::ReadDirEx(const char *pszDirname,
    1752             :                                                int nMaxFiles)
    1753             : {
    1754          17 :     CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "ReadDir(%s)", pszDirname);
    1755             : 
    1756          34 :     const auto [osJSONFilename, osAskedKey] = SplitFilename(pszDirname);
    1757          17 :     if (osJSONFilename.empty())
    1758           4 :         return nullptr;
    1759             : 
    1760          13 :     const auto [refFile, osParqFilename] = Load(
    1761          26 :         osJSONFilename, STARTS_WITH(pszDirname, JSON_REF_CACHED_FS_PREFIX));
    1762          13 :     if (!refFile)
    1763             :     {
    1764           3 :         if (osParqFilename.empty())
    1765           3 :             return nullptr;
    1766             : 
    1767           0 :         return VSIReadDirEx(
    1768           0 :             CPLFormFilenameSafe(CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
    1769             :                                            osParqFilename.c_str()),
    1770             :                                 osAskedKey.c_str(), nullptr)
    1771             :                 .c_str(),
    1772           0 :             nMaxFiles);
    1773             :     }
    1774             : 
    1775          20 :     std::set<std::string> set;
    1776          60 :     for (const auto &[key, value] : refFile->GetMapKeys())
    1777             :     {
    1778          50 :         if (osAskedKey.empty())
    1779             :         {
    1780          20 :             const auto nPos = key.find('/');
    1781          20 :             if (nPos == std::string::npos)
    1782           8 :                 set.insert(key);
    1783             :             else
    1784          12 :                 set.insert(key.substr(0, nPos));
    1785             :         }
    1786          30 :         else if (key.size() > osAskedKey.size() &&
    1787          42 :                  cpl::starts_with(key, osAskedKey) &&
    1788          12 :                  key[osAskedKey.size()] == '/')
    1789             :         {
    1790          24 :             std::string subKey = key.substr(osAskedKey.size() + 1);
    1791          12 :             const auto nPos = subKey.find('/');
    1792          12 :             if (nPos == std::string::npos)
    1793          12 :                 set.insert(std::move(subKey));
    1794             :             else
    1795           0 :                 set.insert(subKey.substr(0, nPos));
    1796             :         }
    1797             :     }
    1798             : 
    1799          20 :     CPLStringList aosRet;
    1800          34 :     for (const std::string &v : set)
    1801             :     {
    1802             :         // CPLDebugOnly("VSIKerchunkJSONRefFileSystem", ".. %s", v.c_str());
    1803          24 :         aosRet.AddString(v.c_str());
    1804             :     }
    1805          10 :     return aosRet.StealList();
    1806             : }
    1807             : 
    1808             : /************************************************************************/
    1809             : /*                VSIInstallKerchunkJSONRefFileSystem()                 */
    1810             : /************************************************************************/
    1811             : 
    1812        1755 : void VSIInstallKerchunkJSONRefFileSystem()
    1813             : {
    1814             :     static std::mutex oMutex;
    1815        3510 :     std::lock_guard<std::mutex> oLock(oMutex);
    1816             :     // cppcheck-suppress knownConditionTrueFalse
    1817        1755 :     if (!VSIKerchunkJSONRefFileSystem::IsFileSystemInstantiated())
    1818             :     {
    1819        1755 :         auto fs = std::make_unique<VSIKerchunkJSONRefFileSystem>().release();
    1820        1755 :         VSIFileManager::InstallHandler(JSON_REF_FS_PREFIX, fs);
    1821        1755 :         VSIFileManager::InstallHandler(JSON_REF_CACHED_FS_PREFIX, fs);
    1822             :     }
    1823        1755 : }

Generated by: LCOV version 1.14