LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_sharedresource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 146 168 86.9 %
Date: 2025-06-19 12:30:01 Functions: 10 10 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        1055 : ZarrSharedResource::ZarrSharedResource(const std::string &osRootDirectoryName,
      23        1055 :                                        bool bUpdatable)
      24        1055 :     : m_bUpdatable(bUpdatable)
      25             : {
      26        1055 :     m_oObj.Add("zarr_consolidated_format", 1);
      27        1055 :     m_oObj.Add("metadata", CPLJSONObject());
      28             : 
      29        1055 :     m_osRootDirectoryName = osRootDirectoryName;
      30        1055 :     if (!m_osRootDirectoryName.empty() && m_osRootDirectoryName.back() == '/')
      31             :     {
      32           0 :         m_osRootDirectoryName.pop_back();
      33             :     }
      34        2110 :     m_poPAM = std::make_shared<GDALPamMultiDim>(
      35        3165 :         CPLFormFilenameSafe(m_osRootDirectoryName.c_str(), "pam", nullptr));
      36        1055 : }
      37             : 
      38             : /************************************************************************/
      39             : /*              ZarrSharedResource::Create()                            */
      40             : /************************************************************************/
      41             : 
      42             : std::shared_ptr<ZarrSharedResource>
      43        1055 : ZarrSharedResource::Create(const std::string &osRootDirectoryName,
      44             :                            bool bUpdatable)
      45             : {
      46             :     return std::shared_ptr<ZarrSharedResource>(
      47        1055 :         new ZarrSharedResource(osRootDirectoryName, bUpdatable));
      48             : }
      49             : 
      50             : /************************************************************************/
      51             : /*              ZarrSharedResource::~ZarrSharedResource()               */
      52             : /************************************************************************/
      53             : 
      54        1055 : ZarrSharedResource::~ZarrSharedResource()
      55             : {
      56             :     // We try to clean caches at dataset closing, especially for Parquet
      57             :     // references, since closing Parquet datasets when the virtual file
      58             :     // systems are destroyed can be too late and cause crashes.
      59        1055 :     VSIKerchunkFileSystemsCleanCache();
      60             : 
      61        1055 :     if (m_bZMetadataModified)
      62             :     {
      63         182 :         CPLJSONDocument oDoc;
      64         182 :         oDoc.SetRoot(m_oObj);
      65         182 :         oDoc.Save(CPLFormFilenameSafe(m_osRootDirectoryName.c_str(),
      66             :                                       ".zmetadata", nullptr));
      67             :     }
      68        1055 : }
      69             : 
      70             : /************************************************************************/
      71             : /*             ZarrSharedResource::OpenRootGroup()                      */
      72             : /************************************************************************/
      73             : 
      74         740 : std::shared_ptr<ZarrGroupBase> ZarrSharedResource::OpenRootGroup()
      75             : {
      76             :     {
      77        1480 :         auto poRG = ZarrV2Group::Create(shared_from_this(), std::string(), "/");
      78         740 :         poRG->SetUpdatable(m_bUpdatable);
      79         740 :         poRG->SetDirectoryName(m_osRootDirectoryName);
      80             : 
      81             :         const std::string osZarrayFilename(CPLFormFilenameSafe(
      82         740 :             m_osRootDirectoryName.c_str(), ".zarray", nullptr));
      83             :         VSIStatBufL sStat;
      84         740 :         const auto nErrorCount = CPLGetErrorCounter();
      85         740 :         if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
      86             :         {
      87         518 :             CPLJSONDocument oDoc;
      88         259 :             if (!oDoc.Load(osZarrayFilename))
      89           0 :                 return nullptr;
      90         518 :             const auto oRoot = oDoc.GetRoot();
      91         259 :             if (oRoot["_NCZARR_ARRAY"].IsValid())
      92             :             {
      93             :                 // If opening a NCZarr array, initialize its group from NCZarr
      94             :                 // metadata.
      95             :                 const std::string osGroupFilename(CPLFormFilenameSafe(
      96           3 :                     CPLGetDirnameSafe(m_osRootDirectoryName.c_str()).c_str(),
      97           3 :                     ".zgroup", nullptr));
      98           3 :                 if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
      99             :                 {
     100           2 :                     CPLJSONDocument oDocGroup;
     101           2 :                     if (oDocGroup.Load(osGroupFilename))
     102             :                     {
     103           2 :                         if (!poRG->InitFromZGroup(oDocGroup.GetRoot()))
     104           1 :                             return nullptr;
     105             :                     }
     106             :                 }
     107             :             }
     108             :             const std::string osArrayName(
     109         516 :                 CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
     110         774 :             if (!poRG->LoadArray(osArrayName, osZarrayFilename, oRoot, false,
     111         774 :                                  CPLJSONObject()))
     112          39 :                 return nullptr;
     113             : 
     114         219 :             return poRG;
     115             :         }
     116         489 :         else if (CPLGetErrorCounter() > nErrorCount &&
     117           8 :                  strstr(CPLGetLastErrorMsg(),
     118             :                         "Generation of Kerchunk Parquet cache"))
     119             :         {
     120           6 :             return nullptr;
     121             :         }
     122             : 
     123             :         const std::string osZmetadataFilename(CPLFormFilenameSafe(
     124         475 :             m_osRootDirectoryName.c_str(), ".zmetadata", nullptr));
     125         475 :         if (CPLTestBool(CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
     126         944 :                                              "YES")) &&
     127         469 :             VSIStatL(osZmetadataFilename.c_str(), &sStat) == 0)
     128             :         {
     129         171 :             if (!m_bZMetadataEnabled)
     130             :             {
     131         171 :                 CPLJSONDocument oDoc;
     132         171 :                 if (!oDoc.Load(osZmetadataFilename))
     133           0 :                     return nullptr;
     134             : 
     135         171 :                 m_bZMetadataEnabled = true;
     136         171 :                 m_oObj = oDoc.GetRoot();
     137             :             }
     138         171 :             poRG->InitFromZMetadata(m_oObj);
     139             : 
     140         171 :             return poRG;
     141             :         }
     142             : 
     143             :         const std::string osGroupFilename(CPLFormFilenameSafe(
     144         304 :             m_osRootDirectoryName.c_str(), ".zgroup", nullptr));
     145         304 :         if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
     146             :         {
     147         174 :             CPLJSONDocument oDoc;
     148          87 :             if (!oDoc.Load(osGroupFilename))
     149           0 :                 return nullptr;
     150             : 
     151          87 :             if (!poRG->InitFromZGroup(oDoc.GetRoot()))
     152           3 :                 return nullptr;
     153          84 :             return poRG;
     154             :         }
     155             :     }
     156             : 
     157             :     // Zarr v3
     158         434 :     auto poRG_V3 = ZarrV3Group::Create(shared_from_this(), std::string(), "/",
     159         868 :                                        m_osRootDirectoryName);
     160         217 :     poRG_V3->SetUpdatable(m_bUpdatable);
     161             : 
     162             :     const std::string osZarrJsonFilename(CPLFormFilenameSafe(
     163         434 :         m_osRootDirectoryName.c_str(), "zarr.json", nullptr));
     164             :     VSIStatBufL sStat;
     165         217 :     if (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0)
     166             :     {
     167         428 :         CPLJSONDocument oDoc;
     168         214 :         if (!oDoc.Load(osZarrJsonFilename))
     169           0 :             return nullptr;
     170         428 :         const auto oRoot = oDoc.GetRoot();
     171         214 :         if (oRoot.GetInteger("zarr_format") != 3)
     172             :         {
     173           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     174             :                      "Unhandled zarr_format value");
     175           0 :             return nullptr;
     176             :         }
     177         642 :         const std::string osNodeType = oRoot.GetString("node_type");
     178         214 :         if (osNodeType == "array")
     179             :         {
     180             :             const std::string osArrayName(
     181         106 :                 CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
     182          53 :             poRG_V3->SetExplored();
     183          53 :             if (!poRG_V3->LoadArray(osArrayName, osZarrJsonFilename, oRoot))
     184          32 :                 return nullptr;
     185             : 
     186          21 :             return poRG_V3;
     187             :         }
     188         161 :         else if (osNodeType == "group")
     189             :         {
     190         161 :             return poRG_V3;
     191             :         }
     192             :         else
     193             :         {
     194           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unhandled node_type value");
     195           0 :             return nullptr;
     196             :         }
     197             :     }
     198             : 
     199             :     // No explicit zarr.json in root directory ? Then recurse until we find
     200             :     // one.
     201           3 :     auto psDir = VSIOpenDir(m_osRootDirectoryName.c_str(), -1, nullptr);
     202           3 :     if (!psDir)
     203           3 :         return nullptr;
     204           0 :     bool bZarrJsonFound = false;
     205           0 :     while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
     206             :     {
     207           0 :         if (!VSI_ISDIR(psEntry->nMode) &&
     208           0 :             strcmp(CPLGetFilename(psEntry->pszName), "zarr.json") == 0)
     209             :         {
     210           0 :             bZarrJsonFound = true;
     211           0 :             break;
     212             :         }
     213           0 :     }
     214           0 :     VSICloseDir(psDir);
     215           0 :     if (bZarrJsonFound)
     216           0 :         return poRG_V3;
     217             : 
     218           0 :     return nullptr;
     219             : }
     220             : 
     221             : /************************************************************************/
     222             : /*             ZarrSharedResource::SetZMetadataItem()                   */
     223             : /************************************************************************/
     224             : 
     225         962 : void ZarrSharedResource::SetZMetadataItem(const std::string &osFilename,
     226             :                                           const CPLJSONObject &obj)
     227             : {
     228         962 :     if (m_bZMetadataEnabled)
     229             :     {
     230        1686 :         CPLString osNormalizedFilename(osFilename);
     231         843 :         osNormalizedFilename.replaceAll('\\', '/');
     232         843 :         CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
     233             :                               (m_osRootDirectoryName + '/').c_str()));
     234         843 :         m_bZMetadataModified = true;
     235             :         const char *pszKey =
     236         843 :             osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
     237        1686 :         auto oMetadata = m_oObj["metadata"];
     238         843 :         oMetadata.DeleteNoSplitName(pszKey);
     239         843 :         oMetadata.AddNoSplitName(pszKey, obj);
     240             :     }
     241         962 : }
     242             : 
     243             : /************************************************************************/
     244             : /*         ZarrSharedResource::DeleteZMetadataItemRecursive()           */
     245             : /************************************************************************/
     246             : 
     247          12 : void ZarrSharedResource::DeleteZMetadataItemRecursive(
     248             :     const std::string &osFilename)
     249             : {
     250          12 :     if (m_bZMetadataEnabled)
     251             :     {
     252           8 :         CPLString osNormalizedFilename(osFilename);
     253           4 :         osNormalizedFilename.replaceAll('\\', '/');
     254           4 :         CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
     255             :                               (m_osRootDirectoryName + '/').c_str()));
     256           4 :         m_bZMetadataModified = true;
     257             :         const char *pszKey =
     258           4 :             osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
     259             : 
     260          12 :         auto oMetadata = m_oObj["metadata"];
     261          28 :         for (auto &item : oMetadata.GetChildren())
     262             :         {
     263          24 :             if (STARTS_WITH(item.GetName().c_str(), pszKey))
     264             :             {
     265          14 :                 oMetadata.DeleteNoSplitName(item.GetName());
     266             :             }
     267             :         }
     268             :     }
     269          12 : }
     270             : 
     271             : /************************************************************************/
     272             : /*             ZarrSharedResource::RenameZMetadataRecursive()           */
     273             : /************************************************************************/
     274             : 
     275          12 : void ZarrSharedResource::RenameZMetadataRecursive(
     276             :     const std::string &osOldFilename, const std::string &osNewFilename)
     277             : {
     278          12 :     if (m_bZMetadataEnabled)
     279             :     {
     280           8 :         CPLString osNormalizedOldFilename(osOldFilename);
     281           4 :         osNormalizedOldFilename.replaceAll('\\', '/');
     282           4 :         CPLAssert(STARTS_WITH(osNormalizedOldFilename.c_str(),
     283             :                               (m_osRootDirectoryName + '/').c_str()));
     284             : 
     285           8 :         CPLString osNormalizedNewFilename(osNewFilename);
     286           4 :         osNormalizedNewFilename.replaceAll('\\', '/');
     287           4 :         CPLAssert(STARTS_WITH(osNormalizedNewFilename.c_str(),
     288             :                               (m_osRootDirectoryName + '/').c_str()));
     289             : 
     290           4 :         m_bZMetadataModified = true;
     291             : 
     292             :         const char *pszOldKeyRadix =
     293           4 :             osNormalizedOldFilename.c_str() + m_osRootDirectoryName.size() + 1;
     294             :         const char *pszNewKeyRadix =
     295           4 :             osNormalizedNewFilename.c_str() + m_osRootDirectoryName.size() + 1;
     296             : 
     297          12 :         auto oMetadata = m_oObj["metadata"];
     298          32 :         for (auto &item : oMetadata.GetChildren())
     299             :         {
     300          28 :             if (STARTS_WITH(item.GetName().c_str(), pszOldKeyRadix))
     301             :             {
     302          15 :                 oMetadata.DeleteNoSplitName(item.GetName());
     303          30 :                 std::string osNewKey(pszNewKeyRadix);
     304          15 :                 osNewKey += (item.GetName().c_str() + strlen(pszOldKeyRadix));
     305          15 :                 oMetadata.AddNoSplitName(osNewKey, item);
     306             :             }
     307             :         }
     308             :     }
     309          12 : }
     310             : 
     311             : /************************************************************************/
     312             : /*             ZarrSharedResource::UpdateDimensionSize()                */
     313             : /************************************************************************/
     314             : 
     315           7 : void ZarrSharedResource::UpdateDimensionSize(
     316             :     const std::shared_ptr<GDALDimension> &poDim)
     317             : {
     318          14 :     auto poRG = m_poWeakRootGroup.lock();
     319           7 :     if (!poRG)
     320           0 :         poRG = OpenRootGroup();
     321           7 :     if (poRG)
     322             :     {
     323           7 :         poRG->UpdateDimensionSize(poDim);
     324             :     }
     325             :     else
     326             :     {
     327           0 :         CPLError(CE_Failure, CPLE_AppDefined, "UpdateDimensionSize() failed");
     328             :     }
     329           7 :     poRG.reset();
     330           7 : }
     331             : 
     332             : /************************************************************************/
     333             : /*             ZarrSharedResource::AddArrayInLoading()                  */
     334             : /************************************************************************/
     335             : 
     336         883 : bool ZarrSharedResource::AddArrayInLoading(const std::string &osZarrayFilename)
     337             : {
     338             :     // Prevent too deep or recursive array loading
     339         883 :     if (m_oSetArrayInLoading.find(osZarrayFilename) !=
     340        1766 :         m_oSetArrayInLoading.end())
     341             :     {
     342           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     343             :                  "Attempt at recursively loading %s", osZarrayFilename.c_str());
     344           1 :         return false;
     345             :     }
     346         882 :     if (m_oSetArrayInLoading.size() == 32)
     347             :     {
     348           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     349             :                  "Too deep call stack in LoadArray()");
     350           1 :         return false;
     351             :     }
     352         881 :     m_oSetArrayInLoading.insert(osZarrayFilename);
     353         881 :     return true;
     354             : }
     355             : 
     356             : /************************************************************************/
     357             : /*             ZarrSharedResource::RemoveArrayInLoading()               */
     358             : /************************************************************************/
     359             : 
     360         881 : void ZarrSharedResource::RemoveArrayInLoading(
     361             :     const std::string &osZarrayFilename)
     362             : {
     363         881 :     m_oSetArrayInLoading.erase(osZarrayFilename);
     364         881 : }

Generated by: LCOV version 1.14