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 2193 : IcechunkSnapshot::Open(const char *pszFilename)
72 : {
73 2193 : CPLDebugOnly("Icechunk", "Opening snapshot %s", pszFilename);
74 4386 : auto fp = VSIFilesystemHandler::OpenStatic(pszFilename, "rb");
75 2193 : if (!fp)
76 : {
77 2 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszFilename);
78 2 : return nullptr;
79 : }
80 :
81 2191 : int nVersion = 0;
82 2191 : auto [buffer, size] =
83 4382 : DecompressFile(pszFilename, fp.get(), FILE_TYPE_SNAPSHOT, &nVersion);
84 2191 : if (!buffer)
85 1 : return nullptr;
86 :
87 : {
88 2190 : Verifier verifier(buffer.get(), size);
89 2190 : 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 4378 : auto snapshot = std::make_unique<IcechunkSnapshot>();
98 2189 : snapshot->m_osFilename = pszFilename;
99 :
100 2189 : const auto *fbsSnapshot = GetSnapshot(buffer.get());
101 2189 : const auto *id = fbsSnapshot->id();
102 2189 : CPLAssertNotNull(id); // guaranteed by VerifySnapshotBuffer()
103 2189 : const auto idBytes = id->bytes();
104 2189 : CPLAssertNotNull(idBytes); // guaranteed by VerifySnapshotBuffer()
105 4378 : const std::string snapshotIdBase32 = CrockfordBase32Encode(*idBytes);
106 2189 : 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 2188 : const auto *message = fbsSnapshot->message();
115 2188 : CPLAssertNotNull(message); // guaranteed by VerifySnapshotBuffer()
116 2188 : snapshot->m_osCommitMessage = GetString(message);
117 2188 : CPLDebugOnly("Icechunk", "snapshot %s: commit message: '%s'", pszFilename,
118 : snapshot->m_osCommitMessage.c_str());
119 :
120 2188 : snapshot->m_flushTimestamp = fbsSnapshot->flushed_at();
121 :
122 : {
123 2188 : const auto *metadata = fbsSnapshot->metadata();
124 2188 : if (metadata && nVersion == 2)
125 : {
126 2179 : 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 2188 : const auto *nodes = fbsSnapshot->nodes();
158 2188 : CPLAssertAlways(nodes); // guaranteed by VerifySnapshotBuffer()
159 2188 : snapshot->m_nodes.reserve(nodes->size());
160 2188 : bool needSort = false;
161 6583 : for (const auto *nodePtr : *nodes)
162 : {
163 4408 : const auto *fbsNodeId = nodePtr->id();
164 4408 : CPLAssertNotNull(fbsNodeId); // guaranteed by VerifySnapshotBuffer()
165 4408 : const auto fbsNodeIdBytes = fbsNodeId->bytes();
166 4408 : CPLAssertAlways(
167 : fbsNodeIdBytes); // guaranteed by VerifySnapshotBuffer()
168 :
169 : ObjectId8 nodeId;
170 : static_assert(sizeof(*fbsNodeIdBytes) == sizeof(nodeId));
171 4408 : memcpy(nodeId.data(), fbsNodeIdBytes->data(), sizeof(nodeId));
172 :
173 4408 : const auto *pathPtr = nodePtr->path();
174 4408 : CPLAssertNotNull(pathPtr); // guaranteed by VerifySnapshotBuffer()
175 :
176 4408 : const std::string path = GetString(pathPtr);
177 11056 : if (path.empty() || path[0] != '/' ||
178 6648 : (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 8814 : if (path.find('\\') != std::string::npos ||
189 8814 : path.find("/./") != std::string::npos ||
190 13221 : path.find("/../") != std::string::npos ||
191 4406 : 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 6632 : needSort = needSort || (!snapshot->m_nodes.empty() &&
200 2226 : snapshot->m_nodes.back().path > path);
201 :
202 4406 : Node node;
203 4406 : node.id = nodeId;
204 4406 : node.path = path;
205 :
206 4406 : const auto *userData = nodePtr->user_data();
207 4406 : CPLAssertAlways(userData); // guaranteed by VerifySnapshotBuffer()
208 8812 : node.content.assign(reinterpret_cast<const char *>(userData->data()),
209 4406 : userData->size());
210 :
211 4406 : 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 4406 : if (const auto arrayData = nodePtr->node_data_as_Array())
217 : {
218 2238 : node.isArray = true;
219 :
220 2238 : uint64_t totalNumChunks = 1;
221 :
222 2238 : if (nVersion == 2)
223 : {
224 2213 : const auto *shape = arrayData->shape_v2();
225 2213 : if (!shape)
226 : {
227 1 : CPLError(CE_Failure, CPLE_AppDefined,
228 : "%s: missing shape_v2 in ArrayData", pszFilename);
229 1 : return nullptr;
230 : }
231 4496 : for (const auto *dimShape : *shape)
232 : {
233 2286 : const uint32_t numChunks = dimShape->num_chunks();
234 2286 : if (numChunks == 0)
235 : {
236 1 : CPLError(CE_Failure, CPLE_AppDefined,
237 : "%s: numChunks == 0", pszFilename);
238 2 : return nullptr;
239 : }
240 2285 : node.numChunks.push_back(numChunks);
241 4570 : if (numChunks >
242 2285 : 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 2284 : totalNumChunks *= numChunks;
249 : }
250 : }
251 : else
252 : {
253 25 : const auto *shape = arrayData->shape();
254 25 : CPLAssertAlways(shape); // guaranteed by VerifySnapshotBuffer()
255 41 : 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 2232 : const auto *manifests = arrayData->manifests();
290 2232 : CPLAssertAlways(manifests); // guaranteed by VerifySnapshotBuffer()
291 :
292 2232 : uint64_t totalNumChunksFromManifests = 0;
293 4464 : for (const auto *manifestPtr : *manifests)
294 : {
295 2236 : const auto manifestId = manifestPtr->object_id();
296 2236 : CPLAssertNotNull(
297 : manifestId); // guaranteed by VerifySnapshotBuffer()
298 2236 : const auto manifestIdBytes = manifestId->bytes();
299 2236 : CPLAssertAlways(
300 : manifestIdBytes); // guaranteed by VerifySnapshotBuffer()
301 :
302 2236 : ManifestRef manifestRef;
303 : static_assert(sizeof(*manifestIdBytes) ==
304 : sizeof(manifestRef.manifestId));
305 2236 : memcpy(manifestRef.manifestId.data(), manifestIdBytes->data(),
306 : sizeof(manifestRef.manifestId));
307 :
308 2236 : const auto extents = manifestPtr->extents();
309 2236 : CPLAssertAlways(
310 : extents); // guaranteed by VerifySnapshotBuffer()
311 :
312 2236 : CPLDebugOnly("Icechunk", "snapshot %s, manifest ref %s:",
313 : snapshotIdBase32.c_str(),
314 : CrockfordBase32Encode(*manifestIdBytes).c_str());
315 :
316 2236 : uint64_t chunkCountFromManifest = 1;
317 2236 : 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 2236 : 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 4550 : for (unsigned iDim = 0; iDim < extents->size(); ++iDim)
355 : {
356 2318 : const auto *extent = (*extents)[iDim];
357 2318 : if (extent->from() >= extent->to() ||
358 4634 : extent->from() >= node.numChunks[iDim] ||
359 2316 : 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 2315 : chunkCountFromManifest *= extent->to() - extent->from();
373 :
374 0 : manifestRef.extents.emplace_back(extent->from(),
375 2315 : extent->to());
376 2315 : 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 2232 : node.manifestRefs.push_back(std::move(manifestRef));
386 :
387 2232 : if (totalNumChunksFromManifests >
388 2232 : std::numeric_limits<uint64_t>::max() -
389 : chunkCountFromManifest)
390 : {
391 : totalNumChunksFromManifests =
392 0 : std::numeric_limits<uint64_t>::max();
393 0 : break;
394 : }
395 2232 : totalNumChunksFromManifests += chunkCountFromManifest;
396 : }
397 :
398 : // Quick partial consistency check
399 2228 : 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 2227 : ChunkIdx anChunkIdx(node.numChunks.size());
414 2227 : std::string chunkStr;
415 8090740 : for (uint64_t iChunk = 0; iChunk < totalNumChunks; ++iChunk)
416 : {
417 8088510 : chunkStr = '[';
418 32326500 : for (size_t iDim = 0; iDim < node.numChunks.size(); ++iDim)
419 : {
420 24238000 : if (iDim > 0)
421 16149500 : chunkStr += ", ";
422 24238000 : chunkStr += std::to_string(anChunkIdx[iDim]);
423 : }
424 8088510 : chunkStr += ']';
425 :
426 8088510 : size_t counter = node.countManifestIdForChunk(anChunkIdx);
427 8088510 : 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 8088510 : if (iChunk + 1 < totalNumChunks)
437 : {
438 : // Increment anChunkIdx
439 8095850 : for (size_t iDim = node.numChunks.size(); iDim > 0;
440 : /* */)
441 : {
442 8095850 : --iDim;
443 8095850 : ++anChunkIdx[iDim];
444 8095850 : if (anChunkIdx[iDim] < node.numChunks[iDim])
445 : {
446 8086280 : break;
447 : }
448 9564 : anChunkIdx[iDim] = 0;
449 : }
450 : }
451 : }
452 : }
453 : }
454 :
455 4395 : snapshot->m_nodes.push_back(std::move(node));
456 : }
457 :
458 2175 : 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 43 : std::sort(snapshot->m_nodes.begin(), snapshot->m_nodes.end(),
464 43 : [](const Node &a, const Node &b) { return a.path < b.path; });
465 : }
466 :
467 : /* --------------------------------------------------------------------*/
468 : /* Parse manifest_files_v2 / manifest_files[] array */
469 : /* --------------------------------------------------------------------*/
470 2175 : if (nVersion == 2)
471 : {
472 2161 : const auto manifests = fbsSnapshot->manifest_files_v2();
473 2161 : if (manifests)
474 : {
475 2161 : snapshot->m_manifestInfos.reserve(manifests->size());
476 4373 : for (const auto *manifestPtr : *manifests)
477 : {
478 2214 : const auto manifestId = manifestPtr->id();
479 2214 : 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 2213 : ManifestInfo info;
487 2213 : info.sizeBytes = manifestPtr->size_bytes();
488 2213 : info.numChunkRefs = manifestPtr->num_chunk_refs();
489 : static_assert(sizeof(*(manifestId->bytes())) ==
490 : sizeof(info.id));
491 2213 : memcpy(info.id.data(), manifestId->bytes()->data(),
492 : sizeof(info.id));
493 2213 : info.strId = CrockfordBase32Encode(info.id);
494 :
495 2213 : 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 2272 : if (!snapshot->m_manifestInfos.empty() &&
502 59 : 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 2212 : snapshot->m_manifestInfos.push_back(std::move(info));
512 : }
513 : }
514 : }
515 : else
516 : {
517 14 : const auto manifests = fbsSnapshot->manifest_files();
518 14 : CPLAssertAlways(manifests); // guaranteed by VerifySnapshotBuffer()
519 :
520 14 : snapshot->m_manifestInfos.reserve(manifests->size());
521 34 : for (const auto *manifestPtr : *manifests)
522 : {
523 21 : const auto manifestId = manifestPtr->id();
524 :
525 21 : ManifestInfo info;
526 21 : info.sizeBytes = manifestPtr->size_bytes();
527 21 : info.numChunkRefs = manifestPtr->num_chunk_refs();
528 : static_assert(sizeof(*(manifestId.bytes())) == sizeof(info.id));
529 21 : memcpy(info.id.data(), manifestId.bytes()->data(), sizeof(info.id));
530 21 : info.strId = CrockfordBase32Encode(info.id);
531 :
532 21 : 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 28 : 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 20 : snapshot->m_manifestInfos.push_back(std::move(info));
548 : }
549 : }
550 :
551 2172 : 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 8102 : 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 16204 : ManifestInfo lookup;
570 8102 : lookup.id = id;
571 : const auto iter =
572 : std::lower_bound(m_manifestInfos.begin(), m_manifestInfos.end(), lookup,
573 8182 : [](const ManifestInfo &a, const ManifestInfo &b)
574 16284 : { return a.id < b.id; });
575 : #endif
576 8102 : if (iter != m_manifestInfos.end() && iter->id == id)
577 8101 : return &(*iter);
578 1 : return nullptr;
579 : }
580 :
581 : /************************************************************************/
582 : /* IcechunkSnapshot::GetNodeFromPath() */
583 : /************************************************************************/
584 :
585 : const IcechunkSnapshot::Node *
586 8373 : 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 16746 : Node lookup;
592 8373 : lookup.path = path;
593 : const auto iter = std::lower_bound(m_nodes.begin(), m_nodes.end(), lookup,
594 16739 : [](const Node &a, const Node &b)
595 25112 : { return a.path < b.path; });
596 : #endif
597 8373 : if (iter != m_nodes.end() && iter->path == path)
598 8303 : return &(*iter);
599 70 : return nullptr;
600 : }
601 :
602 : /************************************************************************/
603 : /* DoRefExtentsMatchChunkIdx() */
604 : /************************************************************************/
605 :
606 8246 : inline bool DoRefExtentsMatchChunkIdx(
607 : const std::vector<IcechunkSnapshot::ChunkIndexRange> &extents,
608 : const ChunkIdx &anChunkIdx)
609 : {
610 8246 : CPLAssert(anChunkIdx.size() == extents.size());
611 16464 : for (size_t iDim = 0; iDim < extents.size(); ++iDim)
612 : {
613 16674 : if (anChunkIdx[iDim] < extents[iDim].from ||
614 8330 : anChunkIdx[iDim] >= extents[iDim].to)
615 : {
616 126 : return false;
617 : }
618 : }
619 8120 : return true;
620 : }
621 :
622 : /************************************************************************/
623 : /* IcechunkSnapshot::findManifestIdForChunk() */
624 : /************************************************************************/
625 :
626 : const ObjectId12 *
627 8121 : IcechunkSnapshot::Node::findManifestIdForChunk(const ChunkIdx &anChunkIdx) const
628 : {
629 8121 : CPLAssert(anChunkIdx.size() == numChunks.size());
630 :
631 : // Special case for scalar arrays such as "crs" written by Icechunk v0
632 8139 : if (anChunkIdx.empty() && manifestRefs.size() == 1 &&
633 9 : manifestRefs[0].extents.size() == 1 &&
634 8130 : 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 8121 : if (lastNode == this && lastRefsIdx < manifestRefs.size())
645 : {
646 97 : const auto &ref = manifestRefs[lastRefsIdx];
647 97 : const bool match = DoRefExtentsMatchChunkIdx(ref.extents, anChunkIdx);
648 97 : CPLDebugOnly("Icechunk", "findManifestIdForChunk() guess: %s",
649 : match ? "success" : "missed");
650 97 : if (match)
651 : {
652 62 : 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 8150 : for (const auto &ref : manifestRefs)
659 : {
660 8149 : if (DoRefExtentsMatchChunkIdx(ref.extents, anChunkIdx))
661 : {
662 8058 : lastNode = this;
663 8058 : lastRefsIdx = &ref - manifestRefs.data();
664 8058 : return &(ref.manifestId);
665 : }
666 : }
667 :
668 1 : return nullptr;
669 : }
670 :
671 : } // namespace gdal::icechunk
|