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 1967 : ZarrSharedResource::ZarrSharedResource(const std::string &osRootDirectoryName,
23 1967 : bool bUpdatable)
24 1967 : : m_bUpdatable(bUpdatable)
25 : {
26 1967 : m_oObjConsolidatedMetadata.Deinit();
27 :
28 1967 : m_osRootDirectoryName = osRootDirectoryName;
29 1967 : if (!m_osRootDirectoryName.empty() && m_osRootDirectoryName.back() == '/')
30 : {
31 0 : m_osRootDirectoryName.pop_back();
32 : }
33 3934 : m_poPAM = std::make_shared<GDALPamMultiDim>(
34 5901 : CPLFormFilenameSafe(m_osRootDirectoryName.c_str(), "pam", nullptr));
35 1967 : }
36 :
37 : /************************************************************************/
38 : /* ZarrSharedResource::Create() */
39 : /************************************************************************/
40 :
41 : std::shared_ptr<ZarrSharedResource>
42 1967 : ZarrSharedResource::Create(const std::string &osRootDirectoryName,
43 : bool bUpdatable)
44 : {
45 : return std::shared_ptr<ZarrSharedResource>(
46 1967 : new ZarrSharedResource(osRootDirectoryName, bUpdatable));
47 : }
48 :
49 : /************************************************************************/
50 : /* ZarrSharedResource::~ZarrSharedResource() */
51 : /************************************************************************/
52 :
53 1967 : 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 1967 : VSIKerchunkFileSystemsCleanCache();
59 :
60 1967 : if (m_bConsolidatedMetadataModified)
61 : {
62 336 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL)
63 : {
64 190 : CPLJSONDocument oDoc;
65 190 : oDoc.SetRoot(m_oObjConsolidatedMetadata);
66 190 : oDoc.Save(CPLFormFilenameSafe(m_osRootDirectoryName.c_str(),
67 : ".zmetadata", nullptr));
68 : }
69 292 : else if (m_eConsolidatedMetadataKind ==
70 292 : ConsolidatedMetadataKind::INTERNAL &&
71 146 : !cpl::starts_with(m_osRootDirectoryName, "/vsizip/"))
72 : {
73 290 : CPLJSONDocument oDoc;
74 : const std::string osFilename = CPLFormFilenameSafe(
75 290 : m_osRootDirectoryName.c_str(), "zarr.json", nullptr);
76 145 : if (oDoc.Load(osFilename))
77 : {
78 286 : oDoc.GetRoot().Set("consolidated_metadata",
79 143 : m_oObjConsolidatedMetadata);
80 143 : oDoc.Save(osFilename);
81 : }
82 : }
83 : }
84 1967 : }
85 :
86 : /************************************************************************/
87 : /* ZarrSharedResource::OpenRootGroup() */
88 : /************************************************************************/
89 :
90 1620 : 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 3240 : m_osRootDirectoryName.c_str(), "zarr.json", nullptr));
95 : VSIStatBufL sStat;
96 : const bool bHasZarrJson =
97 1620 : (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0);
98 :
99 1620 : 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 639 : if (!STARTS_WITH(m_osRootDirectoryName.c_str(), "/vsikerchunk"))
105 : {
106 : const std::string osZmetadataFilename(CPLFormFilenameSafe(
107 602 : m_osRootDirectoryName.c_str(), ".zmetadata", nullptr));
108 602 : CPLJSONDocument oDoc;
109 : bool bOK;
110 : {
111 602 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
112 602 : bOK = oDoc.Load(osZmetadataFilename);
113 : }
114 602 : 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 1268 : auto poRG = ZarrV2Group::Create(shared_from_this(), std::string(), "/");
126 : // Prevents potential recursion
127 634 : m_poWeakRootGroup = poRG;
128 634 : poRG->SetUpdatable(m_bUpdatable);
129 634 : poRG->SetDirectoryName(m_osRootDirectoryName);
130 :
131 : const std::string osZarrayFilename(CPLFormFilenameSafe(
132 634 : m_osRootDirectoryName.c_str(), ".zarray", nullptr));
133 634 : const auto nErrorCount = CPLGetErrorCounter();
134 634 : if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
135 : {
136 662 : CPLJSONDocument oDoc;
137 331 : if (!oDoc.Load(osZarrayFilename))
138 0 : return nullptr;
139 662 : const auto oRoot = oDoc.GetRoot();
140 331 : 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 660 : CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
159 :
160 990 : if (!poRG->LoadArray(osArrayName, osZarrayFilename, oRoot, false,
161 990 : CPLJSONObject()))
162 39 : return nullptr;
163 :
164 291 : return poRG;
165 : }
166 304 : 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 303 : m_osRootDirectoryName.c_str(), ".zmetadata", nullptr));
175 606 : if (CPLTestBool(CSLFetchNameValueDef(
176 303 : GetOpenOptions(), "USE_CONSOLIDATED_METADATA",
177 303 : CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
178 600 : "YES"))) &&
179 297 : VSIStatL(osZmetadataFilename.c_str(), &sStat) == 0)
180 : {
181 195 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::NONE)
182 : {
183 195 : CPLJSONDocument oDoc;
184 195 : if (!oDoc.Load(osZmetadataFilename))
185 0 : return nullptr;
186 :
187 195 : m_eConsolidatedMetadataKind =
188 : ConsolidatedMetadataKind::EXTERNAL;
189 195 : m_oObjConsolidatedMetadata = oDoc.GetRoot();
190 : }
191 :
192 195 : poRG->InitFromConsolidatedMetadata(m_oObjConsolidatedMetadata);
193 :
194 195 : return poRG;
195 : }
196 :
197 : const std::string osGroupFilename(CPLFormFilenameSafe(
198 108 : m_osRootDirectoryName.c_str(), ".zgroup", nullptr));
199 108 : if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
200 : {
201 196 : CPLJSONDocument oDoc;
202 98 : if (!oDoc.Load(osGroupFilename))
203 0 : return nullptr;
204 :
205 98 : if (!poRG->InitFromZGroup(oDoc.GetRoot()))
206 3 : return nullptr;
207 95 : return poRG;
208 : }
209 : }
210 :
211 : // Zarr v3
212 1982 : auto poRG_V3 = ZarrV3Group::Create(shared_from_this(), std::string(), "/",
213 3964 : m_osRootDirectoryName);
214 : // Prevents potential recursion
215 991 : m_poWeakRootGroup = poRG_V3;
216 991 : poRG_V3->SetUpdatable(m_bUpdatable);
217 :
218 991 : if (bHasZarrJson)
219 : {
220 1962 : CPLJSONDocument oDoc;
221 981 : if (!oDoc.Load(osZarrJsonFilename))
222 0 : return nullptr;
223 1962 : const auto oRoot = oDoc.GetRoot();
224 981 : 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 2943 : oRoot.GetObj("consolidated_metadata");
236 181 : if (oConsolidatedMetadata.GetType() == CPLJSONObject::Type::Object &&
237 1162 : oConsolidatedMetadata.GetString("kind") == "inline" &&
238 362 : CPLTestBool(CSLFetchNameValueDef(
239 181 : GetOpenOptions(), "USE_CONSOLIDATED_METADATA",
240 181 : CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
241 : "YES"))))
242 : {
243 181 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::NONE)
244 : {
245 169 : CPLDebug("JSON", "Using consolidated_metadata");
246 169 : m_eConsolidatedMetadataKind =
247 : ConsolidatedMetadataKind::INTERNAL;
248 169 : m_oObjConsolidatedMetadata = oConsolidatedMetadata;
249 169 : m_oRootAttributes = oRoot.GetObj("attributes");
250 : }
251 :
252 181 : poRG_V3->InitFromConsolidatedMetadata(m_oObjConsolidatedMetadata,
253 181 : m_oRootAttributes);
254 : }
255 :
256 2943 : const std::string osNodeType = oRoot.GetString("node_type");
257 981 : if (osNodeType == "array")
258 : {
259 : const std::string osArrayName(
260 1446 : CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
261 723 : poRG_V3->SetExplored();
262 723 : if (!poRG_V3->LoadArray(osArrayName, osZarrJsonFilename, oRoot))
263 51 : return nullptr;
264 :
265 672 : return poRG_V3;
266 : }
267 258 : else if (osNodeType == "group")
268 : {
269 258 : return poRG_V3;
270 : }
271 : else
272 : {
273 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unhandled node_type value");
274 0 : return nullptr;
275 : }
276 : }
277 :
278 : // No explicit zarr.json in root directory ? Then recurse until we find
279 : // one.
280 10 : auto psDir = VSIOpenDir(m_osRootDirectoryName.c_str(), -1, nullptr);
281 10 : if (!psDir)
282 9 : return nullptr;
283 1 : bool bZarrJsonFound = false;
284 1 : while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
285 : {
286 0 : if (!VSI_ISDIR(psEntry->nMode) &&
287 0 : strcmp(CPLGetFilename(psEntry->pszName), "zarr.json") == 0)
288 : {
289 0 : bZarrJsonFound = true;
290 0 : break;
291 : }
292 0 : }
293 1 : VSICloseDir(psDir);
294 1 : if (bZarrJsonFound)
295 0 : return poRG_V3;
296 :
297 1 : return nullptr;
298 : }
299 :
300 : /************************************************************************/
301 : /* ZarrSharedResource::GetRootGroup() */
302 : /************************************************************************/
303 :
304 3559 : std::shared_ptr<ZarrGroupBase> ZarrSharedResource::GetRootGroup()
305 : {
306 3559 : auto poRootGroup = m_poWeakRootGroup.lock();
307 3559 : if (poRootGroup)
308 1939 : return poRootGroup;
309 1620 : poRootGroup = OpenRootGroup();
310 1620 : m_poWeakRootGroup = poRootGroup;
311 1620 : return poRootGroup;
312 : }
313 :
314 : /************************************************************************/
315 : /* ZarrSharedResource::InitConsolidatedMetadataIfNeeded() */
316 : /************************************************************************/
317 :
318 1260 : void ZarrSharedResource::InitConsolidatedMetadataIfNeeded()
319 : {
320 1260 : if (!m_oObjConsolidatedMetadata.IsValid())
321 : {
322 326 : m_oObjConsolidatedMetadata = CPLJSONObject();
323 326 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL)
324 : {
325 173 : m_oObjConsolidatedMetadata.Add("zarr_consolidated_format", 1);
326 173 : m_oObjConsolidatedMetadata.Add("metadata", CPLJSONObject());
327 : }
328 : else
329 : {
330 153 : m_oObjConsolidatedMetadata.Add("kind", "inline");
331 153 : m_oObjConsolidatedMetadata.Add("must_understand", false);
332 153 : m_oObjConsolidatedMetadata.Add("metadata", CPLJSONObject());
333 : }
334 : }
335 1260 : }
336 :
337 : /************************************************************************/
338 : /* ZarrSharedResource::SetZMetadataItem() */
339 : /************************************************************************/
340 :
341 1542 : void ZarrSharedResource::SetZMetadataItem(const std::string &osFilename,
342 : const CPLJSONObject &obj)
343 : {
344 1542 : if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
345 : {
346 1250 : InitConsolidatedMetadataIfNeeded();
347 :
348 1250 : CPLString osNormalizedFilename(osFilename);
349 1250 : osNormalizedFilename.replaceAll('\\', '/');
350 1250 : CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
351 : (m_osRootDirectoryName + '/').c_str()));
352 :
353 1250 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::INTERNAL)
354 : {
355 370 : const auto nPos = osNormalizedFilename.rfind('/');
356 370 : if (nPos == std::string::npos)
357 0 : return;
358 370 : osNormalizedFilename.resize(nPos);
359 : }
360 :
361 : const char *pszKey =
362 1250 : osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
363 1250 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL ||
364 370 : strcmp(pszKey, "zarr.json") != 0)
365 : {
366 1079 : m_bConsolidatedMetadataModified = true;
367 2158 : auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
368 1079 : oMetadata.DeleteNoSplitName(pszKey);
369 1079 : oMetadata.AddNoSplitName(pszKey, obj);
370 : }
371 : }
372 : }
373 :
374 : /************************************************************************/
375 : /* ZarrSharedResource::DeleteZMetadataItemRecursive() */
376 : /************************************************************************/
377 :
378 14 : void ZarrSharedResource::DeleteZMetadataItemRecursive(
379 : const std::string &osFilename)
380 : {
381 14 : if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
382 : {
383 6 : InitConsolidatedMetadataIfNeeded();
384 :
385 6 : CPLString osNormalizedFilename(osFilename);
386 6 : osNormalizedFilename.replaceAll('\\', '/');
387 6 : CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
388 : (m_osRootDirectoryName + '/').c_str()));
389 :
390 6 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::INTERNAL)
391 : {
392 2 : const auto nPos = osNormalizedFilename.rfind('/');
393 2 : if (nPos == std::string::npos)
394 0 : return;
395 2 : osNormalizedFilename.resize(nPos);
396 : }
397 :
398 : const char *pszKey =
399 6 : osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
400 6 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL ||
401 2 : strcmp(pszKey, "zarr.json") != 0)
402 : {
403 6 : m_bConsolidatedMetadataModified = true;
404 18 : auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
405 34 : for (auto &item : oMetadata.GetChildren())
406 : {
407 28 : if (STARTS_WITH(item.GetName().c_str(), pszKey))
408 : {
409 17 : oMetadata.DeleteNoSplitName(item.GetName());
410 : }
411 : }
412 : }
413 : }
414 : }
415 :
416 : /************************************************************************/
417 : /* ZarrSharedResource::RenameZMetadataRecursive() */
418 : /************************************************************************/
419 :
420 12 : void ZarrSharedResource::RenameZMetadataRecursive(
421 : const std::string &osOldFilename, const std::string &osNewFilename)
422 : {
423 12 : if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
424 : {
425 4 : InitConsolidatedMetadataIfNeeded();
426 :
427 8 : CPLString osNormalizedOldFilename(osOldFilename);
428 4 : osNormalizedOldFilename.replaceAll('\\', '/');
429 4 : CPLAssert(STARTS_WITH(osNormalizedOldFilename.c_str(),
430 : (m_osRootDirectoryName + '/').c_str()));
431 :
432 8 : CPLString osNormalizedNewFilename(osNewFilename);
433 4 : osNormalizedNewFilename.replaceAll('\\', '/');
434 4 : CPLAssert(STARTS_WITH(osNormalizedNewFilename.c_str(),
435 : (m_osRootDirectoryName + '/').c_str()));
436 :
437 4 : m_bConsolidatedMetadataModified = true;
438 :
439 : const char *pszOldKeyRadix =
440 4 : osNormalizedOldFilename.c_str() + m_osRootDirectoryName.size() + 1;
441 : const char *pszNewKeyRadix =
442 4 : osNormalizedNewFilename.c_str() + m_osRootDirectoryName.size() + 1;
443 :
444 12 : auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
445 28 : for (auto &item : oMetadata.GetChildren())
446 : {
447 24 : if (STARTS_WITH(item.GetName().c_str(), pszOldKeyRadix))
448 : {
449 11 : oMetadata.DeleteNoSplitName(item.GetName());
450 22 : std::string osNewKey(pszNewKeyRadix);
451 11 : osNewKey += (item.GetName().c_str() + strlen(pszOldKeyRadix));
452 11 : oMetadata.AddNoSplitName(osNewKey, item);
453 : }
454 : }
455 : }
456 12 : }
457 :
458 : /************************************************************************/
459 : /* ZarrSharedResource::UpdateDimensionSize() */
460 : /************************************************************************/
461 :
462 7 : void ZarrSharedResource::UpdateDimensionSize(
463 : const std::shared_ptr<GDALDimension> &poDim)
464 : {
465 14 : auto poRG = m_poWeakRootGroup.lock();
466 7 : if (!poRG)
467 0 : poRG = OpenRootGroup();
468 7 : if (poRG)
469 : {
470 7 : poRG->UpdateDimensionSize(poDim);
471 : }
472 : else
473 : {
474 0 : CPLError(CE_Failure, CPLE_AppDefined, "UpdateDimensionSize() failed");
475 : }
476 7 : poRG.reset();
477 7 : }
478 :
479 : /************************************************************************/
480 : /* ZarrSharedResource::AddArrayInLoading() */
481 : /************************************************************************/
482 :
483 1975 : bool ZarrSharedResource::AddArrayInLoading(const std::string &osZarrayFilename)
484 : {
485 : // Prevent too deep or recursive array loading
486 1975 : if (m_oSetArrayInLoading.find(osZarrayFilename) !=
487 3950 : m_oSetArrayInLoading.end())
488 : {
489 1 : CPLError(CE_Failure, CPLE_AppDefined,
490 : "Attempt at recursively loading %s", osZarrayFilename.c_str());
491 1 : return false;
492 : }
493 1974 : if (m_oSetArrayInLoading.size() == 32)
494 : {
495 1 : CPLError(CE_Failure, CPLE_AppDefined,
496 : "Too deep call stack in LoadArray()");
497 1 : return false;
498 : }
499 1973 : m_oSetArrayInLoading.insert(osZarrayFilename);
500 1973 : return true;
501 : }
502 :
503 : /************************************************************************/
504 : /* ZarrSharedResource::RemoveArrayInLoading() */
505 : /************************************************************************/
506 :
507 1973 : void ZarrSharedResource::RemoveArrayInLoading(
508 : const std::string &osZarrayFilename)
509 : {
510 1973 : m_oSetArrayInLoading.erase(osZarrayFilename);
511 1973 : }
|