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 "icechunksnapshot.h"
14 : #include "icechunkutils.h"
15 : #include "icechunkdrivercore.h"
16 :
17 : #include "cpl_vsi_virtual.h"
18 :
19 : #include <algorithm>
20 : #include <cinttypes>
21 : #include <limits>
22 : #if __cplusplus >= 202002L
23 : #include <ranges>
24 : #endif
25 :
26 : /* ------------------------------------------------------------------------- */
27 :
28 : #if defined(__GNUC__)
29 : #pragma GCC diagnostic push
30 : #pragma GCC diagnostic ignored "-Weffc++"
31 : #pragma GCC diagnostic ignored "-Wnull-dereference"
32 : #endif
33 :
34 : #if defined(__clang__)
35 : #pragma clang diagnostic push
36 : #pragma clang diagnostic ignored "-Wweak-vtables"
37 : #endif
38 :
39 : #include "flatbuffers/flexbuffers.h"
40 : #include "generated/snapshot_generated.h"
41 :
42 : #if defined(__clang__)
43 : #pragma clang diagnostic pop
44 : #endif
45 :
46 : #if defined(__GNUC__)
47 : #pragma GCC diagnostic pop
48 : #endif
49 :
50 : /* ------------------------------------------------------------------------- */
51 :
52 : using namespace flatbuffers;
53 : using namespace generated;
54 :
55 : namespace gdal::icechunk
56 : {
57 : IcechunkSnapshot::IcechunkSnapshot() = default;
58 :
59 : IcechunkSnapshot::~IcechunkSnapshot() = default;
60 :
61 : #if defined(__GNUC__)
62 : #pragma GCC diagnostic push
63 : #pragma GCC diagnostic ignored "-Wnull-dereference"
64 : #endif
65 :
66 : /************************************************************************/
67 : /* IcechunkSnapshot::Open() */
68 : /************************************************************************/
69 :
70 : std::unique_ptr<IcechunkSnapshot>
71 4162 : IcechunkSnapshot::Open(const char *pszFilename)
72 : {
73 4162 : CPLDebugOnly("Icechunk", "Opening snapshot %s", pszFilename);
74 8324 : auto fp = VSIFilesystemHandler::OpenStatic(pszFilename, "rb");
75 4162 : if (!fp)
76 : {
77 2 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszFilename);
78 2 : return nullptr;
79 : }
80 :
81 4160 : int nVersion = 0;
82 4160 : auto [buffer, size] =
83 8320 : DecompressFile(pszFilename, fp.get(), FILE_TYPE_SNAPSHOT, &nVersion);
84 4160 : if (!buffer)
85 1 : return nullptr;
86 :
87 : {
88 4159 : Verifier verifier(buffer.get(), size);
89 4159 : if (!VerifySnapshotBuffer(verifier))
90 : {
91 1 : CPLError(CE_Failure, CPLE_AppDefined,
92 : "%s: invalid Snapshot Flatbuffer", pszFilename);
93 1 : return nullptr;
94 : }
95 : }
96 :
97 8316 : auto snapshot = std::make_unique<IcechunkSnapshot>();
98 4158 : snapshot->m_osFilename = pszFilename;
99 :
100 4158 : const auto *fbsSnapshot = GetSnapshot(buffer.get());
101 4158 : const auto *id = fbsSnapshot->id();
102 4158 : CPLAssertNotNull(id); // guaranteed by VerifySnapshotBuffer()
103 4158 : const auto idBytes = id->bytes();
104 4158 : CPLAssertNotNull(idBytes); // guaranteed by VerifySnapshotBuffer()
105 8316 : const std::string snapshotIdBase32 = CrockfordBase32Encode(*idBytes);
106 4158 : if (snapshotIdBase32 != CPLGetFilename(pszFilename))
107 : {
108 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: id=%s != expected %s",
109 : pszFilename, snapshotIdBase32.c_str(),
110 : CPLGetFilename(pszFilename));
111 1 : return nullptr;
112 : }
113 :
114 4157 : const auto *message = fbsSnapshot->message();
115 4157 : CPLAssertNotNull(message); // guaranteed by VerifySnapshotBuffer()
116 4157 : snapshot->m_osCommitMessage = GetString(message);
117 4157 : CPLDebugOnly("Icechunk", "snapshot %s: commit message: '%s'", pszFilename,
118 : snapshot->m_osCommitMessage.c_str());
119 :
120 4157 : snapshot->m_flushTimestamp = fbsSnapshot->flushed_at();
121 :
122 : {
123 4157 : const auto *metadata = fbsSnapshot->metadata();
124 4157 : if (metadata && nVersion == 2)
125 : {
126 4147 : for (const auto &md : *metadata)
127 : {
128 8 : if (const auto value = md->value())
129 : {
130 8 : if (!flexbuffers::VerifyBuffer(value->data(),
131 8 : value->size()))
132 : {
133 0 : CPLError(CE_Failure, CPLE_AppDefined,
134 : "%s: flexbuffers::VerifyBuffer() failed",
135 : pszFilename);
136 0 : return nullptr;
137 : }
138 : if constexpr (IS_DEBUG_BUILD)
139 : {
140 16 : std::string val;
141 8 : flexbuffers::GetRoot(value->data(), value->size())
142 8 : .ToString(true, true, val);
143 8 : CPLDebugOnly("Icechunk", "snapshot %s: metadata %s=%s",
144 : snapshotIdBase32.c_str(),
145 : md->name() ? md->name()->c_str()
146 : : "(null)",
147 : val.c_str());
148 : }
149 : }
150 : }
151 : }
152 : }
153 :
154 : /* --------------------------------------------------------------------*/
155 : /* Parse nodes[] array */
156 : /* --------------------------------------------------------------------*/
157 4157 : const auto *nodes = fbsSnapshot->nodes();
158 4157 : CPLAssertAlways(nodes); // guaranteed by VerifySnapshotBuffer()
159 4157 : snapshot->m_nodes.reserve(nodes->size());
160 4157 : bool needSort = false;
161 12498 : for (const auto *nodePtr : *nodes)
162 : {
163 8354 : const auto *fbsNodeId = nodePtr->id();
164 8354 : CPLAssertNotNull(fbsNodeId); // guaranteed by VerifySnapshotBuffer()
165 8354 : const auto fbsNodeIdBytes = fbsNodeId->bytes();
166 8354 : CPLAssertAlways(
167 : fbsNodeIdBytes); // guaranteed by VerifySnapshotBuffer()
168 :
169 : ObjectId8 nodeId;
170 : static_assert(sizeof(*fbsNodeIdBytes) == sizeof(nodeId));
171 8354 : memcpy(nodeId.data(), fbsNodeIdBytes->data(), sizeof(nodeId));
172 :
173 8354 : const auto *pathPtr = nodePtr->path();
174 8354 : CPLAssertNotNull(pathPtr); // guaranteed by VerifySnapshotBuffer()
175 :
176 8354 : const std::string path = GetString(pathPtr);
177 20925 : if (path.empty() || path[0] != '/' ||
178 12571 : (path.size() > 1 && path.back() == '/'))
179 : {
180 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s: invalid node path '%s'",
181 : pszFilename, path.c_str());
182 1 : return nullptr;
183 : }
184 :
185 : // Additional checks to avoid later issues. Might not be strictly
186 : // necessary, but if removing them, VSIIcechunkFileSystem should be
187 : // reviewed against risks of path traversal.
188 16706 : if (path.find('\\') != std::string::npos ||
189 16706 : path.find("/./") != std::string::npos ||
190 25059 : path.find("/../") != std::string::npos ||
191 16705 : cpl::ends_with(path, "/.."))
192 : {
193 1 : CPLError(CE_Failure, CPLE_AppDefined,
194 : "%s: path traversal pattern in node path '%s'",
195 : pszFilename, path.c_str());
196 1 : return nullptr;
197 : }
198 :
199 12555 : needSort = needSort || (!snapshot->m_nodes.empty() &&
200 4203 : snapshot->m_nodes.back().path > path);
201 :
202 8352 : Node node;
203 8352 : node.id = nodeId;
204 8352 : node.path = path;
205 :
206 8352 : const auto *userData = nodePtr->user_data();
207 8352 : CPLAssertAlways(userData); // guaranteed by VerifySnapshotBuffer()
208 16704 : node.content.assign(reinterpret_cast<const char *>(userData->data()),
209 8352 : userData->size());
210 :
211 8352 : CPLDebugOnly("Icechunk", "snapshot %s, node %s, path %s, user_data %s",
212 : snapshotIdBase32.c_str(),
213 : CrockfordBase32Encode(*(fbsNodeId->bytes())).c_str(),
214 : path.c_str(), node.content.c_str());
215 :
216 8352 : if (const auto arrayData = nodePtr->node_data_as_Array())
217 : {
218 4215 : node.isArray = true;
219 :
220 4215 : uint64_t totalNumChunks = 1;
221 :
222 4215 : if (nVersion == 2)
223 : {
224 4189 : const auto *shape = arrayData->shape_v2();
225 4189 : if (!shape)
226 : {
227 1 : CPLError(CE_Failure, CPLE_AppDefined,
228 : "%s: missing shape_v2 in ArrayData", pszFilename);
229 1 : return nullptr;
230 : }
231 8461 : for (const auto *dimShape : *shape)
232 : {
233 4275 : const uint32_t numChunks = dimShape->num_chunks();
234 4275 : if (numChunks == 0)
235 : {
236 1 : CPLError(CE_Failure, CPLE_AppDefined,
237 : "%s: numChunks == 0", pszFilename);
238 2 : return nullptr;
239 : }
240 4274 : node.numChunks.push_back(numChunks);
241 8548 : if (numChunks >
242 4274 : std::numeric_limits<uint64_t>::max() / totalNumChunks)
243 : {
244 1 : CPLError(CE_Failure, CPLE_AppDefined,
245 : "%s: too many chunks", pszFilename);
246 1 : return nullptr;
247 : }
248 4273 : totalNumChunks *= numChunks;
249 : }
250 : }
251 : else
252 : {
253 26 : const auto *shape = arrayData->shape();
254 26 : CPLAssertAlways(shape); // guaranteed by VerifySnapshotBuffer()
255 42 : for (const auto *dimShape : *shape)
256 : {
257 19 : const uint64_t array_length = dimShape->array_length();
258 19 : const uint64_t chunk_length = dimShape->chunk_length();
259 19 : if (!array_length || !chunk_length)
260 : {
261 2 : CPLError(CE_Failure, CPLE_AppDefined,
262 : "%s: invalid shape in ArrayData", pszFilename);
263 3 : return nullptr;
264 : }
265 : const uint64_t numChunks64 =
266 17 : cpl::div_round_up(array_length, chunk_length);
267 17 : if (numChunks64 > std::numeric_limits<uint32_t>::max())
268 : {
269 1 : CPLError(
270 : CE_Failure, CPLE_AppDefined,
271 : "%s: invalid shape in ArrayData: too many chunks",
272 : pszFilename);
273 1 : return nullptr;
274 : }
275 16 : const uint32_t numChunks =
276 : static_cast<uint32_t>(numChunks64);
277 16 : node.numChunks.push_back(numChunks);
278 32 : if (numChunks >
279 16 : std::numeric_limits<uint64_t>::max() / totalNumChunks)
280 : {
281 0 : CPLError(CE_Failure, CPLE_AppDefined,
282 : "%s: too many chunks", pszFilename);
283 0 : return nullptr;
284 : }
285 16 : totalNumChunks *= numChunks;
286 : }
287 : }
288 :
289 4209 : const auto *manifests = arrayData->manifests();
290 4209 : CPLAssertAlways(manifests); // guaranteed by VerifySnapshotBuffer()
291 :
292 4209 : uint64_t totalNumChunksFromManifests = 0;
293 8420 : for (const auto *manifestPtr : *manifests)
294 : {
295 4215 : const auto manifestId = manifestPtr->object_id();
296 4215 : CPLAssertNotNull(
297 : manifestId); // guaranteed by VerifySnapshotBuffer()
298 4215 : const auto manifestIdBytes = manifestId->bytes();
299 4215 : CPLAssertAlways(
300 : manifestIdBytes); // guaranteed by VerifySnapshotBuffer()
301 :
302 4215 : ManifestRef manifestRef;
303 : static_assert(sizeof(*manifestIdBytes) ==
304 : sizeof(manifestRef.manifestId));
305 4215 : memcpy(manifestRef.manifestId.data(), manifestIdBytes->data(),
306 : sizeof(manifestRef.manifestId));
307 :
308 4215 : const auto extents = manifestPtr->extents();
309 4215 : CPLAssertAlways(
310 : extents); // guaranteed by VerifySnapshotBuffer()
311 :
312 4215 : CPLDebugOnly("Icechunk", "snapshot %s, manifest ref %s:",
313 : snapshotIdBase32.c_str(),
314 : CrockfordBase32Encode(*manifestIdBytes).c_str());
315 :
316 4215 : uint64_t chunkCountFromManifest = 1;
317 4215 : if (node.numChunks.empty() && extents->size() == 1 &&
318 0 : node.numChunks.empty())
319 : {
320 : // Special case for scalar arrays such as "crs" written by Icechunk v0
321 0 : const auto *extent = (*extents)[0];
322 0 : if (extent->from() != 0 || extent->to() != 1)
323 : {
324 0 : CPLError(CE_Failure, CPLE_AppDefined,
325 : "%s: array %s: invalid manifest extent "
326 : "[%u, %u[ for dim %u",
327 : pszFilename, path.c_str(), extent->from(),
328 : extent->to(), 0);
329 0 : return nullptr;
330 : }
331 :
332 0 : manifestRef.extents.emplace_back(extent->from(),
333 0 : extent->to());
334 0 : CPLDebugOnly("Icechunk",
335 : "snapshot %s, from %" PRIu32 " to %" PRIu32,
336 : snapshotIdBase32.c_str(),
337 : manifestRef.extents.back().from,
338 : manifestRef.extents.back().to);
339 : }
340 : else
341 : {
342 4215 : if (node.numChunks.size() != extents->size())
343 : {
344 1 : CPLError(
345 : CE_Failure, CPLE_AppDefined,
346 : "%s: array %s: manifest extents has not expected "
347 : "dimension count. Got %d from manifest extents, "
348 : "expected %d from node shape",
349 : pszFilename, path.c_str(),
350 1 : static_cast<int>(extents->size()),
351 1 : static_cast<int>(node.numChunks.size()));
352 1 : return nullptr;
353 : }
354 8518 : for (unsigned iDim = 0; iDim < extents->size(); ++iDim)
355 : {
356 4307 : const auto *extent = (*extents)[iDim];
357 4307 : if (extent->from() >= extent->to() ||
358 8612 : extent->from() >= node.numChunks[iDim] ||
359 4305 : extent->to() > node.numChunks[iDim])
360 : {
361 3 : CPLError(CE_Failure, CPLE_AppDefined,
362 : "%s: array %s: invalid manifest extent "
363 : "[%u, %u[ for dim %u",
364 : pszFilename, path.c_str(), extent->from(),
365 : extent->to(), iDim);
366 3 : return nullptr;
367 : }
368 :
369 : // Overflow cannot happen given the validation of extent
370 : // w.r.t node.numChunks and the fact that
371 : // times(node.numChunks) has been checked to fit on uint64_t
372 4304 : chunkCountFromManifest *= extent->to() - extent->from();
373 :
374 0 : manifestRef.extents.emplace_back(extent->from(),
375 4304 : extent->to());
376 4304 : CPLDebugOnly("Icechunk",
377 : "snapshot %s, from %" PRIu32
378 : " to %" PRIu32,
379 : snapshotIdBase32.c_str(),
380 : manifestRef.extents.back().from,
381 : manifestRef.extents.back().to);
382 : }
383 : }
384 :
385 4211 : node.manifestRefs.push_back(std::move(manifestRef));
386 :
387 4211 : if (totalNumChunksFromManifests >
388 4211 : std::numeric_limits<uint64_t>::max() -
389 : chunkCountFromManifest)
390 : {
391 : totalNumChunksFromManifests =
392 0 : std::numeric_limits<uint64_t>::max();
393 0 : break;
394 : }
395 4211 : totalNumChunksFromManifests += chunkCountFromManifest;
396 : }
397 :
398 : // Quick partial consistency check
399 4205 : if (totalNumChunksFromManifests > totalNumChunks)
400 : {
401 1 : CPLError(CE_Failure, CPLE_AppDefined,
402 : "%s: array %s: chunks referenced by manifest extents "
403 : "= %" PRIu64 " > chunks in the array = %" PRIu64,
404 : pszFilename, path.c_str(), totalNumChunksFromManifests,
405 : totalNumChunks);
406 1 : return nullptr;
407 : }
408 :
409 : if constexpr (IS_DEBUG_BUILD)
410 : {
411 : // Check that all chunks of this array are referenced at most
412 : // once
413 4204 : ChunkIdx anChunkIdx(node.numChunks.size());
414 4204 : std::string chunkStr;
415 8107960 : for (uint64_t iChunk = 0; iChunk < totalNumChunks; ++iChunk)
416 : {
417 8103760 : chunkStr = '[';
418 32361100 : for (size_t iDim = 0; iDim < node.numChunks.size(); ++iDim)
419 : {
420 24257400 : if (iDim > 0)
421 16153600 : chunkStr += ", ";
422 24257400 : chunkStr += std::to_string(anChunkIdx[iDim]);
423 : }
424 8103760 : chunkStr += ']';
425 :
426 8103760 : size_t counter = node.countManifestIdForChunk(anChunkIdx);
427 8103760 : if (counter > 1)
428 : {
429 0 : CPLError(CE_Failure, CPLE_AppDefined,
430 : "%s: array %s: found more than one manifest "
431 : "ref for chunk %s",
432 : pszFilename, path.c_str(), chunkStr.c_str());
433 0 : return nullptr;
434 : }
435 :
436 8103760 : if (iChunk + 1 < totalNumChunks)
437 : {
438 : // Increment anChunkIdx
439 8109870 : for (size_t iDim = node.numChunks.size(); iDim > 0;
440 : /* */)
441 : {
442 8109870 : --iDim;
443 8109870 : ++anChunkIdx[iDim];
444 8109870 : if (anChunkIdx[iDim] < node.numChunks[iDim])
445 : {
446 8099550 : break;
447 : }
448 10316 : anChunkIdx[iDim] = 0;
449 : }
450 : }
451 : }
452 : }
453 : }
454 :
455 8341 : snapshot->m_nodes.push_back(std::move(node));
456 : }
457 :
458 4144 : if (needSort)
459 : {
460 : // Nominally not needed but there has been a confusion in the spec
461 : // regarding sort order, so normalize things
462 : // Cf https://github.com/earth-mover/icechunk/issues/2183
463 47 : std::sort(snapshot->m_nodes.begin(), snapshot->m_nodes.end(),
464 47 : [](const Node &a, const Node &b) { return a.path < b.path; });
465 : }
466 :
467 : /* --------------------------------------------------------------------*/
468 : /* Parse manifest_files_v2 / manifest_files[] array */
469 : /* --------------------------------------------------------------------*/
470 4144 : if (nVersion == 2)
471 : {
472 4129 : const auto manifests = fbsSnapshot->manifest_files_v2();
473 4129 : if (manifests)
474 : {
475 4129 : snapshot->m_manifestInfos.reserve(manifests->size());
476 8319 : for (const auto *manifestPtr : *manifests)
477 : {
478 4192 : const auto manifestId = manifestPtr->id();
479 4192 : if (!manifestId || !manifestId->bytes())
480 : {
481 1 : CPLError(CE_Failure, CPLE_AppDefined,
482 : "%s: missing manifest id", pszFilename);
483 2 : return nullptr;
484 : }
485 :
486 4191 : ManifestInfo info;
487 4191 : info.sizeBytes = manifestPtr->size_bytes();
488 4191 : info.numChunkRefs = manifestPtr->num_chunk_refs();
489 : static_assert(sizeof(*(manifestId->bytes())) ==
490 : sizeof(info.id));
491 4191 : memcpy(info.id.data(), manifestId->bytes()->data(),
492 : sizeof(info.id));
493 4191 : info.strId = CrockfordBase32Encode(info.id);
494 :
495 4191 : CPLDebugOnly("Icechunk",
496 : "snapshot %s, manifest %s, size_bytes %" PRIu64
497 : ", num_chunk_refs %u",
498 : snapshotIdBase32.c_str(), info.strId.c_str(),
499 : info.sizeBytes, info.numChunkRefs);
500 :
501 4260 : if (!snapshot->m_manifestInfos.empty() &&
502 69 : info.id <= snapshot->m_manifestInfos.back().id)
503 : {
504 1 : CPLError(
505 : CE_Failure, CPLE_AppDefined,
506 : "%s: ManifestInfo array not sorted by increasing id",
507 : pszFilename);
508 1 : return nullptr;
509 : }
510 :
511 4190 : snapshot->m_manifestInfos.push_back(std::move(info));
512 : }
513 : }
514 : }
515 : else
516 : {
517 15 : const auto manifests = fbsSnapshot->manifest_files();
518 15 : CPLAssertAlways(manifests); // guaranteed by VerifySnapshotBuffer()
519 :
520 15 : snapshot->m_manifestInfos.reserve(manifests->size());
521 36 : for (const auto *manifestPtr : *manifests)
522 : {
523 22 : const auto manifestId = manifestPtr->id();
524 :
525 22 : ManifestInfo info;
526 22 : info.sizeBytes = manifestPtr->size_bytes();
527 22 : info.numChunkRefs = manifestPtr->num_chunk_refs();
528 : static_assert(sizeof(*(manifestId.bytes())) == sizeof(info.id));
529 22 : memcpy(info.id.data(), manifestId.bytes()->data(), sizeof(info.id));
530 22 : info.strId = CrockfordBase32Encode(info.id);
531 :
532 22 : CPLDebugOnly("Icechunk",
533 : "snapshot %s, manifest %s, size_bytes %" PRIu64
534 : ", num_chunk_refs %u",
535 : snapshotIdBase32.c_str(), info.strId.c_str(),
536 : info.sizeBytes, info.numChunkRefs);
537 :
538 29 : if (!snapshot->m_manifestInfos.empty() &&
539 7 : info.id <= snapshot->m_manifestInfos.back().id)
540 : {
541 1 : CPLError(CE_Failure, CPLE_AppDefined,
542 : "%s: ManifestInfo array not sorted by increasing id",
543 : pszFilename);
544 1 : return nullptr;
545 : }
546 :
547 21 : snapshot->m_manifestInfos.push_back(std::move(info));
548 : }
549 : }
550 :
551 4141 : return snapshot;
552 : }
553 :
554 : #if defined(__GNUC__)
555 : #pragma GCC diagnostic pop
556 : #endif
557 :
558 : /************************************************************************/
559 : /* IcechunkSnapshot::GetManifestInfoFromId() */
560 : /************************************************************************/
561 :
562 : const IcechunkSnapshot::ManifestInfo *
563 8108 : IcechunkSnapshot::GetManifestInfoFromId(const ObjectId12 &id) const
564 : {
565 : #if __cplusplus >= 202002L
566 : const auto iter =
567 : std::ranges::lower_bound(m_manifestInfos, id, {}, &ManifestInfo::id);
568 : #else
569 16216 : ManifestInfo lookup;
570 8108 : lookup.id = id;
571 : const auto iter =
572 : std::lower_bound(m_manifestInfos.begin(), m_manifestInfos.end(), lookup,
573 8189 : [](const ManifestInfo &a, const ManifestInfo &b)
574 16297 : { return a.id < b.id; });
575 : #endif
576 8108 : if (iter != m_manifestInfos.end() && iter->id == id)
577 8107 : return &(*iter);
578 1 : return nullptr;
579 : }
580 :
581 : /************************************************************************/
582 : /* IcechunkSnapshot::GetNodeFromPath() */
583 : /************************************************************************/
584 :
585 : const IcechunkSnapshot::Node *
586 8408 : IcechunkSnapshot::GetNodeFromPath(const std::string &path) const
587 : {
588 : #if __cplusplus >= 202002L
589 : const auto iter = std::ranges::lower_bound(m_nodes, path, {}, &Node::path);
590 : #else
591 16816 : Node lookup;
592 8408 : lookup.path = path;
593 : const auto iter = std::lower_bound(m_nodes.begin(), m_nodes.end(), lookup,
594 16803 : [](const Node &a, const Node &b)
595 25211 : { return a.path < b.path; });
596 : #endif
597 8408 : if (iter != m_nodes.end() && iter->path == path)
598 8329 : return &(*iter);
599 79 : return nullptr;
600 : }
601 :
602 : /************************************************************************/
603 : /* DoRefExtentsMatchChunkIdx() */
604 : /************************************************************************/
605 :
606 8252 : inline bool DoRefExtentsMatchChunkIdx(
607 : const std::vector<IcechunkSnapshot::ChunkIndexRange> &extents,
608 : const ChunkIdx &anChunkIdx)
609 : {
610 8252 : CPLAssert(anChunkIdx.size() == extents.size());
611 16477 : for (size_t iDim = 0; iDim < extents.size(); ++iDim)
612 : {
613 16688 : if (anChunkIdx[iDim] < extents[iDim].from ||
614 8337 : anChunkIdx[iDim] >= extents[iDim].to)
615 : {
616 126 : return false;
617 : }
618 : }
619 8126 : return true;
620 : }
621 :
622 : /************************************************************************/
623 : /* IcechunkSnapshot::findManifestIdForChunk() */
624 : /************************************************************************/
625 :
626 : const ObjectId12 *
627 8127 : IcechunkSnapshot::Node::findManifestIdForChunk(const ChunkIdx &anChunkIdx) const
628 : {
629 8127 : CPLAssert(anChunkIdx.size() == numChunks.size());
630 :
631 : // Special case for scalar arrays such as "crs" written by Icechunk v0
632 8147 : if (anChunkIdx.empty() && manifestRefs.size() == 1 &&
633 10 : manifestRefs[0].extents.size() == 1 &&
634 8137 : manifestRefs[0].extents[0].from == 0 &&
635 0 : manifestRefs[0].extents[0].to == 1)
636 : {
637 0 : return &(manifestRefs[0].manifestId);
638 : }
639 :
640 : // Heuristics to find more quickly the chunk, assuming the passed chunk
641 : // index is contained in the last ChunkRef.
642 : thread_local const Node *lastNode = nullptr;
643 : thread_local size_t lastRefsIdx = 0;
644 8127 : if (lastNode == this && lastRefsIdx < manifestRefs.size())
645 : {
646 751 : const auto &ref = manifestRefs[lastRefsIdx];
647 751 : const bool match = DoRefExtentsMatchChunkIdx(ref.extents, anChunkIdx);
648 751 : CPLDebugOnly("Icechunk", "findManifestIdForChunk() guess: %s",
649 : match ? "success" : "missed");
650 751 : if (match)
651 : {
652 716 : return &(ref.manifestId);
653 : }
654 : }
655 :
656 : // Note: if there are many manifestRefs in a node, this linear search could
657 : // become a bottleneck. Consider RTree / KDTree maybe.
658 7502 : for (const auto &ref : manifestRefs)
659 : {
660 7501 : if (DoRefExtentsMatchChunkIdx(ref.extents, anChunkIdx))
661 : {
662 7410 : lastNode = this;
663 7410 : lastRefsIdx = &ref - manifestRefs.data();
664 7410 : return &(ref.manifestId);
665 : }
666 : }
667 :
668 1 : return nullptr;
669 : }
670 :
671 : } // namespace gdal::icechunk
|