LCOV - code coverage report
Current view: top level - frmts/zarr - vsikerchunk_parquet_ref.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 347 371 93.5 %
Date: 2026-02-11 08:43:47 Functions: 15 16 93.8 %

          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#parquet-references
       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             : #include "vsikerchunk.h"
      15             : 
      16             : #include "cpl_json.h"
      17             : #include "cpl_mem_cache.h"
      18             : #include "cpl_vsi_error.h"
      19             : #include "cpl_vsi_virtual.h"
      20             : 
      21             : #include "gdal_priv.h"
      22             : #include "ogrsf_frmts.h"
      23             : 
      24             : #include <algorithm>
      25             : #include <cinttypes>
      26             : #include <functional>
      27             : #include <limits>
      28             : #include <mutex>
      29             : #include <set>
      30             : #include <utility>
      31             : 
      32             : extern "C" int CPL_DLL GDALIsInGlobalDestructor();
      33             : 
      34             : /************************************************************************/
      35             : /*                           VSIZarrArrayInfo                           */
      36             : /************************************************************************/
      37             : 
      38             : struct VSIZarrArrayInfo
      39             : {
      40             :     std::vector<uint64_t> anChunkCount{};
      41             : };
      42             : 
      43             : /************************************************************************/
      44             : /*                      VSIKerchunkParquetRefFile                       */
      45             : /************************************************************************/
      46             : 
      47             : struct VSIKerchunkParquetRefFile
      48             : {
      49             :     int m_nRecordSize = 0;
      50             :     std::map<std::string, std::vector<GByte>> m_oMapKeys{};
      51             :     std::map<std::string, VSIZarrArrayInfo> m_oMapArrayInfo{};
      52             : };
      53             : 
      54             : /************************************************************************/
      55             : /*                   VSIKerchunkParquetRefFileSystem                    */
      56             : /************************************************************************/
      57             : 
      58             : class VSIKerchunkParquetRefFileSystem final : public VSIFilesystemHandler
      59             : {
      60             :   public:
      61        1776 :     VSIKerchunkParquetRefFileSystem()
      62        1776 :     {
      63        1776 :         bool *pbInstantiated = &IsFileSystemInstantiated();
      64        1776 :         *pbInstantiated = true;
      65        1776 :     }
      66             : 
      67             :     ~VSIKerchunkParquetRefFileSystem() override;
      68             : 
      69        4678 :     static bool &IsFileSystemInstantiated()
      70             :     {
      71             :         static bool bIsFileSystemInstantiated = false;
      72        4678 :         return bIsFileSystemInstantiated;
      73             :     }
      74             : 
      75             :     VSIVirtualHandleUniquePtr Open(const char *pszFilename,
      76             :                                    const char *pszAccess, bool bSetError,
      77             :                                    CSLConstList papszOptions) override;
      78             : 
      79             :     int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
      80             :              int nFlags) override;
      81             : 
      82             :     char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
      83             : 
      84             :     char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
      85             :                            CSLConstList papszOptions) override;
      86             : 
      87             :     void CleanCache();
      88             : 
      89             :   private:
      90             :     lru11::Cache<std::string, std::shared_ptr<VSIKerchunkParquetRefFile>,
      91             :                  std::mutex>
      92             :         m_oCache{};
      93             : 
      94             :     std::mutex m_oParquetCacheMutex{};
      95             :     lru11::Cache<std::string, std::shared_ptr<GDALDataset>> *m_poParquetCache{};
      96             : 
      97             :     static std::pair<std::string, std::string>
      98             :     SplitFilename(const char *pszFilename);
      99             : 
     100             :     std::shared_ptr<VSIKerchunkParquetRefFile>
     101             :     Load(const std::string &osRootFilename);
     102             : 
     103             :     struct ChunkInfo
     104             :     {
     105             :         std::string osParquetFileDirectory{};
     106             :         std::unique_ptr<OGRFeature> poFeature{};
     107             :         int iPathField = -1;
     108             :         int iOffsetField = -1;
     109             :         int iSizeField = -1;
     110             :         int iRawField = -1;
     111             :     };
     112             : 
     113             :     ChunkInfo
     114             :     GetChunkInfo(const std::string &osRootFilename,
     115             :                  const std::shared_ptr<VSIKerchunkParquetRefFile> &refFile,
     116             :                  const std::string &osKey);
     117             : 
     118             :     CPL_DISALLOW_COPY_ASSIGN(VSIKerchunkParquetRefFileSystem)
     119             : };
     120             : 
     121             : /************************************************************************/
     122             : /*                  ~VSIKerchunkParquetRefFileSystem()                  */
     123             : /************************************************************************/
     124             : 
     125        1126 : VSIKerchunkParquetRefFileSystem::~VSIKerchunkParquetRefFileSystem()
     126             : {
     127        1126 :     CleanCache();
     128        1126 :     bool *pbInstantiated = &IsFileSystemInstantiated();
     129        1126 :     *pbInstantiated = false;
     130        1126 : }
     131             : 
     132             : /************************************************************************/
     133             : /*            VSIKerchunkParquetRefFileSystem::CleanCache()             */
     134             : /************************************************************************/
     135             : 
     136        2924 : void VSIKerchunkParquetRefFileSystem::CleanCache()
     137             : {
     138             :     // If we are in the unloading of the library do not try to close
     139             :     // datasets to avoid crashes and prefer leaking memory...
     140        2924 :     if (!GDALIsInGlobalDestructor())
     141             :     {
     142        5178 :         std::lock_guard<std::mutex> oLock(m_oParquetCacheMutex);
     143        2589 :         if (m_poParquetCache)
     144             :         {
     145          10 :             m_poParquetCache->clear();
     146          10 :             delete m_poParquetCache;
     147          10 :             m_poParquetCache = nullptr;
     148             :         }
     149             :     }
     150        2924 : }
     151             : 
     152             : /************************************************************************/
     153             : /*           VSIKerchunkParquetRefFileSystem::SplitFilename()           */
     154             : /************************************************************************/
     155             : 
     156             : /*static*/
     157             : std::pair<std::string, std::string>
     158         258 : VSIKerchunkParquetRefFileSystem::SplitFilename(const char *pszFilename)
     159             : {
     160         258 :     if (!STARTS_WITH(pszFilename, PARQUET_REF_FS_PREFIX))
     161           0 :         return {std::string(), std::string()};
     162             : 
     163         516 :     std::string osRootFilename;
     164             : 
     165         258 :     pszFilename += strlen(PARQUET_REF_FS_PREFIX);
     166             : 
     167         258 :     if (*pszFilename == '{')
     168             :     {
     169             :         // Parse /vsikerchunk_parquet_ref/{/path/to/some/parquet_root}[key]
     170         241 :         int nLevel = 1;
     171         241 :         ++pszFilename;
     172       15239 :         for (; *pszFilename; ++pszFilename)
     173             :         {
     174       15226 :             if (*pszFilename == '{')
     175             :             {
     176           0 :                 ++nLevel;
     177             :             }
     178       15226 :             else if (*pszFilename == '}')
     179             :             {
     180         228 :                 --nLevel;
     181         228 :                 if (nLevel == 0)
     182             :                 {
     183         228 :                     ++pszFilename;
     184         228 :                     break;
     185             :                 }
     186             :             }
     187       14998 :             osRootFilename += *pszFilename;
     188             :         }
     189         241 :         if (nLevel != 0)
     190             :         {
     191          13 :             CPLError(CE_Failure, CPLE_AppDefined,
     192             :                      "Invalid %s syntax: should be "
     193             :                      "%s{/path/to/some/file}[/optional_key]",
     194             :                      PARQUET_REF_FS_PREFIX, PARQUET_REF_FS_PREFIX);
     195          13 :             return {std::string(), std::string()};
     196             :         }
     197             : 
     198             :         return {osRootFilename,
     199         456 :                 *pszFilename == '/' ? pszFilename + 1 : pszFilename};
     200             :     }
     201             :     else
     202             :     {
     203          17 :         CPLError(CE_Failure, CPLE_AppDefined,
     204             :                  "Invalid %s syntax: should be "
     205             :                  "%s{/path/to/root/dir}[/optional_key]",
     206             :                  PARQUET_REF_FS_PREFIX, PARQUET_REF_FS_PREFIX);
     207          17 :         return {std::string(), std::string()};
     208             :     }
     209             : }
     210             : 
     211             : /************************************************************************/
     212             : /*               VSIKerchunkParquetRefFileSystem::Load()                */
     213             : /************************************************************************/
     214             : 
     215             : std::shared_ptr<VSIKerchunkParquetRefFile>
     216         228 : VSIKerchunkParquetRefFileSystem::Load(const std::string &osRootFilename)
     217             : {
     218         228 :     std::shared_ptr<VSIKerchunkParquetRefFile> refFile;
     219         228 :     if (m_oCache.tryGet(osRootFilename, refFile))
     220         194 :         return refFile;
     221             : 
     222          68 :     CPLJSONDocument oDoc;
     223             : 
     224             :     const std::string osZMetataFilename =
     225          68 :         CPLFormFilenameSafe(osRootFilename.c_str(), ".zmetadata", nullptr);
     226          34 :     if (!oDoc.Load(osZMetataFilename))
     227             :     {
     228          11 :         CPLError(CE_Failure, CPLE_AppDefined,
     229             :                  "VSIKerchunkParquetRefFileSystem: cannot open %s",
     230             :                  osZMetataFilename.c_str());
     231          11 :         return nullptr;
     232             :     }
     233             : 
     234          46 :     const auto oRoot = oDoc.GetRoot();
     235          69 :     const auto oRecordSize = oRoot.GetObj("record_size");
     236          45 :     if (!oRecordSize.IsValid() ||
     237          22 :         oRecordSize.GetType() != CPLJSONObject::Type::Integer)
     238             :     {
     239           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     240             :                  "VSIKerchunkParquetRefFileSystem: key 'record_size' missing "
     241             :                  "or not of type integer");
     242           2 :         return nullptr;
     243             :     }
     244             : 
     245          63 :     const auto oMetadata = oRoot.GetObj("metadata");
     246          41 :     if (!oMetadata.IsValid() ||
     247          20 :         oMetadata.GetType() != CPLJSONObject::Type::Object)
     248             :     {
     249           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     250             :                  "VSIKerchunkParquetRefFileSystem: key 'metadata' missing "
     251             :                  "or not of type dict");
     252           2 :         return nullptr;
     253             :     }
     254             : 
     255          19 :     refFile = std::make_shared<VSIKerchunkParquetRefFile>();
     256          19 :     refFile->m_nRecordSize = oRecordSize.ToInteger();
     257          19 :     if (refFile->m_nRecordSize < 1)
     258             :     {
     259           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     260             :                  "VSIKerchunkParquetRefFileSystem: Invalid 'record_size'");
     261           1 :         return nullptr;
     262             :     }
     263             : 
     264          66 :     for (const auto &oEntry : oMetadata.GetChildren())
     265             :     {
     266          56 :         const std::string osKeyName = oEntry.GetName();
     267          56 :         if (oEntry.GetType() == CPLJSONObject::Type::Object)
     268             :         {
     269             :             const std::string osSerialized =
     270          55 :                 oEntry.Format(CPLJSONObject::PrettyFormat::Plain);
     271          55 :             std::vector<GByte> abyValue;
     272             :             abyValue.insert(
     273          55 :                 abyValue.end(),
     274          55 :                 reinterpret_cast<const GByte *>(osSerialized.data()),
     275          55 :                 reinterpret_cast<const GByte *>(osSerialized.data()) +
     276         110 :                     osSerialized.size());
     277             : 
     278          55 :             refFile->m_oMapKeys[osKeyName] = std::move(abyValue);
     279             : 
     280          55 :             if (cpl::ends_with(osKeyName, "/.zarray"))
     281             :             {
     282          34 :                 const auto oShape = oEntry.GetArray("shape");
     283          34 :                 const auto oChunks = oEntry.GetArray("chunks");
     284          17 :                 if (!oShape.IsValid())
     285             :                 {
     286           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     287             :                              "VSIKerchunkParquetRefFileSystem: "
     288             :                              "missing 'shape' entry for key '%s'",
     289             :                              osKeyName.c_str());
     290           1 :                     return nullptr;
     291             :                 }
     292          16 :                 else if (!oChunks.IsValid())
     293             :                 {
     294           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     295             :                              "VSIKerchunkParquetRefFileSystem: "
     296             :                              "missing 'chunks' entry for key '%s'",
     297             :                              osKeyName.c_str());
     298           1 :                     return nullptr;
     299             :                 }
     300          15 :                 else if (oShape.Size() != oChunks.Size())
     301             :                 {
     302           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     303             :                              "VSIKerchunkParquetRefFileSystem: "
     304             :                              "'shape' and 'chunks' entries have not the same "
     305             :                              "number of values for key '%s'",
     306             :                              osKeyName.c_str());
     307           1 :                     return nullptr;
     308             :                 }
     309          14 :                 else if (oShape.Size() > 32)
     310             :                 {
     311           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     312             :                              "VSIKerchunkParquetRefFileSystem: "
     313             :                              "'shape' has too many dimensions for key '%s'",
     314             :                              osKeyName.c_str());
     315           1 :                     return nullptr;
     316             :                 }
     317             :                 else
     318             :                 {
     319          13 :                     VSIZarrArrayInfo arrayInfo;
     320          13 :                     uint64_t nTotalChunks = 1;
     321          22 :                     for (int i = 0; i < oShape.Size(); ++i)
     322             :                     {
     323          12 :                         const uint64_t nSize = oShape[i].ToLong();
     324          12 :                         const uint64_t nChunkSize = oChunks[i].ToLong();
     325          12 :                         if (nSize == 0)
     326             :                         {
     327           1 :                             CPLError(CE_Failure, CPLE_AppDefined,
     328             :                                      "VSIKerchunkParquetRefFileSystem: "
     329             :                                      "shape[%d]=0 in "
     330             :                                      "array definition for key '%s'",
     331             :                                      i, osKeyName.c_str());
     332           3 :                             return nullptr;
     333             :                         }
     334          11 :                         else if (nChunkSize == 0)
     335             :                         {
     336           1 :                             CPLError(CE_Failure, CPLE_AppDefined,
     337             :                                      "VSIKerchunkParquetRefFileSystem: "
     338             :                                      "chunks[%d]=0 in "
     339             :                                      "array definition for key '%s'",
     340             :                                      i, osKeyName.c_str());
     341           1 :                             return nullptr;
     342             :                         }
     343          10 :                         const auto nChunkCount =
     344          10 :                             DIV_ROUND_UP(nSize, nChunkSize);
     345          10 :                         if (nChunkCount >
     346          10 :                             std::numeric_limits<uint64_t>::max() / nTotalChunks)
     347             :                         {
     348           1 :                             CPLError(
     349             :                                 CE_Failure, CPLE_AppDefined,
     350             :                                 "VSIKerchunkParquetRefFileSystem: "
     351             :                                 "product(shape[]) > UINT64_MAX for key '%s'",
     352             :                                 osKeyName.c_str());
     353           1 :                             return nullptr;
     354             :                         }
     355           9 :                         nTotalChunks *= nChunkCount;
     356           9 :                         arrayInfo.anChunkCount.push_back(nChunkCount);
     357             :                     }
     358             :                     const std::string osArrayDir = osKeyName.substr(
     359          20 :                         0, osKeyName.size() - strlen("/.zarray"));
     360          10 :                     refFile->m_oMapArrayInfo[osArrayDir] = std::move(arrayInfo);
     361             :                 }
     362             :             }
     363             :         }
     364             :         else
     365             :         {
     366           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     367             :                      "VSIKerchunkParquetRefFileSystem: invalid value type for "
     368             :                      "key '%s'",
     369             :                      osKeyName.c_str());
     370           1 :             return nullptr;
     371             :         }
     372             :     }
     373             : 
     374          10 :     m_oCache.insert(osRootFilename, refFile);
     375          10 :     return refFile;
     376             : }
     377             : 
     378             : /************************************************************************/
     379             : /*           VSIKerchunkParquetRefFileSystem::GetChunkInfo()            */
     380             : /************************************************************************/
     381             : 
     382             : VSIKerchunkParquetRefFileSystem::ChunkInfo
     383         107 : VSIKerchunkParquetRefFileSystem::GetChunkInfo(
     384             :     const std::string &osRootFilename,
     385             :     const std::shared_ptr<VSIKerchunkParquetRefFile> &refFile,
     386             :     const std::string &osKey)
     387             : {
     388         107 :     ChunkInfo info;
     389             : 
     390         214 :     const std::string osArrayPath = CPLGetPathSafe(osKey.c_str());
     391         107 :     const auto oIterArray = refFile->m_oMapArrayInfo.find(osArrayPath);
     392         214 :     const std::string osIndices = CPLGetFilename(osKey.c_str());
     393         161 :     if (oIterArray != refFile->m_oMapArrayInfo.end() && !osIndices.empty() &&
     394         161 :         osIndices[0] >= '0' && osIndices[0] <= '9')
     395             :     {
     396          38 :         const auto &oArrayInfo = oIterArray->second;
     397             :         const CPLStringList aosIndices(
     398          38 :             CSLTokenizeString2(osIndices.c_str(), ".", 0));
     399          38 :         if ((static_cast<size_t>(aosIndices.size()) ==
     400          53 :              oArrayInfo.anChunkCount.size()) ||
     401          30 :             (aosIndices.size() == 1 && strcmp(aosIndices[0], "0") == 0 &&
     402          15 :              oArrayInfo.anChunkCount.empty()))
     403             :         {
     404          38 :             std::vector<uint64_t> anIndices;
     405          72 :             for (size_t i = 0; i < oArrayInfo.anChunkCount.size(); ++i)
     406             :             {
     407          38 :                 char *endptr = nullptr;
     408          38 :                 anIndices.push_back(std::strtoull(aosIndices[i], &endptr, 10));
     409          38 :                 if (aosIndices[i][0] == '-' ||
     410          74 :                     endptr != aosIndices[i] + strlen(aosIndices[i]) ||
     411          36 :                     anIndices[i] >= oArrayInfo.anChunkCount[i])
     412             :                 {
     413           4 :                     return info;
     414             :                 }
     415             :             }
     416             : 
     417          34 :             uint64_t nLinearIndex = 0;
     418          34 :             uint64_t nMulFactor = 1;
     419          65 :             for (size_t i = anIndices.size(); i > 0;)
     420             :             {
     421          31 :                 --i;
     422          31 :                 nLinearIndex += anIndices[i] * nMulFactor;
     423          31 :                 nMulFactor *= oArrayInfo.anChunkCount[i];
     424             :             }
     425             : 
     426          34 :             CPLDebugOnly("VSIKerchunkParquetRefFileSystem",
     427             :                          "Linear chunk index %" PRIu64, nLinearIndex);
     428             : 
     429          34 :             const uint64_t nParquetIdx = nLinearIndex / refFile->m_nRecordSize;
     430             :             const int nIdxInParquet =
     431          34 :                 static_cast<int>(nLinearIndex % refFile->m_nRecordSize);
     432             : 
     433             :             const std::string osParquetFilename = CPLFormFilenameSafe(
     434          34 :                 CPLFormFilenameSafe(osRootFilename.c_str(), osArrayPath.c_str(),
     435             :                                     nullptr)
     436             :                     .c_str(),
     437         102 :                 CPLSPrintf("refs.%" PRIu64 ".parq", nParquetIdx), nullptr);
     438          34 :             CPLDebugOnly("VSIKerchunkParquetRefFileSystem",
     439             :                          "Looking for entry %d in Parquet file %s",
     440             :                          nIdxInParquet, osParquetFilename.c_str());
     441             : 
     442          68 :             std::lock_guard<std::mutex> oLock(m_oParquetCacheMutex);
     443          34 :             std::shared_ptr<GDALDataset> poDS;
     444          34 :             if (!m_poParquetCache)
     445             :             {
     446          11 :                 m_poParquetCache = std::make_unique<lru11::Cache<
     447          22 :                     std::string, std::shared_ptr<GDALDataset>>>()
     448          11 :                                        .release();
     449             :             }
     450          34 :             if (!m_poParquetCache->tryGet(osParquetFilename, poDS))
     451             :             {
     452          20 :                 const char *const apszAllowedDrivers[] = {"PARQUET", "ADBC",
     453             :                                                           nullptr};
     454             :                 CPLConfigOptionSetter oSetter(
     455          40 :                     "OGR_ADBC_AUTO_LOAD_DUCKDB_SPATIAL", "NO", false);
     456          20 :                 poDS.reset(
     457             :                     GDALDataset::Open(osParquetFilename.c_str(),
     458             :                                       GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
     459             :                                       apszAllowedDrivers, nullptr, nullptr));
     460          20 :                 if (poDS)
     461          20 :                     m_poParquetCache->insert(osParquetFilename, poDS);
     462             :             }
     463             : 
     464          34 :             if (poDS && poDS->GetLayerCount() == 1)
     465             :             {
     466          66 :                 const auto IsIntOrInt64 = [](OGRFieldType eType)
     467          66 :                 { return eType == OFTInteger || eType == OFTInteger64; };
     468          34 :                 auto poLayer = poDS->GetLayer(0);
     469          34 :                 const auto poDefn = poLayer->GetLayerDefn();
     470          34 :                 info.iPathField = poDefn->GetFieldIndex("path");
     471          34 :                 info.iOffsetField = poDefn->GetFieldIndex("offset");
     472          34 :                 info.iSizeField = poDefn->GetFieldIndex("size");
     473          34 :                 info.iRawField = poDefn->GetFieldIndex("raw");
     474          34 :                 if (info.iPathField >= 0 && info.iOffsetField >= 0 &&
     475          67 :                     info.iSizeField >= 0 && info.iRawField >= 0 &&
     476          33 :                     poDefn->GetFieldDefn(info.iPathField)->GetType() ==
     477          33 :                         OFTString &&
     478          33 :                     IsIntOrInt64(
     479          66 :                         poDefn->GetFieldDefn(info.iOffsetField)->GetType()) &&
     480          33 :                     IsIntOrInt64(
     481         101 :                         poDefn->GetFieldDefn(info.iSizeField)->GetType()) &&
     482          33 :                     poDefn->GetFieldDefn(info.iRawField)->GetType() ==
     483             :                         OFTBinary)
     484             :                 {
     485             :                     info.osParquetFileDirectory =
     486          33 :                         CPLGetPathSafe(osParquetFilename.c_str());
     487          33 :                     info.poFeature.reset(poLayer->GetFeature(nIdxInParquet));
     488             :                 }
     489             :                 else
     490             :                 {
     491           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     492             :                              "%s has an unexpected field structure",
     493             :                              osParquetFilename.c_str());
     494             :                 }
     495             :             }
     496             :         }
     497             :     }
     498         103 :     return info;
     499             : }
     500             : 
     501             : /************************************************************************/
     502             : /*               VSIKerchunkParquetRefFileSystem::Open()                */
     503             : /************************************************************************/
     504             : 
     505          74 : VSIVirtualHandleUniquePtr VSIKerchunkParquetRefFileSystem::Open(
     506             :     const char *pszFilename, const char *pszAccess, bool /* bSetError */,
     507             :     CSLConstList /* papszOptions */)
     508             : {
     509          74 :     CPLDebugOnly("VSIKerchunkParquetRefFileSystem", "Open(%s)", pszFilename);
     510          74 :     if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
     511           0 :         return nullptr;
     512             : 
     513         148 :     const auto [osRootFilename, osKey] = SplitFilename(pszFilename);
     514          74 :     if (osRootFilename.empty())
     515           6 :         return nullptr;
     516             : 
     517         136 :     const auto refFile = Load(osRootFilename);
     518          68 :     if (!refFile)
     519           3 :         return nullptr;
     520             : 
     521          65 :     const auto oIter = refFile->m_oMapKeys.find(osKey);
     522          65 :     if (oIter == refFile->m_oMapKeys.end())
     523             :     {
     524          58 :         const auto info = GetChunkInfo(osRootFilename, refFile, osKey);
     525          29 :         if (info.poFeature)
     526             :         {
     527          18 :             if (info.poFeature->IsFieldSetAndNotNull(info.iRawField))
     528             :             {
     529           2 :                 auto psField = info.poFeature->GetRawFieldRef(info.iRawField);
     530             :                 // Borrow binary data to feature
     531           2 :                 GByte *abyData = psField->Binary.paData;
     532           2 :                 int nSize = psField->Binary.nCount;
     533           2 :                 psField->Binary.paData = nullptr;
     534           2 :                 psField->Binary.nCount = 0;
     535             :                 // and transmit its ownership to the VSIMem file
     536             :                 return VSIVirtualHandleUniquePtr(
     537             :                     VSIFileFromMemBuffer(nullptr, abyData, nSize,
     538           2 :                                          /* bTakeOwnership = */ true));
     539             :             }
     540             :             else
     541             :             {
     542             :                 const uint64_t nOffset =
     543          16 :                     info.poFeature->GetFieldAsInteger64(info.iOffsetField);
     544             :                 const int nSize =
     545          16 :                     info.poFeature->GetFieldAsInteger(info.iSizeField);
     546             : 
     547             :                 std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
     548          16 :                     info.poFeature->GetFieldAsString(info.iPathField),
     549          48 :                     info.osParquetFileDirectory);
     550          16 :                 if (osVSIPath.empty())
     551           0 :                     return nullptr;
     552             : 
     553             :                 const std::string osPath =
     554             :                     nSize ? CPLSPrintf("/vsisubfile/%" PRIu64 "_%u,%s", nOffset,
     555             :                                        nSize, osVSIPath.c_str())
     556          32 :                           : std::move(osVSIPath);
     557          16 :                 CPLDebugOnly("VSIKerchunkParquetRefFileSystem", "Opening %s",
     558             :                              osPath.c_str());
     559             :                 CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
     560          32 :                                               "EMPTY_DIR", false);
     561             :                 auto fp = VSIFilesystemHandler::OpenStatic(osPath.c_str(), "rb",
     562          32 :                                                            true);
     563          16 :                 if (!fp)
     564             :                 {
     565           1 :                     if (!VSIToCPLError(CE_Failure, CPLE_FileIO))
     566           0 :                         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
     567             :                                  osPath.c_str());
     568             :                 }
     569          16 :                 return fp;
     570             :             }
     571             :         }
     572             : 
     573          11 :         return nullptr;
     574             :     }
     575             : 
     576          36 :     const auto &abyValue = oIter->second;
     577             :     return VSIVirtualHandleUniquePtr(
     578          36 :         VSIFileFromMemBuffer(nullptr, const_cast<GByte *>(abyValue.data()),
     579          72 :                              abyValue.size(), /* bTakeOwnership = */ false));
     580             : }
     581             : 
     582             : /************************************************************************/
     583             : /*               VSIKerchunkParquetRefFileSystem::Stat()                */
     584             : /************************************************************************/
     585             : 
     586         144 : int VSIKerchunkParquetRefFileSystem::Stat(const char *pszFilename,
     587             :                                           VSIStatBufL *pStatBuf, int nFlags)
     588             : {
     589         144 :     CPLDebugOnly("VSIKerchunkParquetRefFileSystem", "Stat(%s)", pszFilename);
     590         144 :     memset(pStatBuf, 0, sizeof(VSIStatBufL));
     591             : 
     592         288 :     const auto [osRootFilename, osKey] = SplitFilename(pszFilename);
     593         144 :     if (osRootFilename.empty())
     594          12 :         return -1;
     595             : 
     596         264 :     const auto refFile = Load(osRootFilename);
     597         132 :     if (!refFile)
     598          18 :         return -1;
     599             : 
     600         114 :     if (osKey.empty())
     601             :     {
     602           4 :         pStatBuf->st_mode = S_IFDIR;
     603           4 :         return 0;
     604             :     }
     605             : 
     606         110 :     const auto oIter = refFile->m_oMapKeys.find(osKey);
     607         110 :     if (oIter == refFile->m_oMapKeys.end())
     608             :     {
     609         142 :         const auto info = GetChunkInfo(osRootFilename, refFile, osKey);
     610          71 :         if (info.poFeature)
     611             :         {
     612          11 :             if (info.poFeature->IsFieldSetAndNotNull(info.iRawField))
     613             :             {
     614           0 :                 int nSize = 0;
     615           0 :                 info.poFeature->GetFieldAsBinary(info.iRawField, &nSize);
     616           0 :                 pStatBuf->st_size = nSize;
     617             :             }
     618             :             else
     619             :             {
     620          11 :                 pStatBuf->st_size =
     621          11 :                     info.poFeature->GetFieldAsInteger64(info.iSizeField);
     622          11 :                 if (pStatBuf->st_size == 0)
     623             :                 {
     624             :                     const std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
     625           5 :                         info.poFeature->GetFieldAsString(info.iPathField),
     626          15 :                         info.osParquetFileDirectory);
     627           5 :                     if (osVSIPath.empty())
     628           0 :                         return -1;
     629           5 :                     return VSIStatExL(osVSIPath.c_str(), pStatBuf, nFlags);
     630             :                 }
     631             :             }
     632           6 :             pStatBuf->st_mode = S_IFREG;
     633           6 :             return 0;
     634             :         }
     635             : 
     636         180 :         if (cpl::contains(refFile->m_oMapKeys, osKey + "/.zgroup") ||
     637         120 :             cpl::contains(refFile->m_oMapKeys, osKey + "/.zarray"))
     638             :         {
     639           7 :             pStatBuf->st_mode = S_IFDIR;
     640           7 :             return 0;
     641             :         }
     642             : 
     643          53 :         return -1;
     644             :     }
     645             : 
     646          39 :     const auto &abyValue = oIter->second;
     647          39 :     pStatBuf->st_size = abyValue.size();
     648          39 :     pStatBuf->st_mode = S_IFREG;
     649             : 
     650          39 :     return 0;
     651             : }
     652             : 
     653             : /************************************************************************/
     654             : /*          VSIKerchunkParquetRefFileSystem::GetFileMetadata()          */
     655             : /************************************************************************/
     656             : 
     657          16 : char **VSIKerchunkParquetRefFileSystem::GetFileMetadata(
     658             :     const char *pszFilename, const char *pszDomain,
     659             :     CSLConstList /* papszOptions */)
     660             : {
     661          16 :     if (!pszDomain || !EQUAL(pszDomain, "CHUNK_INFO"))
     662           3 :         return nullptr;
     663             : 
     664          26 :     const auto [osRootFilename, osKey] = SplitFilename(pszFilename);
     665          13 :     if (osRootFilename.empty() || osKey.empty())
     666           6 :         return nullptr;
     667             : 
     668          14 :     const auto refFile = Load(osRootFilename);
     669           7 :     if (!refFile)
     670           0 :         return nullptr;
     671             : 
     672           7 :     const auto oIter = refFile->m_oMapKeys.find(osKey);
     673           7 :     if (oIter != refFile->m_oMapKeys.end())
     674             :     {
     675           0 :         CPLStringList aosMetadata;
     676           0 :         const auto &abyData = oIter->second;
     677             :         aosMetadata.SetNameValue(
     678             :             "SIZE",
     679           0 :             CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(abyData.size())));
     680           0 :         if (abyData.size() <
     681           0 :             static_cast<size_t>(std::numeric_limits<int>::max() - 1))
     682             :         {
     683           0 :             char *pszBase64 = CPLBase64Encode(static_cast<int>(abyData.size()),
     684             :                                               abyData.data());
     685           0 :             aosMetadata.SetNameValue("BASE64", pszBase64);
     686           0 :             CPLFree(pszBase64);
     687             :         }
     688           0 :         return aosMetadata.StealList();
     689             :     }
     690             : 
     691          14 :     const auto info = GetChunkInfo(osRootFilename, refFile, osKey);
     692           7 :     if (!info.poFeature)
     693           3 :         return nullptr;
     694             : 
     695           8 :     CPLStringList aosMetadata;
     696           4 :     if (info.poFeature->IsFieldSetAndNotNull(info.iRawField))
     697             :     {
     698           2 :         const auto psField = info.poFeature->GetRawFieldRef(info.iRawField);
     699             :         aosMetadata.SetNameValue("SIZE",
     700           2 :                                  CPLSPrintf("%d", psField->Binary.nCount));
     701             :         char *pszBase64 =
     702           2 :             CPLBase64Encode(psField->Binary.nCount, psField->Binary.paData);
     703           2 :         aosMetadata.SetNameValue("BASE64", pszBase64);
     704           2 :         CPLFree(pszBase64);
     705             :     }
     706             :     else
     707             :     {
     708             :         const uint64_t nOffset =
     709           2 :             info.poFeature->GetFieldAsInteger64(info.iOffsetField);
     710           2 :         const int nSize = info.poFeature->GetFieldAsInteger(info.iSizeField);
     711             : 
     712             :         std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
     713           2 :             info.poFeature->GetFieldAsString(info.iPathField),
     714           4 :             info.osParquetFileDirectory);
     715           2 :         if (osVSIPath.empty())
     716           0 :             return nullptr;
     717           2 :         if (nSize)
     718             :         {
     719           0 :             aosMetadata.SetNameValue("SIZE", CPLSPrintf("%d", nSize));
     720             :         }
     721             :         else
     722             :         {
     723             :             VSIStatBufL sStatBuf;
     724           2 :             if (VSIStatL(osVSIPath.c_str(), &sStatBuf) != 0)
     725           0 :                 return nullptr;
     726             :             aosMetadata.SetNameValue(
     727             :                 "SIZE", CPLSPrintf(CPL_FRMT_GUIB,
     728           2 :                                    static_cast<GUIntBig>(sStatBuf.st_size)));
     729             :         }
     730             :         aosMetadata.SetNameValue(
     731             :             "OFFSET",
     732           2 :             CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(nOffset)));
     733           2 :         aosMetadata.SetNameValue("FILENAME", osVSIPath.c_str());
     734             :     }
     735             : 
     736           4 :     return aosMetadata.StealList();
     737             : }
     738             : 
     739             : /************************************************************************/
     740             : /*             VSIKerchunkParquetRefFileSystem::ReadDirEx()             */
     741             : /************************************************************************/
     742             : 
     743          27 : char **VSIKerchunkParquetRefFileSystem::ReadDirEx(const char *pszDirname,
     744             :                                                   int nMaxFiles)
     745             : {
     746          27 :     CPLDebugOnly("VSIKerchunkParquetRefFileSystem", "ReadDir(%s)", pszDirname);
     747             : 
     748          54 :     const auto [osRootFilename, osAskedKey] = SplitFilename(pszDirname);
     749          27 :     if (osRootFilename.empty())
     750           6 :         return nullptr;
     751             : 
     752          42 :     const auto refFile = Load(osRootFilename);
     753          21 :     if (!refFile)
     754           3 :         return nullptr;
     755             : 
     756          36 :     std::set<std::string> set;
     757          90 :     for (const auto &[key, value] : refFile->m_oMapKeys)
     758             :     {
     759          72 :         if (osAskedKey.empty())
     760             :         {
     761          28 :             const auto nPos = key.find('/');
     762          28 :             if (nPos == std::string::npos)
     763          14 :                 set.insert(key);
     764             :             else
     765          14 :                 set.insert(key.substr(0, nPos));
     766             :         }
     767          44 :         else if (key.size() > osAskedKey.size() &&
     768          60 :                  cpl::starts_with(key, osAskedKey) &&
     769          16 :                  key[osAskedKey.size()] == '/')
     770             :         {
     771          32 :             std::string subKey = key.substr(osAskedKey.size() + 1);
     772          16 :             const auto nPos = subKey.find('/');
     773          16 :             if (nPos == std::string::npos)
     774          16 :                 set.insert(std::move(subKey));
     775             :             else
     776           0 :                 set.insert(subKey.substr(0, nPos));
     777             :         }
     778             :     }
     779             : 
     780          36 :     CPLStringList aosRet;
     781          55 :     for (const std::string &v : set)
     782             :     {
     783             :         // CPLDebugOnly("VSIKerchunkParquetRefFileSystem", ".. %s", v.c_str());
     784          37 :         aosRet.AddString(v.c_str());
     785             :     }
     786             : 
     787             :     // Synthesize file names for x.y.z chunks
     788          18 :     const auto oIterArray = refFile->m_oMapArrayInfo.find(osAskedKey);
     789          18 :     if (oIterArray != refFile->m_oMapArrayInfo.end())
     790             :     {
     791           8 :         const auto &oArrayInfo = oIterArray->second;
     792           8 :         if (oArrayInfo.anChunkCount.empty())
     793             :         {
     794           4 :             aosRet.AddString("0");
     795             :         }
     796             :         else
     797             :         {
     798           8 :             std::string osCurElt;
     799           8 :             std::function<bool(size_t)> Enumerate;
     800           4 :             if (nMaxFiles <= 0)
     801           3 :                 nMaxFiles = 100 * 1024 * 1024;
     802             : 
     803           8 :             Enumerate = [nMaxFiles, &aosRet, &oArrayInfo, &osCurElt,
     804          98 :                          &Enumerate](size_t iDim)
     805             :             {
     806           8 :                 const size_t sizeBefore = osCurElt.size();
     807          19 :                 for (uint64_t i = 0; i < oArrayInfo.anChunkCount[iDim]; ++i)
     808             :                 {
     809          13 :                     osCurElt += CPLSPrintf("%" PRIu64, i);
     810          13 :                     if (iDim + 1 < oArrayInfo.anChunkCount.size())
     811             :                     {
     812           4 :                         osCurElt += '.';
     813           4 :                         if (!Enumerate(iDim + 1))
     814           1 :                             return false;
     815             :                     }
     816             :                     else
     817             :                     {
     818           9 :                         if (aosRet.size() >= nMaxFiles)
     819           1 :                             return false;
     820           8 :                         aosRet.AddString(osCurElt);
     821             :                     }
     822          11 :                     osCurElt.resize(sizeBefore);
     823             :                 }
     824           6 :                 return true;
     825           4 :             };
     826             : 
     827           4 :             Enumerate(0);
     828             :         }
     829             :     }
     830             : 
     831          18 :     return aosRet.StealList();
     832             : }
     833             : 
     834             : /************************************************************************/
     835             : /*               VSIInstallKerchunkParquetRefFileSystem()               */
     836             : /************************************************************************/
     837             : 
     838        1776 : void VSIInstallKerchunkParquetRefFileSystem()
     839             : {
     840             :     static std::mutex oMutex;
     841        3552 :     std::lock_guard<std::mutex> oLock(oMutex);
     842             :     // cppcheck-suppress knownConditionTrueFalse
     843        1776 :     if (!VSIKerchunkParquetRefFileSystem::IsFileSystemInstantiated())
     844             :     {
     845        1776 :         VSIFileManager::InstallHandler(
     846             :             PARQUET_REF_FS_PREFIX,
     847        3552 :             std::make_shared<VSIKerchunkParquetRefFileSystem>());
     848             :     }
     849        1776 : }
     850             : 
     851             : /************************************************************************/
     852             : /*             VSIKerchunkParquetRefFileSystemCleanCache()              */
     853             : /************************************************************************/
     854             : 
     855        1798 : void VSIKerchunkParquetRefFileSystemCleanCache()
     856             : {
     857           0 :     auto poFS = dynamic_cast<VSIKerchunkParquetRefFileSystem *>(
     858        1798 :         VSIFileManager::GetHandler(PARQUET_REF_FS_PREFIX));
     859        1798 :     if (poFS)
     860        1798 :         poFS->CleanCache();
     861        1798 : }

Generated by: LCOV version 1.14