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 "icechunkrepo.h"
14 : #include "icechunkmanifest.h"
15 : #include "icechunksnapshot.h"
16 : #include "icechunkutils.h"
17 : #include "icechunkdrivercore.h"
18 :
19 : #include "cpl_json.h"
20 : #include "cpl_mem_cache.h"
21 :
22 : #include <cinttypes>
23 : #include <limits>
24 : #include <mutex>
25 :
26 : /* ------------------------------------------------------------------------- */
27 :
28 : #if defined(__GNUC__)
29 : #pragma GCC diagnostic push
30 : #pragma GCC diagnostic ignored "-Weffc++"
31 : #endif
32 :
33 : #if defined(__clang__)
34 : #pragma clang diagnostic push
35 : #pragma clang diagnostic ignored "-Wweak-vtables"
36 : #endif
37 :
38 : #include "generated/repo_generated.h"
39 :
40 : #if defined(__clang__)
41 : #pragma clang diagnostic pop
42 : #endif
43 :
44 : #if defined(__GNUC__)
45 : #pragma GCC diagnostic pop
46 : #endif
47 :
48 : /* ------------------------------------------------------------------------- */
49 :
50 : using namespace flatbuffers;
51 : using namespace generated;
52 :
53 : namespace gdal::icechunk
54 : {
55 : IcechunkFile::IcechunkFile() = default;
56 :
57 : IcechunkFile::~IcechunkFile() = default;
58 :
59 : IcechunkRepo::IcechunkRepo() = default;
60 :
61 : IcechunkRepo::~IcechunkRepo() = default;
62 :
63 : /************************************************************************/
64 : /* IcechunkRepo::OpenV1() */
65 : /************************************************************************/
66 :
67 : /* static */
68 13 : std::unique_ptr<IcechunkRepo> IcechunkRepo::OpenV1(const char *pszRootPath)
69 : {
70 13 : auto repo = std::make_unique<IcechunkRepo>();
71 13 : repo->m_osRootPath = pszRootPath;
72 :
73 : const std::string osRefsDir =
74 26 : CPLFormFilenameSafe(pszRootPath, "refs", nullptr);
75 26 : const CPLStringList aosRefs(VSIReadDir(osRefsDir.c_str()));
76 63 : for (const char *pszRef : aosRefs)
77 : {
78 50 : if (strcmp(pszRef, ".") != 0 && strcmp(pszRef, "..") != 0)
79 : {
80 : const std::string osRefDir =
81 48 : CPLFormFilenameSafe(osRefsDir.c_str(), pszRef, nullptr);
82 24 : if (STARTS_WITH(pszRef, "branch."))
83 : {
84 26 : CPLJSONDocument oDoc;
85 13 : if (oDoc.Load(CPLFormFilenameSafe(osRefDir.c_str(), "ref.json",
86 : nullptr)))
87 : {
88 : const auto osSnapshotId =
89 26 : oDoc.GetRoot().GetString("snapshot");
90 13 : repo->m_oMapBranchNameToSnapshotId[pszRef +
91 26 : strlen("branch.")] =
92 13 : osSnapshotId;
93 : }
94 : }
95 11 : else if (STARTS_WITH(pszRef, "tag."))
96 : {
97 22 : CPLJSONDocument oDoc;
98 11 : if (oDoc.Load(CPLFormFilenameSafe(osRefDir.c_str(), "ref.json",
99 : nullptr)))
100 : {
101 : const auto osSnapshotId =
102 22 : oDoc.GetRoot().GetString("snapshot");
103 22 : repo->m_oMapTagNameToSnapshotId[pszRef + strlen("tag.")] =
104 11 : osSnapshotId;
105 : }
106 : }
107 : }
108 : }
109 :
110 26 : return repo;
111 : }
112 :
113 : /************************************************************************/
114 : /* ProcessConfig() */
115 : /************************************************************************/
116 :
117 2104 : static void ProcessConfig(const CPLJSONObject &oConfig)
118 : {
119 6312 : const auto oVCC = oConfig["virtual_chunk_containers"];
120 2104 : if (oVCC.GetType() == CPLJSONObject::Type::Object)
121 : {
122 4196 : for (const auto &oVCCChild : oVCC.GetChildren())
123 : {
124 6294 : const auto osURLPrefix = oVCCChild.GetString("url_prefix");
125 6294 : const auto oStore = oVCCChild["store"];
126 4196 : if (cpl::starts_with(osURLPrefix, "s3://") &&
127 2098 : oStore.GetType() == CPLJSONObject::Type::Object)
128 : {
129 6294 : const auto oS3 = oStore["s3"];
130 2098 : if (oS3.GetType() == CPLJSONObject::Type::Object)
131 : {
132 4196 : std::string osPath("/vsis3/");
133 2098 : osPath += osURLPrefix.substr(strlen("s3://"));
134 :
135 6294 : const std::string osRegion = oS3.GetString("region");
136 2098 : if (!osRegion.empty())
137 : {
138 2098 : CPLDebug("Icechunk", "Set AWS_DEFAULT_REGION=%s for %s",
139 : osRegion.c_str(), osPath.c_str());
140 2098 : VSISetPathSpecificOption(osPath.c_str(),
141 : "AWS_DEFAULT_REGION",
142 : osRegion.c_str());
143 : }
144 :
145 2098 : const bool bAnonymous = oS3.GetBool("anonymous");
146 2098 : if (bAnonymous)
147 : {
148 2098 : CPLDebug("Icechunk",
149 : "Set AWS_NO_SIGN_REQUEST=YES for %s",
150 : osPath.c_str());
151 2098 : VSISetPathSpecificOption(osPath.c_str(),
152 : "AWS_NO_SIGN_REQUEST", "YES");
153 : }
154 :
155 2098 : const bool bRequesterPays = oS3.GetBool("requester_pays");
156 2098 : if (bRequesterPays &&
157 0 : !CPLTestBool(VSIGetPathSpecificOption(
158 : osPath.c_str(), "AWS_REQUEST_PAYER", "NO")))
159 : {
160 0 : CPLError(CE_Warning, CPLE_AppDefined,
161 : "AWS_REQUEST_PAYER=YES must be set to "
162 : "access %s",
163 : osPath.c_str());
164 : }
165 : }
166 : }
167 0 : else if ((cpl::starts_with(osURLPrefix, "gs://") ||
168 0 : cpl::starts_with(osURLPrefix, "gcs://")) &&
169 0 : oStore.GetType() == CPLJSONObject::Type::Object)
170 : {
171 0 : const auto oGCS = oStore["gcs"];
172 0 : if (oGCS.GetType() == CPLJSONObject::Type::Object)
173 : {
174 0 : std::string osPath("/vsigs/");
175 0 : osPath += osURLPrefix.substr(osURLPrefix.find("://") + 3);
176 :
177 0 : const auto bAnonymous = oGCS.GetBool("anonymous");
178 0 : if (bAnonymous)
179 : {
180 0 : CPLDebug("Icechunk",
181 : "Set GS_NO_SIGN_REQUEST=YES for %s",
182 : osPath.c_str());
183 0 : VSISetPathSpecificOption(osPath.c_str(),
184 : "GS_NO_SIGN_REQUEST", "YES");
185 : }
186 : }
187 : }
188 : }
189 : }
190 2104 : }
191 :
192 : /************************************************************************/
193 : /* IcechunkRepo::Open() */
194 : /************************************************************************/
195 :
196 : /** Open a "repo" file */
197 : /* static */
198 4183 : std::unique_ptr<IcechunkRepo> IcechunkRepo::Open(const char *pszFilename,
199 : VSIVirtualHandle *fp)
200 : {
201 4183 : CPLDebugOnly("Icechunk", "Opening repo %s", pszFilename);
202 8366 : std::string osFilename(pszFilename);
203 4183 : VSIVirtualHandleUniquePtr tmpFp;
204 4183 : if (!fp)
205 : {
206 4157 : for (int iAttempt = 1; iAttempt <= 2; ++iAttempt)
207 : {
208 8312 : if (iAttempt == 2 ||
209 4155 : strcmp(CPLGetFilename(pszFilename), "repo") != 0)
210 : {
211 : osFilename =
212 4155 : CPLFormFilenameSafe(osFilename.c_str(), "repo", nullptr);
213 : tmpFp =
214 4155 : VSIFilesystemHandler::OpenStatic(osFilename.c_str(), "rb");
215 : // For network file systems, try to read one byte
216 4155 : char chDummy = 1;
217 4155 : if (tmpFp && tmpFp->Read(&chDummy, 1) != 1)
218 : {
219 0 : tmpFp.reset();
220 : }
221 :
222 4155 : if (tmpFp)
223 : {
224 4139 : CPL_IGNORE_RET_VAL(tmpFp->Seek(0, SEEK_SET));
225 4139 : pszFilename = osFilename.c_str();
226 : }
227 : else
228 : {
229 : VSIStatBufL sStat;
230 16 : if (VSIStatL(
231 32 : CPLFormFilenameSafe(pszFilename, "refs", nullptr)
232 : .c_str(),
233 16 : &sStat) == 0)
234 : {
235 : // Icechunk v1
236 13 : return OpenV1(pszFilename);
237 : }
238 : }
239 4142 : break;
240 : }
241 : else
242 : {
243 2 : tmpFp = VSIFilesystemHandler::OpenStatic(pszFilename, "rb");
244 : // For network file systems, try to read one byte
245 2 : char chDummy = 1;
246 2 : if (!tmpFp || tmpFp->Read(&chDummy, 1) != 1)
247 : {
248 2 : tmpFp.reset();
249 : // This might be a repository named "repo". Retry with
250 : // that assumption
251 : }
252 : else
253 : {
254 0 : if (tmpFp)
255 0 : CPL_IGNORE_RET_VAL(tmpFp->Seek(0, SEEK_SET));
256 0 : break;
257 : }
258 : }
259 : }
260 :
261 4142 : fp = tmpFp.get();
262 4142 : if (!fp)
263 : {
264 3 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszFilename);
265 3 : return nullptr;
266 : }
267 : }
268 :
269 4167 : int nFileVersion = 0;
270 4167 : auto [buffer, size] =
271 8334 : DecompressFile(pszFilename, fp, FILE_TYPE_REPO_INFO, &nFileVersion);
272 4167 : if (!buffer)
273 2 : return nullptr;
274 :
275 : {
276 4165 : Verifier verifier(buffer.get(), size);
277 4165 : if (!VerifyRepoBuffer(verifier))
278 : {
279 3 : CPLError(CE_Failure, CPLE_AppDefined, "%s: invalid Repo Flatbuffer",
280 : pszFilename);
281 3 : return nullptr;
282 : }
283 : }
284 :
285 8324 : auto repo = std::make_unique<IcechunkRepo>();
286 4162 : repo->m_osRootPath = CPLGetPathSafe(pszFilename);
287 :
288 4162 : const Repo *repoPtr = GetRepo(buffer.get());
289 :
290 4162 : const int nSpecVersion = repoPtr->spec_version();
291 4162 : CPLDebugOnly("Icechunk", "Repo spec_version = %d", nSpecVersion);
292 4162 : if (nSpecVersion != 1 && nSpecVersion != 2)
293 : {
294 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: invalid spec_version %d",
295 : pszFilename, nSpecVersion);
296 1 : return nullptr;
297 : }
298 4161 : if (nFileVersion != nSpecVersion)
299 : {
300 1 : CPLError(CE_Failure, CPLE_AppDefined,
301 : "%s: file version=%d != spec_version=%d", pszFilename,
302 : nFileVersion, nSpecVersion);
303 1 : return nullptr;
304 : }
305 :
306 4160 : const auto status = repoPtr->status();
307 4160 : if (status)
308 : {
309 4160 : const auto availability = status->availability();
310 4160 : if (availability == RepoAvailability::Offline)
311 : {
312 1 : const auto reason = status->limited_availability_reason();
313 1 : CPLError(CE_Failure, CPLE_AppDefined,
314 : "%s: repository is offline: %s", pszFilename,
315 0 : reason ? reason->c_str() : "unknown reason");
316 1 : return nullptr;
317 : }
318 : }
319 :
320 : {
321 4159 : const auto metadata = repoPtr->metadata();
322 4159 : if (metadata && nSpecVersion == 2)
323 : {
324 4087 : for (const auto &md : *metadata)
325 : {
326 0 : if (const auto value = md->value())
327 : {
328 0 : if (!flexbuffers::VerifyBuffer(value->data(),
329 0 : value->size()))
330 : {
331 0 : CPLError(CE_Failure, CPLE_AppDefined,
332 : "%s: flexbuffers::VerifyBuffer() failed",
333 : pszFilename);
334 0 : return nullptr;
335 : }
336 : if constexpr (IS_DEBUG_BUILD)
337 : {
338 0 : std::string val;
339 0 : flexbuffers::GetRoot(value->data(), value->size())
340 0 : .ToString(true, true, val);
341 0 : CPLDebugOnly("Icechunk", "metadata %s=%s",
342 : md->name() ? md->name()->c_str()
343 : : "(null)",
344 : val.c_str());
345 : }
346 : }
347 : }
348 : }
349 : }
350 :
351 4159 : if (const auto config = repoPtr->config())
352 : {
353 2104 : if (!flexbuffers::VerifyBuffer(config->data(), config->size()))
354 : {
355 0 : CPLError(CE_Failure, CPLE_AppDefined,
356 : "%s: flexbuffers::VerifyBuffer() failed", pszFilename);
357 0 : return nullptr;
358 : }
359 2104 : std::string val;
360 2104 : flexbuffers::GetRoot(config->data(), config->size())
361 2104 : .ToString(true, true, val);
362 2104 : CPLDebugOnly("Icechunk", "config %s", val.c_str());
363 2104 : CPLJSONDocument oDoc;
364 2104 : if (!oDoc.LoadMemory(val))
365 : {
366 0 : CPLError(CE_Failure, CPLE_AppDefined,
367 : "%s: invalid configuration: %s", pszFilename, val.c_str());
368 0 : return nullptr;
369 : }
370 2104 : ProcessConfig(oDoc.GetRoot());
371 : }
372 :
373 4159 : const auto snapshots = repoPtr->snapshots();
374 4159 : CPLAssertAlways(snapshots); // guaranteed by VerifyRepoBuffer()
375 13228 : for (uint32_t snapshotIdx = 0; snapshotIdx < snapshots->size();
376 : ++snapshotIdx)
377 : {
378 9070 : const auto *snapshot = (*snapshots)[snapshotIdx];
379 9070 : const auto *id = snapshot->id();
380 9070 : CPLAssertNotNull(id); // guaranteed by VerifyRepoBuffer()
381 9070 : CPLAssertNotNull(id->bytes()); // guaranteed by VerifyRepoBuffer()
382 :
383 9070 : const auto *message = snapshot->message();
384 9070 : CPLAssertNotNull(message); // guaranteed by VerifyRepoBuffer()
385 :
386 9070 : CPLDebugOnly("Icechunk",
387 : "snapshot '%s', parent_offset %d, message '%s'",
388 : CrockfordBase32Encode(*(id->bytes())).c_str(),
389 : snapshot->parent_offset(), message->c_str());
390 :
391 9070 : const auto metadata = snapshot->metadata();
392 9070 : if (metadata && nSpecVersion == 2)
393 : {
394 13917 : for (const auto &md : *metadata)
395 : {
396 4917 : if (const auto value = md->value())
397 : {
398 4917 : if (!flexbuffers::VerifyBuffer(value->data(),
399 4917 : value->size()))
400 : {
401 1 : CPLError(CE_Failure, CPLE_AppDefined,
402 : "%s: flexbuffers::VerifyBuffer() failed",
403 : pszFilename);
404 1 : return nullptr;
405 : }
406 : if constexpr (IS_DEBUG_BUILD)
407 : {
408 9832 : std::string val;
409 4916 : flexbuffers::GetRoot(value->data(), value->size())
410 4916 : .ToString(true, true, val);
411 4916 : CPLDebugOnly("Icechunk", " metadata %s=%s",
412 : md->name() ? md->name()->c_str()
413 : : "(null)",
414 : val.c_str());
415 : }
416 : }
417 : }
418 : }
419 : }
420 :
421 : // Parse tags
422 4158 : const auto tags = repoPtr->tags();
423 4158 : CPLAssertAlways(tags); // guaranteed by VerifyRepoBuffer()
424 4161 : for (const auto &ref : *tags)
425 : {
426 5 : const auto *refName = ref->name();
427 5 : CPLAssertNotNull(refName); // guaranteed by VerifyRepoBuffer()
428 5 : CPLDebugOnly("Icechunk", "tag '%s', snapshot_index %u",
429 : refName->c_str(), ref->snapshot_index());
430 :
431 5 : if (ref->snapshot_index() >= snapshots->size())
432 : {
433 1 : CPLError(CE_Failure, CPLE_AppDefined,
434 : "%s: tag '%s', invalid snapshot_index %u", pszFilename,
435 : refName->c_str(), ref->snapshot_index());
436 2 : return nullptr;
437 : }
438 :
439 4 : const auto *snapshot = (*snapshots)[ref->snapshot_index()];
440 4 : const auto *id = snapshot->id();
441 4 : CPLAssertNotNull(id); // guaranteed by VerifyRepoBuffer()
442 4 : if (!repo->m_oMapTagNameToSnapshotId
443 4 : .insert({GetString(refName),
444 8 : CrockfordBase32Encode(*(id->bytes()))})
445 4 : .second)
446 : {
447 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: more than one tag '%s'",
448 : pszFilename, refName->c_str());
449 1 : return nullptr;
450 : }
451 : }
452 :
453 : // Parse branches
454 4156 : const auto branches = repoPtr->branches();
455 4156 : CPLAssertAlways(branches); // guaranteed by VerifyRepoBuffer()
456 8310 : for (const auto &ref : *branches)
457 : {
458 4156 : const auto *refName = ref->name();
459 4156 : CPLAssertNotNull(refName); // guaranteed by VerifyRepoBuffer()
460 4156 : CPLDebugOnly("Icechunk", "branch '%s', snapshot_index %u",
461 : refName->c_str(), ref->snapshot_index());
462 :
463 4156 : if (ref->snapshot_index() >= snapshots->size())
464 : {
465 1 : CPLError(CE_Failure, CPLE_AppDefined,
466 : "%s: branch '%s', invalid snapshot_index %u", pszFilename,
467 : refName->c_str(), ref->snapshot_index());
468 2 : return nullptr;
469 : }
470 :
471 4155 : const auto *snapshot = (*snapshots)[ref->snapshot_index()];
472 4155 : const auto *id = snapshot->id();
473 4155 : CPLAssertNotNull(id); // guaranteed by VerifyRepoBuffer()
474 4155 : if (!repo->m_oMapBranchNameToSnapshotId
475 4155 : .insert({GetString(refName),
476 8310 : CrockfordBase32Encode(*(id->bytes()))})
477 4155 : .second)
478 : {
479 1 : CPLError(CE_Failure, CPLE_AppDefined,
480 : "%s: more than one branch '%s'", pszFilename,
481 : refName->c_str());
482 1 : return nullptr;
483 : }
484 : }
485 :
486 4154 : return repo;
487 : }
488 :
489 : /************************************************************************/
490 : /* IcechunkRepo::OpenSnapshotOnBranch() */
491 : /************************************************************************/
492 :
493 : /** Open the snapshot corresponding to the passed branch name. */
494 : std::unique_ptr<IcechunkSnapshot>
495 4158 : IcechunkRepo::OpenSnapshotOnBranch(const std::string &name,
496 : bool emitErrorIfUnknownBranch) const
497 : {
498 4158 : const auto oIter = m_oMapBranchNameToSnapshotId.find(name);
499 4158 : if (oIter == m_oMapBranchNameToSnapshotId.end())
500 : {
501 1 : if (emitErrorIfUnknownBranch)
502 0 : CPLError(CE_Failure, CPLE_AppDefined, "No branch '%s'",
503 : name.c_str());
504 1 : return nullptr;
505 : }
506 4157 : const auto &snapshotId = oIter->second;
507 :
508 : std::string osSnapshotFilename = CPLFormFilenameSafe(
509 4157 : CPLFormFilenameSafe(m_osRootPath.c_str(), "snapshots", nullptr).c_str(),
510 12471 : snapshotId.c_str(), nullptr);
511 4157 : return IcechunkSnapshot::Open(osSnapshotFilename.c_str());
512 : }
513 :
514 : /************************************************************************/
515 : /* IcechunkRepo::OpenSnapshotOnTag() */
516 : /************************************************************************/
517 :
518 : /** Open the snapshot corresponding to the passed tag name. */
519 : std::unique_ptr<IcechunkSnapshot>
520 6 : IcechunkRepo::OpenSnapshotOnTag(const std::string &name,
521 : bool emitErrorIfUnknownTag) const
522 : {
523 6 : const auto oIter = m_oMapTagNameToSnapshotId.find(name);
524 6 : if (oIter == m_oMapTagNameToSnapshotId.end())
525 : {
526 1 : if (emitErrorIfUnknownTag)
527 0 : CPLError(CE_Failure, CPLE_AppDefined, "No tag '%s'", name.c_str());
528 1 : return nullptr;
529 : }
530 5 : const auto &snapshotId = oIter->second;
531 :
532 : std::string osSnapshotFilename = CPLFormFilenameSafe(
533 5 : CPLFormFilenameSafe(m_osRootPath.c_str(), "snapshots", nullptr).c_str(),
534 15 : snapshotId.c_str(), nullptr);
535 5 : return IcechunkSnapshot::Open(osSnapshotFilename.c_str());
536 : }
537 :
538 : /************************************************************************/
539 : /* GetRepoCache() */
540 : /************************************************************************/
541 :
542 : using CacheType =
543 : lru11::Cache<std::string, std::shared_ptr<IcechunkManifest>, std::mutex>;
544 :
545 9113 : static CacheType &GetRepoCache()
546 : {
547 : static lru11::Cache<std::string, std::shared_ptr<IcechunkManifest>,
548 : std::mutex>
549 9113 : goCache;
550 9113 : return goCache;
551 : }
552 :
553 : /************************************************************************/
554 : /* IcechunkRepo::OpenManifest() */
555 : /************************************************************************/
556 :
557 : /** Open the manifest corresponding to the passed manifest id. */
558 : std::shared_ptr<IcechunkManifest>
559 8107 : IcechunkRepo::OpenManifest(const std::string &manifestId,
560 : uint64_t nExpectedFileSize,
561 : uint32_t nExpectedChunkRefs) const
562 : {
563 8107 : CacheType &goCache = GetRepoCache();
564 8107 : std::shared_ptr<IcechunkManifest> manifest;
565 : const std::string cacheKey =
566 16214 : std::string(m_osRootPath).append("|").append(manifestId);
567 8107 : if (goCache.tryGet(cacheKey, manifest))
568 4491 : return manifest;
569 :
570 : std::string osFilename = CPLFormFilenameSafe(
571 3616 : CPLFormFilenameSafe(m_osRootPath.c_str(), "manifests", nullptr).c_str(),
572 10848 : manifestId.c_str(), nullptr);
573 3616 : manifest = IcechunkManifest::Open(osFilename.c_str());
574 3616 : if (manifest)
575 : {
576 : VSIStatBufL sStat;
577 7204 : if (VSIStatL(manifest->GetFilename().c_str(), &sStat) == 0 &&
578 3602 : static_cast<uint64_t>(sStat.st_size) != nExpectedFileSize)
579 : {
580 0 : CPLError(CE_Failure, CPLE_AppDefined,
581 : "Actual file size of manifest %s = %" PRIu64
582 : " does not match expected one = %" PRIu64,
583 0 : osFilename.c_str(), static_cast<uint64_t>(sStat.st_size),
584 : nExpectedFileSize);
585 0 : manifest.reset();
586 : }
587 3602 : else if (manifest->GetChunkRefsCount() != nExpectedChunkRefs)
588 : {
589 1 : CPLError(CE_Failure, CPLE_AppDefined,
590 : "Actual count of chunk references in manifest %s = %u "
591 : "does not match expected one = %u",
592 : osFilename.c_str(), manifest->GetChunkRefsCount(),
593 : nExpectedChunkRefs);
594 1 : manifest.reset();
595 : }
596 : else
597 : {
598 3601 : goCache.insert(cacheKey, manifest);
599 : }
600 : }
601 3616 : return manifest;
602 : }
603 :
604 : /************************************************************************/
605 : /* IcechunkRepo::ClearCaches() */
606 : /************************************************************************/
607 :
608 1006 : /* static */ void IcechunkRepo::ClearCaches()
609 : {
610 1006 : CacheType &goCache = GetRepoCache();
611 1006 : goCache.clear();
612 1006 : }
613 :
614 : } // namespace gdal::icechunk
|