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

Generated by: LCOV version 1.14