LCOV - code coverage report
Current view: top level - frmts/icechunk - icechunkdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 160 167 95.8 %
Date: 2026-06-19 21:24:00 Functions: 15 16 93.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Icechunk driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "icechunkdrivercore.h"
      14             : 
      15             : #include "cpl_json.h"
      16             : #include "cpl_time.h"
      17             : 
      18             : #include "gdalalgorithm.h"
      19             : #include "gdal_frmts.h"
      20             : #include "gdal_priv.h"
      21             : 
      22             : #include "ogr_p.h"
      23             : 
      24             : #include "icechunkrepo.h"
      25             : #include "icechunksnapshot.h"
      26             : #include "icechunkutils.h"
      27             : 
      28             : #ifndef _
      29             : #define _(x) (x)
      30             : #endif
      31             : 
      32             : namespace gdal::icechunk
      33             : {
      34             : /************************************************************************/
      35             : /*                            DatasetOpen()                             */
      36             : /************************************************************************/
      37             : 
      38          85 : static GDALDataset *DatasetOpen(GDALOpenInfo *poOpenInfo)
      39             : {
      40          85 :     if (!IcechunkDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
      41           0 :         return nullptr;
      42             : 
      43          85 :     std::unique_ptr<GDALOpenInfo> poTmpOpenInfo;  // keep in that scope
      44         170 :     std::string osBranchName;
      45         170 :     std::string osTagName;
      46             :     std::string osFilename = GetFilenameFromDatasetName(
      47         255 :         poOpenInfo->pszFilename, osBranchName, osTagName);
      48          85 :     if (osFilename.empty())
      49           1 :         return nullptr;  // Error emitted by GetFilenameFromDatasetName
      50          84 :     if (osFilename != poOpenInfo->pszFilename)
      51             :     {
      52             :         poTmpOpenInfo =
      53           7 :             std::make_unique<GDALOpenInfo>(osFilename.c_str(), GA_ReadOnly);
      54           7 :         poTmpOpenInfo->nOpenFlags = poOpenInfo->nOpenFlags;
      55           7 :         poOpenInfo = poTmpOpenInfo.get();
      56             :     }
      57             : 
      58          84 :     auto repo = IcechunkRepo::Open(poOpenInfo->pszFilename,
      59          84 :                                    poOpenInfo->bIsDirectory ? nullptr
      60         168 :                                                             : poOpenInfo->fpL);
      61          84 :     if (!repo)
      62          13 :         return nullptr;
      63             : 
      64             :     class DummyDataset : public GDALDataset
      65             :     {
      66             :       public:
      67           6 :         DummyDataset()
      68           6 :         {
      69           6 :             nRasterXSize = 0;
      70           6 :             nRasterYSize = 0;
      71           6 :         }
      72             : 
      73           1 :         std::shared_ptr<GDALGroup> GetRootGroup() const override
      74             :         {
      75             :             class DummyGroup : public GDALGroup
      76             :             {
      77             :               public:
      78           1 :                 DummyGroup() : GDALGroup(std::string(), "/")
      79             :                 {
      80           1 :                 }
      81             :             };
      82             : 
      83           1 :             return std::make_shared<DummyGroup>();
      84             :         }
      85             :     };
      86             : 
      87             :     const auto ConcatBranchOrTagNames =
      88           3 :         [](const std::map<std::string, std::string> &mapNameToSnapshotId)
      89             :     {
      90           3 :         std::string s;
      91           6 :         for (const auto &[name, _] : mapNameToSnapshotId)
      92             :         {
      93           3 :             if (!s.empty())
      94           0 :                 s += ", ";
      95           3 :             s += '"';
      96           3 :             s += name;
      97           3 :             s += '"';
      98             :         }
      99           3 :         return s;
     100             :     };
     101             : 
     102          71 :     std::unique_ptr<IcechunkSnapshot> snapshot;
     103          71 :     if (osTagName.empty())
     104             :     {
     105          69 :         if (osBranchName.empty())
     106             :         {
     107          67 :             const auto branches = repo->GetBranches();
     108          67 :             if (branches.empty())
     109             :             {
     110           1 :                 return std::make_unique<DummyDataset>().release();
     111             :             }
     112          66 :             else if (branches.find("main") != branches.end())
     113             :             {
     114          65 :                 osBranchName = "main";
     115             :             }
     116             :             else
     117             :             {
     118           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     119             :                          "You need to specify a branch name among %s",
     120           2 :                          ConcatBranchOrTagNames(repo->GetBranches()).c_str());
     121           1 :                 return nullptr;
     122             :             }
     123             :         }
     124             : 
     125          67 :         const auto nErrorCount = CPLGetErrorCounter();
     126          67 :         snapshot = repo->OpenSnapshotOnBranch(osBranchName, false);
     127          67 :         if (!snapshot)
     128             :         {
     129          22 :             if (nErrorCount == CPLGetErrorCounter())
     130             :             {
     131           1 :                 CPLError(
     132             :                     CE_Failure, CPLE_AppDefined,
     133             :                     "Invalid branch name \"%s\". Valid branch names are: %s",
     134             :                     osBranchName.c_str(),
     135           2 :                     ConcatBranchOrTagNames(repo->GetBranches()).c_str());
     136             :             }
     137          22 :             return nullptr;
     138             :         }
     139             :     }
     140             :     else
     141             :     {
     142           2 :         const auto nErrorCount = CPLGetErrorCounter();
     143           2 :         snapshot = repo->OpenSnapshotOnTag(osTagName, false);
     144           2 :         if (!snapshot)
     145             :         {
     146           1 :             if (nErrorCount == CPLGetErrorCounter())
     147             :             {
     148           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     149             :                          "Invalid tag name \"%s\". Valid tag names are: %s",
     150             :                          osTagName.c_str(),
     151           2 :                          ConcatBranchOrTagNames(repo->GetTags()).c_str());
     152             :             }
     153           1 :             return nullptr;
     154             :         }
     155             :     }
     156             : 
     157          46 :     if (snapshot->GetNodeCount() <= 1)
     158             :     {
     159           5 :         return std::make_unique<DummyDataset>().release();
     160             :     }
     161             : 
     162          41 :     auto poZarrDriver = GetGDALDriverManager()->GetDriverByName("ZARR");
     163          41 :     if (!poZarrDriver)
     164             :     {
     165           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     166             :                  "Cannot open Icechunk dataset due to missing Zarr driver");
     167           0 :         return nullptr;
     168             :     }
     169          41 :     const auto pfnOpen = poZarrDriver->GetOpenCallback();
     170          41 :     if (!pfnOpen)
     171             :     {
     172             :         // Cannot happen if using official GDAL Zarr driver!
     173           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     174             :                  "Cannot open Icechunk dataset due to missing Open() method in "
     175             :                  "Zarr driver");
     176           0 :         return nullptr;
     177             :     }
     178             : 
     179             :     const std::string osVSIIcechunkFilename =
     180         123 :         std::string("ZARR:\"/vsiicechunk/{").append(osFilename).append("}\"");
     181          82 :     GDALOpenInfo oOpenInfoZarr(osVSIIcechunkFilename.c_str(), GA_ReadOnly);
     182          41 :     oOpenInfoZarr.nOpenFlags = poOpenInfo->nOpenFlags;
     183          41 :     oOpenInfoZarr.papszOpenOptions = poOpenInfo->papszOpenOptions;
     184             :     // cppcheck-suppress returnDanglingLifetime
     185          41 :     return pfnOpen(&oOpenInfoZarr);
     186             : }
     187             : 
     188             : /************************************************************************/
     189             : /*                            ClearCaches()                             */
     190             : /************************************************************************/
     191             : 
     192        1006 : static void ClearCaches(GDALDriver *)
     193             : {
     194        1006 :     gdal::icechunk::IcechunkRepo::ClearCaches();
     195        1006 :     VSIIcechunkFileSystemClearCaches();
     196        1006 : }
     197             : 
     198             : /************************************************************************/
     199             : /*                    TimestampInMicrosecToISO8211()                    */
     200             : /************************************************************************/
     201             : 
     202           3 : static std::string TimestampInMicrosecToISO8211(uint64_t nTimestamp)
     203             : {
     204             :     struct tm brokendown;
     205           3 :     constexpr int MICROSECONDS_IN_SEC = 1000 * 1000;
     206           3 :     CPLUnixTimeToYMDHMS(nTimestamp / MICROSECONDS_IN_SEC, &brokendown);
     207             :     OGRField sField;
     208           3 :     sField.Date.Year = static_cast<GInt16>(brokendown.tm_year + 1900);
     209           3 :     sField.Date.Month = static_cast<GByte>(brokendown.tm_mon + 1);
     210           3 :     sField.Date.Day = static_cast<GByte>(brokendown.tm_mday);
     211           3 :     sField.Date.Hour = static_cast<GByte>(brokendown.tm_hour);
     212           3 :     sField.Date.Minute = static_cast<GByte>(brokendown.tm_min);
     213           3 :     sField.Date.Second = static_cast<float>(
     214           3 :         brokendown.tm_sec + (nTimestamp % MICROSECONDS_IN_SEC) /
     215             :                                 static_cast<float>(MICROSECONDS_IN_SEC));
     216           3 :     sField.Date.TZFlag = OGR_TZFLAG_UTC;
     217             :     std::unique_ptr<char, VSIFreeReleaser> pszDateTime(
     218           3 :         OGRGetXMLDateTime(&sField, /* bAlwaysMillisecond = */ false));
     219           6 :     return pszDateTime.get();
     220             : }
     221             : 
     222             : /************************************************************************/
     223             : /*                          ListRefsAlgorithm                           */
     224             : /************************************************************************/
     225             : 
     226         282 : class ListRefsAlgorithm /* non-final */ : public GDALAlgorithm
     227             : {
     228             :   public:
     229             :     ~ListRefsAlgorithm() override;
     230             : 
     231             :   protected:
     232         282 :     ListRefsAlgorithm(const std::string &osName,
     233             :                       const std::string &osDescription,
     234             :                       const std::string &osHelpURL)
     235         282 :         : GDALAlgorithm(osName, osDescription, osHelpURL)
     236             :     {
     237         282 :         AddInputDatasetArg(&m_dataset, GDAL_OF_MULTIDIM_RASTER);
     238         282 :         AddOutputStringArg(&m_outputString);
     239         282 :     }
     240             : 
     241             :     GDALArgDatasetValue m_dataset{};
     242             :     std::string m_outputString{};
     243             : };
     244             : 
     245             : ListRefsAlgorithm::~ListRefsAlgorithm() = default;
     246             : 
     247             : /************************************************************************/
     248             : /*                        ListBranchesAlgorithm                         */
     249             : /************************************************************************/
     250             : 
     251             : class ListBranchesAlgorithm final : public ListRefsAlgorithm
     252             : {
     253             :   public:
     254             :     static constexpr const char *NAME = LIST_BRANCHES;
     255             : 
     256         141 :     ListBranchesAlgorithm()
     257         141 :         : ListRefsAlgorithm(
     258         282 :               NAME, std::string("List branches of an Icechunk repository"),
     259         423 :               "/programs/gdal_driver_icechunk_list_branches.html")
     260             :     {
     261         141 :     }
     262             : 
     263             :   protected:
     264             :     bool RunImpl(GDALProgressFunc, void *) override;
     265             : };
     266             : 
     267           3 : bool ListBranchesAlgorithm::RunImpl(GDALProgressFunc, void *)
     268             : {
     269           6 :     std::string osBranchName;
     270           6 :     std::string osTagName;
     271             :     const std::string osFilename = GetFilenameFromDatasetName(
     272           6 :         m_dataset.GetName(), osBranchName, osTagName);
     273           6 :     auto repo = IcechunkRepo::Open(osFilename.c_str());
     274           3 :     if (!repo)
     275           1 :         return false;
     276             : 
     277           2 :     CPLJSONArray oArray;
     278           4 :     for (const auto &[branchName, _] : repo->GetBranches())
     279             :     {
     280           4 :         CPLJSONObject oCommit;
     281           2 :         oCommit.Set("name", branchName);
     282           4 :         auto snapshot = repo->OpenSnapshotOnBranch(branchName);
     283           2 :         if (snapshot)
     284             :         {
     285           2 :             oCommit.Set("commit_message", snapshot->GetCommitMessage());
     286           2 :             if (const uint64_t nTimestamp = snapshot->GetFlushTimestamp())
     287             :             {
     288           2 :                 oCommit.Set("timestamp",
     289           4 :                             TimestampInMicrosecToISO8211(nTimestamp));
     290             :             }
     291             :         }
     292           2 :         oArray.Add(oCommit);
     293             :     }
     294           2 :     m_outputString = oArray.ToString();
     295           2 :     m_outputString += '\n';
     296             : 
     297           2 :     return true;
     298             : }
     299             : 
     300             : /************************************************************************/
     301             : /*                          ListTagsAlgorithm                           */
     302             : /************************************************************************/
     303             : 
     304             : class ListTagsAlgorithm final : public ListRefsAlgorithm
     305             : {
     306             :   public:
     307             :     static constexpr const char *NAME = LIST_TAGS;
     308             : 
     309         141 :     ListTagsAlgorithm()
     310         141 :         : ListRefsAlgorithm(NAME,
     311         282 :                             std::string("List tags of an Icechunk repository"),
     312         423 :                             "/programs/gdal_driver_icechunk_list_tags.html")
     313             :     {
     314         141 :     }
     315             : 
     316             :   protected:
     317             :     bool RunImpl(GDALProgressFunc, void *) override;
     318             : };
     319             : 
     320           3 : bool ListTagsAlgorithm::RunImpl(GDALProgressFunc, void *)
     321             : {
     322           6 :     std::string osBranchName;
     323           6 :     std::string osTagName;
     324             :     const std::string osFilename = GetFilenameFromDatasetName(
     325           6 :         m_dataset.GetName(), osBranchName, osTagName);
     326           6 :     auto repo = IcechunkRepo::Open(osFilename.c_str());
     327           3 :     if (!repo)
     328           1 :         return false;
     329             : 
     330           2 :     CPLJSONArray oArray;
     331           3 :     for (const auto &[tagName, _] : repo->GetTags())
     332             :     {
     333           2 :         CPLJSONObject oCommit;
     334           1 :         oCommit.Set("name", tagName);
     335           2 :         auto snapshot = repo->OpenSnapshotOnTag(tagName);
     336           1 :         if (snapshot)
     337             :         {
     338           1 :             oCommit.Set("commit_message", snapshot->GetCommitMessage());
     339           1 :             if (const uint64_t nTimestamp = snapshot->GetFlushTimestamp())
     340             :             {
     341           1 :                 oCommit.Set("timestamp",
     342           2 :                             TimestampInMicrosecToISO8211(nTimestamp));
     343             :             }
     344             :         }
     345           1 :         oArray.Add(oCommit);
     346             :     }
     347           2 :     m_outputString = oArray.ToString();
     348           2 :     m_outputString += '\n';
     349             : 
     350           2 :     return true;
     351             : }
     352             : 
     353             : /************************************************************************/
     354             : /*                        InstantiateAlgorithm()                        */
     355             : /************************************************************************/
     356             : 
     357             : static GDALAlgorithm *
     358         282 : InstantiateAlgorithm(const std::vector<std::string> &aosPath)
     359             : {
     360         282 :     if (aosPath.size() == 1 && aosPath[0] == ListBranchesAlgorithm::NAME)
     361             :     {
     362         141 :         return std::make_unique<ListBranchesAlgorithm>().release();
     363             :     }
     364         141 :     else if (aosPath.size() == 1 && aosPath[0] == ListTagsAlgorithm::NAME)
     365             :     {
     366         141 :         return std::make_unique<ListTagsAlgorithm>().release();
     367             :     }
     368             :     else
     369             :     {
     370           0 :         return nullptr;
     371             :     }
     372             : }
     373             : 
     374             : }  // namespace gdal::icechunk
     375             : 
     376             : /************************************************************************/
     377             : /*                       GDALRegister_Icechunk()                        */
     378             : /************************************************************************/
     379             : 
     380        2135 : void GDALRegister_Icechunk()
     381             : 
     382             : {
     383        2135 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
     384         263 :         return;
     385             : 
     386        1872 :     gdal::icechunk::VSIInstallIcechunkFileSystem();
     387             : 
     388        3744 :     auto poDriver = std::make_unique<GDALDriver>();
     389        1872 :     IcechunkDriverSetCommonMetadata(poDriver.get());
     390             : 
     391        1872 :     poDriver->pfnOpen = gdal::icechunk::DatasetOpen;
     392        1872 :     poDriver->pfnClearCaches = gdal::icechunk::ClearCaches;
     393        1872 :     poDriver->pfnInstantiateAlgorithm = gdal::icechunk::InstantiateAlgorithm;
     394             : 
     395        1872 :     GetGDALDriverManager()->RegisterDriver(poDriver.release());
     396             : }

Generated by: LCOV version 1.14