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

Generated by: LCOV version 1.14