LCOV - code coverage report
Current view: top level - frmts/zarr - vsikerchunk_parquet_ref.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 311 321 96.9 %
Date: 2025-09-10 17:48:50 Functions: 15 15 100.0 %

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

Generated by: LCOV version 1.14