Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Zarr driver
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "zarr.h"
14 : #include "vsikerchunk.h"
15 :
16 : #include "cpl_json.h"
17 :
18 : /************************************************************************/
19 : /* ZarrSharedResource::ZarrSharedResource() */
20 : /************************************************************************/
21 :
22 2085 : ZarrSharedResource::ZarrSharedResource(const std::string &osRootDirectoryName,
23 2085 : bool bUpdatable)
24 2085 : : m_bUpdatable(bUpdatable)
25 : {
26 2085 : m_oObjConsolidatedMetadata.Deinit();
27 :
28 2085 : m_osRootDirectoryName = osRootDirectoryName;
29 2085 : if (!m_osRootDirectoryName.empty() && m_osRootDirectoryName.back() == '/')
30 : {
31 0 : m_osRootDirectoryName.pop_back();
32 : }
33 4170 : m_poPAM = std::make_shared<GDALPamMultiDim>(
34 6255 : CPLFormFilenameSafe(m_osRootDirectoryName.c_str(), "pam", nullptr));
35 2085 : }
36 :
37 : /************************************************************************/
38 : /* ZarrSharedResource::Create() */
39 : /************************************************************************/
40 :
41 : std::shared_ptr<ZarrSharedResource>
42 2085 : ZarrSharedResource::Create(const std::string &osRootDirectoryName,
43 : bool bUpdatable)
44 : {
45 : return std::shared_ptr<ZarrSharedResource>(
46 2085 : new ZarrSharedResource(osRootDirectoryName, bUpdatable));
47 : }
48 :
49 : /************************************************************************/
50 : /* ZarrSharedResource::~ZarrSharedResource() */
51 : /************************************************************************/
52 :
53 2085 : ZarrSharedResource::~ZarrSharedResource()
54 : {
55 : // We try to clean caches at dataset closing, especially for Parquet
56 : // references, since closing Parquet datasets when the virtual file
57 : // systems are destroyed can be too late and cause crashes.
58 2085 : VSIKerchunkFileSystemsCleanCache();
59 :
60 2085 : if (m_bConsolidatedMetadataModified)
61 : {
62 367 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL)
63 : {
64 124 : CPLJSONDocument oDoc;
65 124 : oDoc.SetRoot(m_oObjConsolidatedMetadata);
66 124 : oDoc.Save(CPLFormFilenameSafe(m_osRootDirectoryName.c_str(),
67 : ".zmetadata", nullptr));
68 : }
69 486 : else if (m_eConsolidatedMetadataKind ==
70 486 : ConsolidatedMetadataKind::INTERNAL &&
71 486 : !cpl::starts_with(m_osRootDirectoryName, "/vsizip/"))
72 : {
73 484 : CPLJSONDocument oDoc;
74 : const std::string osFilename = CPLFormFilenameSafe(
75 484 : m_osRootDirectoryName.c_str(), "zarr.json", nullptr);
76 242 : if (oDoc.Load(osFilename))
77 : {
78 480 : oDoc.GetRoot().Set("consolidated_metadata",
79 240 : m_oObjConsolidatedMetadata);
80 240 : oDoc.Save(osFilename);
81 : }
82 : }
83 : }
84 2085 : }
85 :
86 : /************************************************************************/
87 : /* ZarrSharedResource::OpenRootGroup() */
88 : /************************************************************************/
89 :
90 1715 : std::shared_ptr<ZarrGroupBase> ZarrSharedResource::OpenRootGroup()
91 : {
92 : // Probe zarr.json first so v3 datasets skip the v2 stat cascade.
93 : const std::string osZarrJsonFilename(CPLFormFilenameSafe(
94 3430 : m_osRootDirectoryName.c_str(), "zarr.json", nullptr));
95 : VSIStatBufL sStat;
96 : const bool bHasZarrJson =
97 1715 : (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0);
98 :
99 1715 : if (!bHasZarrJson)
100 : {
101 : // Detect Kerchunk Parquet reference files before creating a v2
102 : // group. Kerchunk .zmetadata has a "record_size" field that
103 : // regular consolidated metadata does not.
104 601 : if (!STARTS_WITH(m_osRootDirectoryName.c_str(), "/vsikerchunk"))
105 : {
106 : const std::string osZmetadataFilename(CPLFormFilenameSafe(
107 564 : m_osRootDirectoryName.c_str(), ".zmetadata", nullptr));
108 564 : CPLJSONDocument oDoc;
109 : bool bOK;
110 : {
111 564 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
112 564 : bOK = oDoc.Load(osZmetadataFilename);
113 : }
114 564 : if (bOK && oDoc.GetRoot().GetObj("record_size").IsValid())
115 : {
116 : m_osKerchunkParquetPath =
117 : CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
118 5 : m_osRootDirectoryName.c_str());
119 5 : CPLDebugOnly("ZARR", "Opening %s",
120 : m_osKerchunkParquetPath.c_str());
121 5 : return nullptr;
122 : }
123 : }
124 :
125 1192 : auto poRG = ZarrV2Group::Create(shared_from_this(), std::string(), "/");
126 : // Prevents potential recursion
127 596 : m_poWeakRootGroup = poRG;
128 596 : poRG->SetUpdatable(m_bUpdatable);
129 596 : poRG->SetDirectoryName(m_osRootDirectoryName);
130 :
131 : const std::string osZarrayFilename(CPLFormFilenameSafe(
132 596 : m_osRootDirectoryName.c_str(), ".zarray", nullptr));
133 596 : const auto nErrorCount = CPLGetErrorCounter();
134 596 : if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
135 : {
136 664 : CPLJSONDocument oDoc;
137 332 : if (!oDoc.Load(osZarrayFilename))
138 0 : return nullptr;
139 664 : const auto oRoot = oDoc.GetRoot();
140 332 : if (oRoot["_NCZARR_ARRAY"].IsValid())
141 : {
142 : // If opening a NCZarr array, initialize its group from NCZarr
143 : // metadata.
144 : const std::string osGroupFilename(CPLFormFilenameSafe(
145 3 : CPLGetDirnameSafe(m_osRootDirectoryName.c_str()).c_str(),
146 3 : ".zgroup", nullptr));
147 3 : if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
148 : {
149 2 : CPLJSONDocument oDocGroup;
150 2 : if (oDocGroup.Load(osGroupFilename))
151 : {
152 2 : if (!poRG->InitFromZGroup(oDocGroup.GetRoot()))
153 1 : return nullptr;
154 : }
155 : }
156 : }
157 : const std::string osArrayName(
158 662 : CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
159 :
160 993 : if (!poRG->LoadArray(osArrayName, osZarrayFilename, oRoot, false,
161 993 : CPLJSONObject()))
162 39 : return nullptr;
163 :
164 292 : return poRG;
165 : }
166 265 : else if (CPLGetErrorCounter() > nErrorCount &&
167 1 : strstr(CPLGetLastErrorMsg(),
168 : "Generation of Kerchunk Parquet cache"))
169 : {
170 0 : return nullptr;
171 : }
172 :
173 : const std::string osZmetadataFilename(CPLFormFilenameSafe(
174 264 : m_osRootDirectoryName.c_str(), ".zmetadata", nullptr));
175 528 : if (CPLTestBool(CSLFetchNameValueDef(
176 264 : GetOpenOptions(), "USE_CONSOLIDATED_METADATA",
177 264 : CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
178 522 : "YES"))) &&
179 258 : VSIStatL(osZmetadataFilename.c_str(), &sStat) == 0)
180 : {
181 154 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::NONE)
182 : {
183 154 : CPLJSONDocument oDoc;
184 154 : if (!oDoc.Load(osZmetadataFilename))
185 0 : return nullptr;
186 :
187 154 : m_eConsolidatedMetadataKind =
188 : ConsolidatedMetadataKind::EXTERNAL;
189 154 : m_oObjConsolidatedMetadata = oDoc.GetRoot();
190 : }
191 :
192 154 : poRG->InitFromConsolidatedMetadata(m_oObjConsolidatedMetadata);
193 :
194 154 : return poRG;
195 : }
196 :
197 : const std::string osGroupFilename(CPLFormFilenameSafe(
198 110 : m_osRootDirectoryName.c_str(), ".zgroup", nullptr));
199 110 : if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
200 : {
201 200 : CPLJSONDocument oDoc;
202 100 : if (!oDoc.Load(osGroupFilename))
203 0 : return nullptr;
204 :
205 100 : if (!poRG->InitFromZGroup(oDoc.GetRoot()))
206 3 : return nullptr;
207 97 : return poRG;
208 : }
209 : }
210 :
211 : // Zarr v3
212 2248 : auto poRG_V3 = ZarrV3Group::Create(shared_from_this(), std::string(), "/",
213 4496 : m_osRootDirectoryName);
214 : // Prevents potential recursion
215 1124 : m_poWeakRootGroup = poRG_V3;
216 1124 : poRG_V3->SetUpdatable(m_bUpdatable);
217 :
218 1124 : if (bHasZarrJson)
219 : {
220 2228 : CPLJSONDocument oDoc;
221 1114 : if (!oDoc.Load(osZarrJsonFilename))
222 0 : return nullptr;
223 2228 : const auto oRoot = oDoc.GetRoot();
224 1114 : if (oRoot.GetInteger("zarr_format") != 3)
225 : {
226 0 : CPLError(CE_Failure, CPLE_AppDefined,
227 : "Unhandled zarr_format value");
228 0 : return nullptr;
229 : }
230 :
231 : // Not yet adopted, but described at https://github.com/zarr-developers/zarr-specs/pull/309/files
232 : // and used for example by
233 : // https://s3.explorer.eopf.copernicus.eu/esa-zarr-sentinel-explorer-fra/tests-output/sentinel-2-l2a/S2B_MSIL2A_20251218T110359_N0511_R094_T32VLK_20251218T115223.zarr/measurements/reflectance/zarr.json
234 : const auto oConsolidatedMetadata =
235 3342 : oRoot.GetObj("consolidated_metadata");
236 262 : if (oConsolidatedMetadata.GetType() == CPLJSONObject::Type::Object &&
237 1376 : oConsolidatedMetadata.GetString("kind") == "inline" &&
238 524 : CPLTestBool(CSLFetchNameValueDef(
239 262 : GetOpenOptions(), "USE_CONSOLIDATED_METADATA",
240 262 : CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
241 : "YES"))))
242 : {
243 262 : m_oRootAttributes.Deinit();
244 262 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::NONE)
245 : {
246 238 : CPLDebug("JSON", "Using consolidated_metadata");
247 238 : m_eConsolidatedMetadataKind =
248 : ConsolidatedMetadataKind::INTERNAL;
249 238 : m_oObjConsolidatedMetadata = oConsolidatedMetadata;
250 238 : m_oRootAttributes = oRoot.GetObj("attributes");
251 : }
252 :
253 262 : poRG_V3->InitFromConsolidatedMetadata(m_oObjConsolidatedMetadata,
254 262 : m_oRootAttributes);
255 : }
256 :
257 3342 : const std::string osNodeType = oRoot.GetString("node_type");
258 1114 : if (osNodeType == "array")
259 : {
260 : const std::string osArrayName(
261 1446 : CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
262 723 : poRG_V3->SetExplored();
263 723 : if (!poRG_V3->LoadArray(osArrayName, osZarrJsonFilename, oRoot))
264 51 : return nullptr;
265 :
266 672 : return poRG_V3;
267 : }
268 391 : else if (osNodeType == "group")
269 : {
270 391 : return poRG_V3;
271 : }
272 : else
273 : {
274 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unhandled node_type value");
275 0 : return nullptr;
276 : }
277 : }
278 :
279 : // No explicit zarr.json in root directory ? Then recurse until we find
280 : // one.
281 10 : auto psDir = VSIOpenDir(m_osRootDirectoryName.c_str(), -1, nullptr);
282 10 : if (!psDir)
283 9 : return nullptr;
284 1 : bool bZarrJsonFound = false;
285 1 : while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
286 : {
287 0 : if (!VSI_ISDIR(psEntry->nMode) &&
288 0 : strcmp(CPLGetFilename(psEntry->pszName), "zarr.json") == 0)
289 : {
290 0 : bZarrJsonFound = true;
291 0 : break;
292 : }
293 0 : }
294 1 : VSICloseDir(psDir);
295 1 : if (bZarrJsonFound)
296 0 : return poRG_V3;
297 :
298 1 : return nullptr;
299 : }
300 :
301 : /************************************************************************/
302 : /* ZarrSharedResource::GetRootGroup() */
303 : /************************************************************************/
304 :
305 3812 : std::shared_ptr<ZarrGroupBase> ZarrSharedResource::GetRootGroup()
306 : {
307 3812 : auto poRootGroup = m_poWeakRootGroup.lock();
308 3812 : if (poRootGroup)
309 2097 : return poRootGroup;
310 1715 : poRootGroup = OpenRootGroup();
311 1715 : m_poWeakRootGroup = poRootGroup;
312 1715 : return poRootGroup;
313 : }
314 :
315 : /************************************************************************/
316 : /* ZarrSharedResource::InitConsolidatedMetadataIfNeeded() */
317 : /************************************************************************/
318 :
319 1202 : void ZarrSharedResource::InitConsolidatedMetadataIfNeeded()
320 : {
321 1202 : if (!m_oObjConsolidatedMetadata.IsValid())
322 : {
323 361 : m_oObjConsolidatedMetadata = CPLJSONObject();
324 361 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL)
325 : {
326 107 : m_oObjConsolidatedMetadata.Add("zarr_consolidated_format", 1);
327 107 : m_oObjConsolidatedMetadata.Add("metadata", CPLJSONObject());
328 : }
329 : else
330 : {
331 254 : m_oObjConsolidatedMetadata.Add("kind", "inline");
332 254 : m_oObjConsolidatedMetadata.Add("must_understand", false);
333 254 : m_oObjConsolidatedMetadata.Add("metadata", CPLJSONObject());
334 : }
335 : }
336 1202 : }
337 :
338 : /************************************************************************/
339 : /* ZarrSharedResource::SetZMetadataItem() */
340 : /************************************************************************/
341 :
342 1484 : void ZarrSharedResource::SetZMetadataItem(const std::string &osFilename,
343 : const CPLJSONObject &obj)
344 : {
345 1484 : if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
346 : {
347 1192 : InitConsolidatedMetadataIfNeeded();
348 :
349 1192 : CPLString osNormalizedFilename(osFilename);
350 1192 : osNormalizedFilename.replaceAll('\\', '/');
351 1192 : CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
352 : (m_osRootDirectoryName + '/').c_str()));
353 :
354 1192 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::INTERNAL)
355 : {
356 745 : const auto nPos = osNormalizedFilename.rfind('/');
357 745 : if (nPos == std::string::npos)
358 0 : return;
359 745 : osNormalizedFilename.resize(nPos);
360 : }
361 :
362 : const char *pszKey =
363 1192 : osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
364 1192 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL ||
365 745 : strcmp(pszKey, "zarr.json") != 0)
366 : {
367 914 : m_bConsolidatedMetadataModified = true;
368 1828 : auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
369 914 : oMetadata.DeleteNoSplitName(pszKey);
370 914 : oMetadata.AddNoSplitName(pszKey, obj);
371 : }
372 : }
373 : }
374 :
375 : /************************************************************************/
376 : /* ZarrSharedResource::DeleteZMetadataItemRecursive() */
377 : /************************************************************************/
378 :
379 14 : void ZarrSharedResource::DeleteZMetadataItemRecursive(
380 : const std::string &osFilename)
381 : {
382 14 : if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
383 : {
384 6 : InitConsolidatedMetadataIfNeeded();
385 :
386 6 : CPLString osNormalizedFilename(osFilename);
387 6 : osNormalizedFilename.replaceAll('\\', '/');
388 6 : CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
389 : (m_osRootDirectoryName + '/').c_str()));
390 :
391 6 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::INTERNAL)
392 : {
393 2 : const auto nPos = osNormalizedFilename.rfind('/');
394 2 : if (nPos == std::string::npos)
395 0 : return;
396 2 : osNormalizedFilename.resize(nPos);
397 : }
398 :
399 : const char *pszKey =
400 6 : osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
401 6 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL ||
402 2 : strcmp(pszKey, "zarr.json") != 0)
403 : {
404 6 : m_bConsolidatedMetadataModified = true;
405 18 : auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
406 34 : for (auto &item : oMetadata.GetChildren())
407 : {
408 28 : if (STARTS_WITH(item.GetName().c_str(), pszKey))
409 : {
410 17 : oMetadata.DeleteNoSplitName(item.GetName());
411 : }
412 : }
413 : }
414 : }
415 : }
416 :
417 : /************************************************************************/
418 : /* ZarrSharedResource::RenameZMetadataRecursive() */
419 : /************************************************************************/
420 :
421 12 : void ZarrSharedResource::RenameZMetadataRecursive(
422 : const std::string &osOldFilename, const std::string &osNewFilename)
423 : {
424 12 : if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
425 : {
426 4 : InitConsolidatedMetadataIfNeeded();
427 :
428 8 : CPLString osNormalizedOldFilename(osOldFilename);
429 4 : osNormalizedOldFilename.replaceAll('\\', '/');
430 4 : CPLAssert(STARTS_WITH(osNormalizedOldFilename.c_str(),
431 : (m_osRootDirectoryName + '/').c_str()));
432 :
433 8 : CPLString osNormalizedNewFilename(osNewFilename);
434 4 : osNormalizedNewFilename.replaceAll('\\', '/');
435 4 : CPLAssert(STARTS_WITH(osNormalizedNewFilename.c_str(),
436 : (m_osRootDirectoryName + '/').c_str()));
437 :
438 4 : m_bConsolidatedMetadataModified = true;
439 :
440 : const char *pszOldKeyRadix =
441 4 : osNormalizedOldFilename.c_str() + m_osRootDirectoryName.size() + 1;
442 : const char *pszNewKeyRadix =
443 4 : osNormalizedNewFilename.c_str() + m_osRootDirectoryName.size() + 1;
444 :
445 12 : auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
446 28 : for (auto &item : oMetadata.GetChildren())
447 : {
448 24 : if (STARTS_WITH(item.GetName().c_str(), pszOldKeyRadix))
449 : {
450 11 : oMetadata.DeleteNoSplitName(item.GetName());
451 22 : std::string osNewKey(pszNewKeyRadix);
452 11 : osNewKey += (item.GetName().c_str() + strlen(pszOldKeyRadix));
453 11 : oMetadata.AddNoSplitName(osNewKey, item);
454 : }
455 : }
456 : }
457 12 : }
458 :
459 : /************************************************************************/
460 : /* ZarrSharedResource::UpdateDimensionSize() */
461 : /************************************************************************/
462 :
463 7 : void ZarrSharedResource::UpdateDimensionSize(
464 : const std::shared_ptr<GDALDimension> &poDim)
465 : {
466 14 : auto poRG = m_poWeakRootGroup.lock();
467 7 : if (!poRG)
468 0 : poRG = OpenRootGroup();
469 7 : if (poRG)
470 : {
471 7 : poRG->UpdateDimensionSize(poDim);
472 : }
473 : else
474 : {
475 0 : CPLError(CE_Failure, CPLE_AppDefined, "UpdateDimensionSize() failed");
476 : }
477 7 : poRG.reset();
478 7 : }
479 :
480 : /************************************************************************/
481 : /* ZarrSharedResource::AddArrayInLoading() */
482 : /************************************************************************/
483 :
484 2127 : bool ZarrSharedResource::AddArrayInLoading(const std::string &osZarrayFilename)
485 : {
486 : // Prevent too deep or recursive array loading
487 2127 : if (m_oSetArrayInLoading.find(osZarrayFilename) !=
488 4254 : m_oSetArrayInLoading.end())
489 : {
490 1 : CPLError(CE_Failure, CPLE_AppDefined,
491 : "Attempt at recursively loading %s", osZarrayFilename.c_str());
492 1 : return false;
493 : }
494 2126 : if (m_oSetArrayInLoading.size() == 32)
495 : {
496 1 : CPLError(CE_Failure, CPLE_AppDefined,
497 : "Too deep call stack in LoadArray()");
498 1 : return false;
499 : }
500 2125 : m_oSetArrayInLoading.insert(osZarrayFilename);
501 2125 : return true;
502 : }
503 :
504 : /************************************************************************/
505 : /* ZarrSharedResource::RemoveArrayInLoading() */
506 : /************************************************************************/
507 :
508 2125 : void ZarrSharedResource::RemoveArrayInLoading(
509 : const std::string &osZarrayFilename)
510 : {
511 2125 : m_oSetArrayInLoading.erase(osZarrayFilename);
512 2125 : }
|