LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_group.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 224 250 89.6 %
Date: 2025-10-22 21:46:17 Functions: 20 21 95.2 %

          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             : 
      15             : #include <algorithm>
      16             : #include <cassert>
      17             : #include <limits>
      18             : #include <map>
      19             : #include <set>
      20             : 
      21             : /************************************************************************/
      22             : /*                          ~ZarrGroupBase()                            */
      23             : /************************************************************************/
      24             : 
      25        1550 : ZarrGroupBase::~ZarrGroupBase()
      26             : {
      27        1550 :     CPL_IGNORE_RET_VAL(ZarrGroupBase::Close());
      28        1550 : }
      29             : 
      30             : /************************************************************************/
      31             : /*                            Close()                                   */
      32             : /************************************************************************/
      33             : 
      34        4890 : bool ZarrGroupBase::Close()
      35             : {
      36        4890 :     bool ret = true;
      37             : 
      38        5676 :     for (auto &kv : m_oMapGroups)
      39             :     {
      40         786 :         ret = kv.second->Close() && ret;
      41             :     }
      42             : 
      43        9028 :     for (auto &kv : m_oMapMDArrays)
      44             :     {
      45        4138 :         ret = kv.second->Flush() && ret;
      46             :     }
      47        4890 :     return ret;
      48             : }
      49             : 
      50             : /************************************************************************/
      51             : /*                           GetMDArrayNames()                          */
      52             : /************************************************************************/
      53             : 
      54        1165 : std::vector<std::string> ZarrGroupBase::GetMDArrayNames(CSLConstList) const
      55             : {
      56        1165 :     if (!CheckValidAndErrorOutIfNot())
      57           0 :         return {};
      58             : 
      59        1165 :     if (!m_bDirectoryExplored)
      60         282 :         ExploreDirectory();
      61             : 
      62        1165 :     return m_aosArrays;
      63             : }
      64             : 
      65             : /************************************************************************/
      66             : /*                            RegisterArray()                           */
      67             : /************************************************************************/
      68             : 
      69        1276 : void ZarrGroupBase::RegisterArray(const std::shared_ptr<ZarrArray> &array) const
      70             : {
      71        1276 :     m_oMapMDArrays[array->GetName()] = array;
      72        1276 :     if (!cpl::contains(m_oSetArrayNames, array->GetName()))
      73             :     {
      74        1186 :         m_oSetArrayNames.insert(array->GetName());
      75        1186 :         m_aosArrays.emplace_back(array->GetName());
      76             :     }
      77        2552 :     array->RegisterGroup(
      78        2552 :         std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()));
      79        1276 : }
      80             : 
      81             : /************************************************************************/
      82             : /*                            GetGroupNames()                           */
      83             : /************************************************************************/
      84             : 
      85         310 : std::vector<std::string> ZarrGroupBase::GetGroupNames(CSLConstList) const
      86             : {
      87         310 :     if (!CheckValidAndErrorOutIfNot())
      88           0 :         return {};
      89             : 
      90         310 :     if (!m_bDirectoryExplored)
      91          39 :         ExploreDirectory();
      92             : 
      93         310 :     return m_aosGroups;
      94             : }
      95             : 
      96             : /************************************************************************/
      97             : /*                             DeleteGroup()                            */
      98             : /************************************************************************/
      99             : 
     100          18 : bool ZarrGroupBase::DeleteGroup(const std::string &osName,
     101             :                                 CSLConstList /*papszOptions*/)
     102             : {
     103          18 :     if (!CheckValidAndErrorOutIfNot())
     104           0 :         return false;
     105             : 
     106          18 :     if (!m_bUpdatable)
     107             :     {
     108           6 :         CPLError(CE_Failure, CPLE_NotSupported,
     109             :                  "Dataset not open in update mode");
     110           6 :         return false;
     111             :     }
     112          12 :     if (CPLHasPathTraversal(osName.c_str()))
     113             :     {
     114           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Path traversal detected in %s",
     115             :                  osName.c_str());
     116           0 :         return false;
     117             :     }
     118          12 :     GetGroupNames();
     119             : 
     120          12 :     auto oIterNames = std::find(m_aosGroups.begin(), m_aosGroups.end(), osName);
     121          12 :     if (oIterNames == m_aosGroups.end())
     122             :     {
     123           6 :         CPLError(CE_Failure, CPLE_AppDefined,
     124             :                  "Group %s is not a sub-group of this group", osName.c_str());
     125           6 :         return false;
     126             :     }
     127             : 
     128             :     const std::string osSubDirName =
     129          12 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     130           6 :     if (VSIRmdirRecursive(osSubDirName.c_str()) != 0)
     131             :     {
     132           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot delete %s",
     133             :                  osSubDirName.c_str());
     134           0 :         return false;
     135             :     }
     136             : 
     137           6 :     m_poSharedResource->DeleteZMetadataItemRecursive(osSubDirName);
     138             : 
     139           6 :     m_oSetGroupNames.erase(osName);
     140           6 :     m_aosGroups.erase(oIterNames);
     141             : 
     142           6 :     auto oIter = m_oMapGroups.find(osName);
     143           6 :     if (oIter != m_oMapGroups.end())
     144             :     {
     145           4 :         oIter->second->Deleted();
     146           4 :         m_oMapGroups.erase(oIter);
     147             :     }
     148             : 
     149           6 :     return true;
     150             : }
     151             : 
     152             : /************************************************************************/
     153             : /*                       NotifyChildrenOfDeletion()                     */
     154             : /************************************************************************/
     155             : 
     156           6 : void ZarrGroupBase::NotifyChildrenOfDeletion()
     157             : {
     158           8 :     for (const auto &oIter : m_oMapGroups)
     159           2 :         oIter.second->ParentDeleted();
     160             : 
     161          10 :     for (const auto &oIter : m_oMapMDArrays)
     162           4 :         oIter.second->ParentDeleted();
     163             : 
     164           6 :     m_oAttrGroup.ParentDeleted();
     165             : 
     166          10 :     for (const auto &oIter : m_oMapDimensions)
     167           4 :         oIter.second->ParentDeleted();
     168           6 : }
     169             : 
     170             : /************************************************************************/
     171             : /*                  ZarrGroupBase::CreateAttribute()                    */
     172             : /************************************************************************/
     173             : 
     174          78 : std::shared_ptr<GDALAttribute> ZarrGroupBase::CreateAttribute(
     175             :     const std::string &osName, const std::vector<GUInt64> &anDimensions,
     176             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
     177             : {
     178          78 :     if (!CheckValidAndErrorOutIfNot())
     179           0 :         return nullptr;
     180             : 
     181          78 :     if (!m_bUpdatable)
     182             :     {
     183           3 :         CPLError(CE_Failure, CPLE_NotSupported,
     184             :                  "Dataset not open in update mode");
     185           3 :         return nullptr;
     186             :     }
     187          75 :     if (anDimensions.size() >= 2)
     188             :     {
     189           3 :         CPLError(CE_Failure, CPLE_NotSupported,
     190             :                  "Cannot create attributes of dimension >= 2");
     191           3 :         return nullptr;
     192             :     }
     193          72 :     LoadAttributes();
     194             :     return m_oAttrGroup.CreateAttribute(osName, anDimensions, oDataType,
     195          72 :                                         papszOptions);
     196             : }
     197             : 
     198             : /************************************************************************/
     199             : /*                  ZarrGroupBase::DeleteAttribute()                   */
     200             : /************************************************************************/
     201             : 
     202          18 : bool ZarrGroupBase::DeleteAttribute(const std::string &osName, CSLConstList)
     203             : {
     204          18 :     if (!CheckValidAndErrorOutIfNot())
     205           0 :         return false;
     206             : 
     207          18 :     if (!m_bUpdatable)
     208             :     {
     209           6 :         CPLError(CE_Failure, CPLE_NotSupported,
     210             :                  "Dataset not open in update mode");
     211           6 :         return false;
     212             :     }
     213             : 
     214          12 :     LoadAttributes();
     215          12 :     return m_oAttrGroup.DeleteAttribute(osName);
     216             : }
     217             : 
     218             : /************************************************************************/
     219             : /*                            GetDimensions()                           */
     220             : /************************************************************************/
     221             : 
     222             : std::vector<std::shared_ptr<GDALDimension>>
     223         631 : ZarrGroupBase::GetDimensions(CSLConstList) const
     224             : {
     225         631 :     if (!CheckValidAndErrorOutIfNot())
     226           0 :         return {};
     227             : 
     228         631 :     if (!m_bReadFromZMetadata && !m_bDimensionsInstantiated)
     229             :     {
     230         291 :         m_bDimensionsInstantiated = true;
     231             :         // We need to instantiate arrays to discover dimensions
     232         582 :         const auto aosArrays = GetMDArrayNames();
     233         325 :         for (const auto &osArray : aosArrays)
     234             :         {
     235          34 :             OpenMDArray(osArray);
     236             :         }
     237             :     }
     238             : 
     239        1262 :     std::vector<std::shared_ptr<GDALDimension>> oRes;
     240        1121 :     for (const auto &oIter : m_oMapDimensions)
     241             :     {
     242         490 :         oRes.push_back(oIter.second);
     243             :     }
     244         631 :     return oRes;
     245             : }
     246             : 
     247             : /************************************************************************/
     248             : /*                            DeleteMDArray()                           */
     249             : /************************************************************************/
     250             : 
     251          18 : bool ZarrGroupBase::DeleteMDArray(const std::string &osName,
     252             :                                   CSLConstList /*papszOptions*/)
     253             : {
     254          18 :     if (!CheckValidAndErrorOutIfNot())
     255           0 :         return false;
     256             : 
     257          18 :     if (!m_bUpdatable)
     258             :     {
     259           6 :         CPLError(CE_Failure, CPLE_NotSupported,
     260             :                  "Dataset not open in update mode");
     261           6 :         return false;
     262             :     }
     263          12 :     if (CPLHasPathTraversal(osName.c_str()))
     264             :     {
     265           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Path traversal detected in %s",
     266             :                  osName.c_str());
     267           0 :         return false;
     268             :     }
     269          12 :     GetMDArrayNames();
     270             : 
     271          12 :     auto oIterNames = std::find(m_aosArrays.begin(), m_aosArrays.end(), osName);
     272          12 :     if (oIterNames == m_aosArrays.end())
     273             :     {
     274           6 :         CPLError(CE_Failure, CPLE_AppDefined,
     275             :                  "Array %s is not an array of this group", osName.c_str());
     276           6 :         return false;
     277             :     }
     278             : 
     279             :     const std::string osSubDirName =
     280          12 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     281           6 :     if (VSIRmdirRecursive(osSubDirName.c_str()) != 0)
     282             :     {
     283           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot delete %s",
     284             :                  osSubDirName.c_str());
     285           0 :         return false;
     286             :     }
     287             : 
     288           6 :     m_poSharedResource->DeleteZMetadataItemRecursive(osSubDirName);
     289             : 
     290           6 :     m_oSetArrayNames.erase(osName);
     291           6 :     m_aosArrays.erase(oIterNames);
     292             : 
     293           6 :     auto oIter = m_oMapMDArrays.find(osName);
     294           6 :     if (oIter != m_oMapMDArrays.end())
     295             :     {
     296           4 :         oIter->second->Deleted();
     297           4 :         m_oMapMDArrays.erase(oIter);
     298             :     }
     299             : 
     300           6 :     return true;
     301             : }
     302             : 
     303             : /************************************************************************/
     304             : /*                             CreateDimension()                        */
     305             : /************************************************************************/
     306             : 
     307         557 : std::shared_ptr<GDALDimension> ZarrGroupBase::CreateDimension(
     308             :     const std::string &osName, const std::string &osType,
     309             :     const std::string &osDirection, GUInt64 nSize, CSLConstList)
     310             : {
     311         557 :     if (!CheckValidAndErrorOutIfNot())
     312           0 :         return nullptr;
     313             : 
     314         557 :     if (osName.empty())
     315             :     {
     316           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     317             :                  "Empty dimension name not supported");
     318           0 :         return nullptr;
     319             :     }
     320         557 :     GetDimensions(nullptr);
     321             : 
     322         557 :     if (m_oMapDimensions.find(osName) != m_oMapDimensions.end())
     323             :     {
     324           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     325             :                  "A dimension with same name already exists");
     326           0 :         return nullptr;
     327             :     }
     328             :     auto newDim(std::make_shared<ZarrDimension>(
     329         557 :         m_poSharedResource,
     330        1114 :         std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()), GetFullName(),
     331        1114 :         osName, osType, osDirection, nSize));
     332         557 :     newDim->SetXArrayDimension();
     333         557 :     m_oMapDimensions[osName] = newDim;
     334         557 :     return newDim;
     335             : }
     336             : 
     337             : /************************************************************************/
     338             : /*                          RenameDimension()                           */
     339             : /************************************************************************/
     340             : 
     341          12 : bool ZarrGroupBase::RenameDimension(const std::string &osOldName,
     342             :                                     const std::string &osNewName)
     343             : {
     344          12 :     if (m_oMapDimensions.find(osNewName) != m_oMapDimensions.end())
     345             :     {
     346           6 :         CPLError(CE_Failure, CPLE_AppDefined,
     347             :                  "A dimension with same name already exists");
     348           6 :         return false;
     349             :     }
     350           6 :     auto oIter = m_oMapDimensions.find(osOldName);
     351           6 :     if (oIter == m_oMapDimensions.end())
     352             :     {
     353           0 :         CPLAssert(false);
     354             :         return false;
     355             :     }
     356           6 :     auto poDim = std::move(oIter->second);
     357           6 :     m_oMapDimensions.erase(oIter);
     358           6 :     m_oMapDimensions[osNewName] = std::move(poDim);
     359           6 :     return true;
     360             : }
     361             : 
     362             : /************************************************************************/
     363             : /*                  ZarrGroupBase::UpdateDimensionSize()                */
     364             : /************************************************************************/
     365             : 
     366           7 : void ZarrGroupBase::UpdateDimensionSize(
     367             :     const std::shared_ptr<GDALDimension> &poUpdatedDim)
     368             : {
     369          14 :     const auto aosGroupNames = GetGroupNames();
     370           7 :     for (const auto &osGroupName : aosGroupNames)
     371             :     {
     372           0 :         auto poSubGroup = OpenZarrGroup(osGroupName);
     373           0 :         if (poSubGroup)
     374             :         {
     375           0 :             poSubGroup->UpdateDimensionSize(poUpdatedDim);
     376             :         }
     377             :     }
     378          14 :     const auto aosArrayNames = GetMDArrayNames();
     379          22 :     for (const auto &osArrayName : aosArrayNames)
     380             :     {
     381             :         // Disable checks that size of variables referenced by _ARRAY_DIMENSIONS
     382             :         // are consistent with array shapes, as we are in the middle of updating
     383             :         // things
     384          15 :         m_bDimSizeInUpdate = true;
     385          30 :         auto poArray = OpenZarrArray(osArrayName);
     386          15 :         m_bDimSizeInUpdate = false;
     387          15 :         if (poArray)
     388             :         {
     389          39 :             for (auto &poDim : poArray->GetDimensions())
     390             :             {
     391          24 :                 if (poDim->GetFullName() == poUpdatedDim->GetFullName())
     392             :                 {
     393             :                     auto poModifiableDim =
     394          30 :                         std::dynamic_pointer_cast<ZarrDimension>(poDim);
     395          15 :                     CPLAssert(poModifiableDim);
     396          15 :                     poModifiableDim->SetSize(poUpdatedDim->GetSize());
     397          15 :                     poArray->SetDefinitionModified(true);
     398             :                 }
     399             :             }
     400             :         }
     401             :     }
     402           7 : }
     403             : 
     404             : /************************************************************************/
     405             : /*                  ZarrGroupBase::NotifyArrayRenamed()                 */
     406             : /************************************************************************/
     407             : 
     408           6 : void ZarrGroupBase::NotifyArrayRenamed(const std::string &osOldName,
     409             :                                        const std::string &osNewName)
     410             : {
     411           6 :     for (auto &osName : m_aosArrays)
     412             :     {
     413           6 :         if (osName == osOldName)
     414             :         {
     415           6 :             osName = osNewName;
     416           6 :             break;
     417             :         }
     418             :     }
     419             : 
     420           6 :     auto oIter = m_oMapMDArrays.find(osOldName);
     421           6 :     if (oIter != m_oMapMDArrays.end())
     422             :     {
     423           6 :         auto poArray = std::move(oIter->second);
     424           6 :         m_oMapMDArrays.erase(oIter);
     425           6 :         m_oMapMDArrays[osNewName] = std::move(poArray);
     426             :     }
     427           6 : }
     428             : 
     429             : /************************************************************************/
     430             : /*                         IsValidObjectName()                          */
     431             : /************************************************************************/
     432             : 
     433             : /* static */
     434         680 : bool ZarrGroupBase::IsValidObjectName(const std::string &osName)
     435             : {
     436        1954 :     return !(osName.empty() || osName == "." || osName == ".." ||
     437        1274 :              osName.find('/') != std::string::npos ||
     438        1258 :              osName.find('\\') != std::string::npos ||
     439         625 :              osName.find(':') != std::string::npos ||
     440        1297 :              STARTS_WITH(osName.c_str(), ".z"));
     441             : }
     442             : 
     443             : /************************************************************************/
     444             : /*                 CheckArrayOrGroupWithSameNameDoesNotExist()          */
     445             : /************************************************************************/
     446             : 
     447          27 : bool ZarrGroupBase::CheckArrayOrGroupWithSameNameDoesNotExist(
     448             :     const std::string &osName) const
     449             : {
     450          54 :     const auto groupNames = GetGroupNames();
     451          27 :     if (std::find(groupNames.begin(), groupNames.end(), osName) !=
     452          54 :         groupNames.end())
     453             :     {
     454           9 :         CPLError(CE_Failure, CPLE_AppDefined,
     455             :                  "A group with same name already exists");
     456           9 :         return false;
     457             :     }
     458             : 
     459          36 :     const auto arrayNames = GetMDArrayNames();
     460          18 :     if (std::find(arrayNames.begin(), arrayNames.end(), osName) !=
     461          36 :         arrayNames.end())
     462             :     {
     463           6 :         CPLError(CE_Failure, CPLE_AppDefined,
     464             :                  "An array with same name already exists");
     465           6 :         return false;
     466             :     }
     467             : 
     468          12 :     return true;
     469             : }
     470             : 
     471             : /************************************************************************/
     472             : /*                              Rename()                                */
     473             : /************************************************************************/
     474             : 
     475          36 : bool ZarrGroupBase::Rename(const std::string &osNewName)
     476             : {
     477          36 :     if (!CheckValidAndErrorOutIfNot())
     478           3 :         return false;
     479             : 
     480          33 :     if (!m_bUpdatable)
     481             :     {
     482           6 :         CPLError(CE_Failure, CPLE_NotSupported,
     483             :                  "Dataset not open in update mode");
     484           6 :         return false;
     485             :     }
     486          27 :     if (!IsValidObjectName(osNewName))
     487             :     {
     488           6 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
     489           6 :         return false;
     490             :     }
     491          21 :     if (m_osName == "/")
     492             :     {
     493           6 :         CPLError(CE_Failure, CPLE_NotSupported, "Cannot rename root group");
     494           6 :         return false;
     495             :     }
     496             : 
     497          30 :     auto pParent = std::dynamic_pointer_cast<ZarrGroupBase>(m_poParent.lock());
     498          15 :     if (pParent)
     499             :     {
     500          15 :         if (!pParent->CheckArrayOrGroupWithSameNameDoesNotExist(osNewName))
     501           9 :             return false;
     502             :     }
     503             : 
     504          12 :     std::string osNewDirectoryName(m_osDirectoryName);
     505           6 :     osNewDirectoryName.resize(osNewDirectoryName.size() - m_osName.size());
     506           6 :     osNewDirectoryName += osNewName;
     507             : 
     508           6 :     if (VSIRename(m_osDirectoryName.c_str(), osNewDirectoryName.c_str()) != 0)
     509             :     {
     510           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Renaming of %s to %s failed",
     511             :                  m_osDirectoryName.c_str(), osNewDirectoryName.c_str());
     512           0 :         return false;
     513             :     }
     514             : 
     515           6 :     if (pParent)
     516             :     {
     517           6 :         auto oIter = pParent->m_oMapGroups.find(m_osName);
     518           6 :         if (oIter != pParent->m_oMapGroups.end())
     519             :         {
     520           6 :             pParent->m_oMapGroups.erase(oIter);
     521           6 :             CPLAssert(m_pSelf.lock());
     522          12 :             pParent->m_oMapGroups[osNewName] =
     523          18 :                 std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     524             :         }
     525             : 
     526           6 :         for (auto &osName : pParent->m_aosGroups)
     527             :         {
     528           6 :             if (osName == m_osName)
     529             :             {
     530           6 :                 osName = osNewName;
     531           6 :                 break;
     532             :             }
     533             :         }
     534             :     }
     535             : 
     536           6 :     m_poSharedResource->RenameZMetadataRecursive(m_osDirectoryName,
     537             :                                                  osNewDirectoryName);
     538             : 
     539           6 :     m_osDirectoryName = std::move(osNewDirectoryName);
     540             : 
     541           6 :     BaseRename(osNewName);
     542             : 
     543           6 :     return true;
     544             : }
     545             : 
     546             : /************************************************************************/
     547             : /*                          ParentRenamed()                             */
     548             : /************************************************************************/
     549             : 
     550           4 : void ZarrGroupBase::ParentRenamed(const std::string &osNewParentFullName)
     551             : {
     552           8 :     auto pParent = std::dynamic_pointer_cast<ZarrGroupBase>(m_poParent.lock());
     553             :     // The parent necessarily exist, since it notified us
     554           4 :     CPLAssert(pParent);
     555             : 
     556           8 :     m_osDirectoryName = CPLFormFilenameSafe(pParent->m_osDirectoryName.c_str(),
     557           4 :                                             m_osName.c_str(), nullptr);
     558             : 
     559           4 :     GDALGroup::ParentRenamed(osNewParentFullName);
     560           4 : }
     561             : 
     562             : /************************************************************************/
     563             : /*                       NotifyChildrenOfRenaming()                     */
     564             : /************************************************************************/
     565             : 
     566          10 : void ZarrGroupBase::NotifyChildrenOfRenaming()
     567             : {
     568          14 :     for (const auto &oIter : m_oMapGroups)
     569           4 :         oIter.second->ParentRenamed(m_osFullName);
     570             : 
     571          19 :     for (const auto &oIter : m_oMapMDArrays)
     572           9 :         oIter.second->ParentRenamed(m_osFullName);
     573             : 
     574          10 :     m_oAttrGroup.ParentRenamed(m_osFullName);
     575             : 
     576          16 :     for (const auto &oIter : m_oMapDimensions)
     577           6 :         oIter.second->ParentRenamed(m_osFullName);
     578          10 : }

Generated by: LCOV version 1.14