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

Generated by: LCOV version 1.14