LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_sharedresource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 204 228 89.5 %
Date: 2026-02-11 08:43:47 Functions: 12 12 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "zarr.h"
      14             : #include "vsikerchunk.h"
      15             : 
      16             : #include "cpl_json.h"
      17             : 
      18             : /************************************************************************/
      19             : /*               ZarrSharedResource::ZarrSharedResource()               */
      20             : /************************************************************************/
      21             : 
      22        1798 : ZarrSharedResource::ZarrSharedResource(const std::string &osRootDirectoryName,
      23        1798 :                                        bool bUpdatable)
      24        1798 :     : m_bUpdatable(bUpdatable)
      25             : {
      26        1798 :     m_oObjConsolidatedMetadata.Deinit();
      27             : 
      28        1798 :     m_osRootDirectoryName = osRootDirectoryName;
      29        1798 :     if (!m_osRootDirectoryName.empty() && m_osRootDirectoryName.back() == '/')
      30             :     {
      31           0 :         m_osRootDirectoryName.pop_back();
      32             :     }
      33        3596 :     m_poPAM = std::make_shared<GDALPamMultiDim>(
      34        5394 :         CPLFormFilenameSafe(m_osRootDirectoryName.c_str(), "pam", nullptr));
      35        1798 : }
      36             : 
      37             : /************************************************************************/
      38             : /*                     ZarrSharedResource::Create()                     */
      39             : /************************************************************************/
      40             : 
      41             : std::shared_ptr<ZarrSharedResource>
      42        1798 : ZarrSharedResource::Create(const std::string &osRootDirectoryName,
      43             :                            bool bUpdatable)
      44             : {
      45             :     return std::shared_ptr<ZarrSharedResource>(
      46        1798 :         new ZarrSharedResource(osRootDirectoryName, bUpdatable));
      47             : }
      48             : 
      49             : /************************************************************************/
      50             : /*              ZarrSharedResource::~ZarrSharedResource()               */
      51             : /************************************************************************/
      52             : 
      53        1798 : ZarrSharedResource::~ZarrSharedResource()
      54             : {
      55             :     // We try to clean caches at dataset closing, especially for Parquet
      56             :     // references, since closing Parquet datasets when the virtual file
      57             :     // systems are destroyed can be too late and cause crashes.
      58        1798 :     VSIKerchunkFileSystemsCleanCache();
      59             : 
      60        1798 :     if (m_bConsolidatedMetadataModified)
      61             :     {
      62         302 :         if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL)
      63             :         {
      64         186 :             CPLJSONDocument oDoc;
      65         186 :             oDoc.SetRoot(m_oObjConsolidatedMetadata);
      66         186 :             oDoc.Save(CPLFormFilenameSafe(m_osRootDirectoryName.c_str(),
      67             :                                           ".zmetadata", nullptr));
      68             :         }
      69         232 :         else if (m_eConsolidatedMetadataKind ==
      70         232 :                      ConsolidatedMetadataKind::INTERNAL &&
      71         116 :                  !cpl::starts_with(m_osRootDirectoryName, "/vsizip/"))
      72             :         {
      73         230 :             CPLJSONDocument oDoc;
      74             :             const std::string osFilename = CPLFormFilenameSafe(
      75         230 :                 m_osRootDirectoryName.c_str(), "zarr.json", nullptr);
      76         115 :             if (oDoc.Load(osFilename))
      77             :             {
      78         226 :                 oDoc.GetRoot().Set("consolidated_metadata",
      79         113 :                                    m_oObjConsolidatedMetadata);
      80         113 :                 oDoc.Save(osFilename);
      81             :             }
      82             :         }
      83             :     }
      84        1798 : }
      85             : 
      86             : /************************************************************************/
      87             : /*                 ZarrSharedResource::OpenRootGroup()                  */
      88             : /************************************************************************/
      89             : 
      90        1482 : std::shared_ptr<ZarrGroupBase> ZarrSharedResource::OpenRootGroup()
      91             : {
      92             :     // Probe zarr.json first so v3 datasets skip the v2 stat cascade.
      93             :     const std::string osZarrJsonFilename(CPLFormFilenameSafe(
      94        2964 :         m_osRootDirectoryName.c_str(), "zarr.json", nullptr));
      95             :     VSIStatBufL sStat;
      96             :     const bool bHasZarrJson =
      97        1482 :         (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0);
      98             : 
      99        1482 :     if (!bHasZarrJson)
     100             :     {
     101        1114 :         auto poRG = ZarrV2Group::Create(shared_from_this(), std::string(), "/");
     102             :         // Prevents potential recursion
     103         557 :         m_poWeakRootGroup = poRG;
     104         557 :         poRG->SetUpdatable(m_bUpdatable);
     105         557 :         poRG->SetDirectoryName(m_osRootDirectoryName);
     106             : 
     107             :         const std::string osZarrayFilename(CPLFormFilenameSafe(
     108         557 :             m_osRootDirectoryName.c_str(), ".zarray", nullptr));
     109         557 :         const auto nErrorCount = CPLGetErrorCounter();
     110         557 :         if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
     111             :         {
     112         530 :             CPLJSONDocument oDoc;
     113         265 :             if (!oDoc.Load(osZarrayFilename))
     114           0 :                 return nullptr;
     115         530 :             const auto oRoot = oDoc.GetRoot();
     116         265 :             if (oRoot["_NCZARR_ARRAY"].IsValid())
     117             :             {
     118             :                 // If opening a NCZarr array, initialize its group from NCZarr
     119             :                 // metadata.
     120             :                 const std::string osGroupFilename(CPLFormFilenameSafe(
     121           3 :                     CPLGetDirnameSafe(m_osRootDirectoryName.c_str()).c_str(),
     122           3 :                     ".zgroup", nullptr));
     123           3 :                 if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
     124             :                 {
     125           2 :                     CPLJSONDocument oDocGroup;
     126           2 :                     if (oDocGroup.Load(osGroupFilename))
     127             :                     {
     128           2 :                         if (!poRG->InitFromZGroup(oDocGroup.GetRoot()))
     129           1 :                             return nullptr;
     130             :                     }
     131             :                 }
     132             :             }
     133             :             const std::string osArrayName(
     134         528 :                 CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
     135             : 
     136         792 :             if (!poRG->LoadArray(osArrayName, osZarrayFilename, oRoot, false,
     137         792 :                                  CPLJSONObject()))
     138          39 :                 return nullptr;
     139             : 
     140         225 :             return poRG;
     141             :         }
     142         293 :         else if (CPLGetErrorCounter() > nErrorCount &&
     143           1 :                  strstr(CPLGetLastErrorMsg(),
     144             :                         "Generation of Kerchunk Parquet cache"))
     145             :         {
     146           0 :             return nullptr;
     147             :         }
     148             : 
     149             :         const std::string osZmetadataFilename(CPLFormFilenameSafe(
     150         292 :             m_osRootDirectoryName.c_str(), ".zmetadata", nullptr));
     151         584 :         if (CPLTestBool(CSLFetchNameValueDef(
     152         292 :                 GetOpenOptions(), "USE_CONSOLIDATED_METADATA",
     153         292 :                 CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
     154         578 :                                      "YES"))) &&
     155         286 :             VSIStatL(osZmetadataFilename.c_str(), &sStat) == 0)
     156             :         {
     157         190 :             if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::NONE)
     158             :             {
     159         190 :                 CPLJSONDocument oDoc;
     160         190 :                 if (!oDoc.Load(osZmetadataFilename))
     161           0 :                     return nullptr;
     162             : 
     163         190 :                 m_eConsolidatedMetadataKind =
     164             :                     ConsolidatedMetadataKind::EXTERNAL;
     165         190 :                 m_oObjConsolidatedMetadata = oDoc.GetRoot();
     166             :             }
     167             : 
     168         190 :             poRG->InitFromConsolidatedMetadata(m_oObjConsolidatedMetadata);
     169             : 
     170         190 :             return poRG;
     171             :         }
     172             : 
     173             :         const std::string osGroupFilename(CPLFormFilenameSafe(
     174         102 :             m_osRootDirectoryName.c_str(), ".zgroup", nullptr));
     175         102 :         if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
     176             :         {
     177         184 :             CPLJSONDocument oDoc;
     178          92 :             if (!oDoc.Load(osGroupFilename))
     179           0 :                 return nullptr;
     180             : 
     181          92 :             if (!poRG->InitFromZGroup(oDoc.GetRoot()))
     182           3 :                 return nullptr;
     183          89 :             return poRG;
     184             :         }
     185             :     }
     186             : 
     187             :     // Zarr v3
     188        1870 :     auto poRG_V3 = ZarrV3Group::Create(shared_from_this(), std::string(), "/",
     189        3740 :                                        m_osRootDirectoryName);
     190             :     // Prevents potential recursion
     191         935 :     m_poWeakRootGroup = poRG_V3;
     192         935 :     poRG_V3->SetUpdatable(m_bUpdatable);
     193             : 
     194         935 :     if (bHasZarrJson)
     195             :     {
     196        1850 :         CPLJSONDocument oDoc;
     197         925 :         if (!oDoc.Load(osZarrJsonFilename))
     198           0 :             return nullptr;
     199        1850 :         const auto oRoot = oDoc.GetRoot();
     200         925 :         if (oRoot.GetInteger("zarr_format") != 3)
     201             :         {
     202           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     203             :                      "Unhandled zarr_format value");
     204           0 :             return nullptr;
     205             :         }
     206             : 
     207             :         // Not yet adopted, but described at https://github.com/zarr-developers/zarr-specs/pull/309/files
     208             :         // and used for example by
     209             :         // https://s3.explorer.eopf.copernicus.eu/esa-zarr-sentinel-explorer-fra/tests-output/sentinel-2-l2a/S2B_MSIL2A_20251218T110359_N0511_R094_T32VLK_20251218T115223.zarr/measurements/reflectance/zarr.json
     210             :         const auto oConsolidatedMetadata =
     211        2775 :             oRoot.GetObj("consolidated_metadata");
     212         161 :         if (oConsolidatedMetadata.GetType() == CPLJSONObject::Type::Object &&
     213        1086 :             oConsolidatedMetadata.GetString("kind") == "inline" &&
     214         322 :             CPLTestBool(CSLFetchNameValueDef(
     215         161 :                 GetOpenOptions(), "USE_CONSOLIDATED_METADATA",
     216         161 :                 CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
     217             :                                      "YES"))))
     218             :         {
     219         161 :             if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::NONE)
     220             :             {
     221         153 :                 CPLDebug("JSON", "Using consolidated_metadata");
     222         153 :                 m_eConsolidatedMetadataKind =
     223             :                     ConsolidatedMetadataKind::INTERNAL;
     224         153 :                 m_oObjConsolidatedMetadata = oConsolidatedMetadata;
     225         153 :                 m_oRootAttributes = oRoot.GetObj("attributes");
     226             :             }
     227             : 
     228         161 :             poRG_V3->InitFromConsolidatedMetadata(m_oObjConsolidatedMetadata,
     229         161 :                                                   m_oRootAttributes);
     230             :         }
     231             : 
     232        2775 :         const std::string osNodeType = oRoot.GetString("node_type");
     233         925 :         if (osNodeType == "array")
     234             :         {
     235             :             const std::string osArrayName(
     236        1416 :                 CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
     237         708 :             poRG_V3->SetExplored();
     238         708 :             if (!poRG_V3->LoadArray(osArrayName, osZarrJsonFilename, oRoot))
     239          50 :                 return nullptr;
     240             : 
     241         658 :             return poRG_V3;
     242             :         }
     243         217 :         else if (osNodeType == "group")
     244             :         {
     245         217 :             return poRG_V3;
     246             :         }
     247             :         else
     248             :         {
     249           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unhandled node_type value");
     250           0 :             return nullptr;
     251             :         }
     252             :     }
     253             : 
     254             :     // No explicit zarr.json in root directory ? Then recurse until we find
     255             :     // one.
     256          10 :     auto psDir = VSIOpenDir(m_osRootDirectoryName.c_str(), -1, nullptr);
     257          10 :     if (!psDir)
     258           9 :         return nullptr;
     259           1 :     bool bZarrJsonFound = false;
     260           1 :     while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
     261             :     {
     262           0 :         if (!VSI_ISDIR(psEntry->nMode) &&
     263           0 :             strcmp(CPLGetFilename(psEntry->pszName), "zarr.json") == 0)
     264             :         {
     265           0 :             bZarrJsonFound = true;
     266           0 :             break;
     267             :         }
     268           0 :     }
     269           1 :     VSICloseDir(psDir);
     270           1 :     if (bZarrJsonFound)
     271           0 :         return poRG_V3;
     272             : 
     273           1 :     return nullptr;
     274             : }
     275             : 
     276             : /************************************************************************/
     277             : /*                  ZarrSharedResource::GetRootGroup()                  */
     278             : /************************************************************************/
     279             : 
     280        3255 : std::shared_ptr<ZarrGroupBase> ZarrSharedResource::GetRootGroup()
     281             : {
     282        3255 :     auto poRootGroup = m_poWeakRootGroup.lock();
     283        3255 :     if (poRootGroup)
     284        1773 :         return poRootGroup;
     285        1482 :     poRootGroup = OpenRootGroup();
     286        1482 :     m_poWeakRootGroup = poRootGroup;
     287        1482 :     return poRootGroup;
     288             : }
     289             : 
     290             : /************************************************************************/
     291             : /*        ZarrSharedResource::InitConsolidatedMetadataIfNeeded()        */
     292             : /************************************************************************/
     293             : 
     294        1127 : void ZarrSharedResource::InitConsolidatedMetadataIfNeeded()
     295             : {
     296        1127 :     if (!m_oObjConsolidatedMetadata.IsValid())
     297             :     {
     298         291 :         m_oObjConsolidatedMetadata = CPLJSONObject();
     299         291 :         if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL)
     300             :         {
     301         169 :             m_oObjConsolidatedMetadata.Add("zarr_consolidated_format", 1);
     302         169 :             m_oObjConsolidatedMetadata.Add("metadata", CPLJSONObject());
     303             :         }
     304             :         else
     305             :         {
     306         122 :             m_oObjConsolidatedMetadata.Add("kind", "inline");
     307         122 :             m_oObjConsolidatedMetadata.Add("must_understand", false);
     308         122 :             m_oObjConsolidatedMetadata.Add("metadata", CPLJSONObject());
     309             :         }
     310             :     }
     311        1127 : }
     312             : 
     313             : /************************************************************************/
     314             : /*                ZarrSharedResource::SetZMetadataItem()                */
     315             : /************************************************************************/
     316             : 
     317        1408 : void ZarrSharedResource::SetZMetadataItem(const std::string &osFilename,
     318             :                                           const CPLJSONObject &obj)
     319             : {
     320        1408 :     if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
     321             :     {
     322        1119 :         InitConsolidatedMetadataIfNeeded();
     323             : 
     324        1119 :         CPLString osNormalizedFilename(osFilename);
     325        1119 :         osNormalizedFilename.replaceAll('\\', '/');
     326        1119 :         CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
     327             :                               (m_osRootDirectoryName + '/').c_str()));
     328             : 
     329        1119 :         if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::INTERNAL)
     330             :         {
     331         250 :             const auto nPos = osNormalizedFilename.rfind('/');
     332         250 :             if (nPos == std::string::npos)
     333           0 :                 return;
     334         250 :             osNormalizedFilename.resize(nPos);
     335             :         }
     336             : 
     337             :         const char *pszKey =
     338        1119 :             osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
     339        1119 :         if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL ||
     340         250 :             strcmp(pszKey, "zarr.json") != 0)
     341             :         {
     342         994 :             m_bConsolidatedMetadataModified = true;
     343        1988 :             auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
     344         994 :             oMetadata.DeleteNoSplitName(pszKey);
     345         994 :             oMetadata.AddNoSplitName(pszKey, obj);
     346             :         }
     347             :     }
     348             : }
     349             : 
     350             : /************************************************************************/
     351             : /*          ZarrSharedResource::DeleteZMetadataItemRecursive()          */
     352             : /************************************************************************/
     353             : 
     354          12 : void ZarrSharedResource::DeleteZMetadataItemRecursive(
     355             :     const std::string &osFilename)
     356             : {
     357          12 :     if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
     358             :     {
     359           4 :         InitConsolidatedMetadataIfNeeded();
     360             : 
     361           4 :         CPLString osNormalizedFilename(osFilename);
     362           4 :         osNormalizedFilename.replaceAll('\\', '/');
     363           4 :         CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
     364             :                               (m_osRootDirectoryName + '/').c_str()));
     365             : 
     366           4 :         if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::INTERNAL)
     367             :         {
     368           0 :             const auto nPos = osNormalizedFilename.rfind('/');
     369           0 :             if (nPos == std::string::npos)
     370           0 :                 return;
     371           0 :             osNormalizedFilename.resize(nPos);
     372             :         }
     373             : 
     374             :         const char *pszKey =
     375           4 :             osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
     376           4 :         if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL ||
     377           0 :             strcmp(pszKey, "zarr.json") != 0)
     378             :         {
     379           4 :             m_bConsolidatedMetadataModified = true;
     380          12 :             auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
     381          28 :             for (auto &item : oMetadata.GetChildren())
     382             :             {
     383          24 :                 if (STARTS_WITH(item.GetName().c_str(), pszKey))
     384             :                 {
     385          14 :                     oMetadata.DeleteNoSplitName(item.GetName());
     386             :                 }
     387             :             }
     388             :         }
     389             :     }
     390             : }
     391             : 
     392             : /************************************************************************/
     393             : /*            ZarrSharedResource::RenameZMetadataRecursive()            */
     394             : /************************************************************************/
     395             : 
     396          12 : void ZarrSharedResource::RenameZMetadataRecursive(
     397             :     const std::string &osOldFilename, const std::string &osNewFilename)
     398             : {
     399          12 :     if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
     400             :     {
     401           4 :         InitConsolidatedMetadataIfNeeded();
     402             : 
     403           8 :         CPLString osNormalizedOldFilename(osOldFilename);
     404           4 :         osNormalizedOldFilename.replaceAll('\\', '/');
     405           4 :         CPLAssert(STARTS_WITH(osNormalizedOldFilename.c_str(),
     406             :                               (m_osRootDirectoryName + '/').c_str()));
     407             : 
     408           8 :         CPLString osNormalizedNewFilename(osNewFilename);
     409           4 :         osNormalizedNewFilename.replaceAll('\\', '/');
     410           4 :         CPLAssert(STARTS_WITH(osNormalizedNewFilename.c_str(),
     411             :                               (m_osRootDirectoryName + '/').c_str()));
     412             : 
     413           4 :         m_bConsolidatedMetadataModified = true;
     414             : 
     415             :         const char *pszOldKeyRadix =
     416           4 :             osNormalizedOldFilename.c_str() + m_osRootDirectoryName.size() + 1;
     417             :         const char *pszNewKeyRadix =
     418           4 :             osNormalizedNewFilename.c_str() + m_osRootDirectoryName.size() + 1;
     419             : 
     420          12 :         auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
     421          28 :         for (auto &item : oMetadata.GetChildren())
     422             :         {
     423          24 :             if (STARTS_WITH(item.GetName().c_str(), pszOldKeyRadix))
     424             :             {
     425          11 :                 oMetadata.DeleteNoSplitName(item.GetName());
     426          22 :                 std::string osNewKey(pszNewKeyRadix);
     427          11 :                 osNewKey += (item.GetName().c_str() + strlen(pszOldKeyRadix));
     428          11 :                 oMetadata.AddNoSplitName(osNewKey, item);
     429             :             }
     430             :         }
     431             :     }
     432          12 : }
     433             : 
     434             : /************************************************************************/
     435             : /*              ZarrSharedResource::UpdateDimensionSize()               */
     436             : /************************************************************************/
     437             : 
     438           7 : void ZarrSharedResource::UpdateDimensionSize(
     439             :     const std::shared_ptr<GDALDimension> &poDim)
     440             : {
     441          14 :     auto poRG = m_poWeakRootGroup.lock();
     442           7 :     if (!poRG)
     443           0 :         poRG = OpenRootGroup();
     444           7 :     if (poRG)
     445             :     {
     446           7 :         poRG->UpdateDimensionSize(poDim);
     447             :     }
     448             :     else
     449             :     {
     450           0 :         CPLError(CE_Failure, CPLE_AppDefined, "UpdateDimensionSize() failed");
     451             :     }
     452           7 :     poRG.reset();
     453           7 : }
     454             : 
     455             : /************************************************************************/
     456             : /*               ZarrSharedResource::AddArrayInLoading()                */
     457             : /************************************************************************/
     458             : 
     459        1821 : bool ZarrSharedResource::AddArrayInLoading(const std::string &osZarrayFilename)
     460             : {
     461             :     // Prevent too deep or recursive array loading
     462        1821 :     if (m_oSetArrayInLoading.find(osZarrayFilename) !=
     463        3642 :         m_oSetArrayInLoading.end())
     464             :     {
     465           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     466             :                  "Attempt at recursively loading %s", osZarrayFilename.c_str());
     467           1 :         return false;
     468             :     }
     469        1820 :     if (m_oSetArrayInLoading.size() == 32)
     470             :     {
     471           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     472             :                  "Too deep call stack in LoadArray()");
     473           1 :         return false;
     474             :     }
     475        1819 :     m_oSetArrayInLoading.insert(osZarrayFilename);
     476        1819 :     return true;
     477             : }
     478             : 
     479             : /************************************************************************/
     480             : /*              ZarrSharedResource::RemoveArrayInLoading()              */
     481             : /************************************************************************/
     482             : 
     483        1819 : void ZarrSharedResource::RemoveArrayInLoading(
     484             :     const std::string &osZarrayFilename)
     485             : {
     486        1819 :     m_oSetArrayInLoading.erase(osZarrayFilename);
     487        1819 : }

Generated by: LCOV version 1.14