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 1798 : ZarrSharedResource::ZarrSharedResource(const std::string &osRootDirectoryName,
23 1798 : bool bUpdatable)
24 1798 : : m_bUpdatable(bUpdatable)
25 : {
26 1798 : m_oObjConsolidatedMetadata.Deinit();
27 :
28 1798 : m_osRootDirectoryName = osRootDirectoryName;
29 1798 : if (!m_osRootDirectoryName.empty() && m_osRootDirectoryName.back() == '/')
30 : {
31 0 : m_osRootDirectoryName.pop_back();
32 : }
33 3596 : m_poPAM = std::make_shared<GDALPamMultiDim>(
34 5394 : CPLFormFilenameSafe(m_osRootDirectoryName.c_str(), "pam", nullptr));
35 1798 : }
36 :
37 : /************************************************************************/
38 : /* ZarrSharedResource::Create() */
39 : /************************************************************************/
40 :
41 : std::shared_ptr<ZarrSharedResource>
42 1798 : ZarrSharedResource::Create(const std::string &osRootDirectoryName,
43 : bool bUpdatable)
44 : {
45 : return std::shared_ptr<ZarrSharedResource>(
46 1798 : new ZarrSharedResource(osRootDirectoryName, bUpdatable));
47 : }
48 :
49 : /************************************************************************/
50 : /* ZarrSharedResource::~ZarrSharedResource() */
51 : /************************************************************************/
52 :
53 1798 : 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 1798 : VSIKerchunkFileSystemsCleanCache();
59 :
60 1798 : if (m_bConsolidatedMetadataModified)
61 : {
62 302 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL)
63 : {
64 186 : CPLJSONDocument oDoc;
65 186 : oDoc.SetRoot(m_oObjConsolidatedMetadata);
66 186 : oDoc.Save(CPLFormFilenameSafe(m_osRootDirectoryName.c_str(),
67 : ".zmetadata", nullptr));
68 : }
69 232 : else if (m_eConsolidatedMetadataKind ==
70 232 : ConsolidatedMetadataKind::INTERNAL &&
71 116 : !cpl::starts_with(m_osRootDirectoryName, "/vsizip/"))
72 : {
73 230 : CPLJSONDocument oDoc;
74 : const std::string osFilename = CPLFormFilenameSafe(
75 230 : m_osRootDirectoryName.c_str(), "zarr.json", nullptr);
76 115 : if (oDoc.Load(osFilename))
77 : {
78 226 : oDoc.GetRoot().Set("consolidated_metadata",
79 113 : m_oObjConsolidatedMetadata);
80 113 : oDoc.Save(osFilename);
81 : }
82 : }
83 : }
84 1798 : }
85 :
86 : /************************************************************************/
87 : /* ZarrSharedResource::OpenRootGroup() */
88 : /************************************************************************/
89 :
90 1482 : 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 2964 : m_osRootDirectoryName.c_str(), "zarr.json", nullptr));
95 : VSIStatBufL sStat;
96 : const bool bHasZarrJson =
97 1482 : (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0);
98 :
99 1482 : if (!bHasZarrJson)
100 : {
101 1114 : auto poRG = ZarrV2Group::Create(shared_from_this(), std::string(), "/");
102 : // Prevents potential recursion
103 557 : m_poWeakRootGroup = poRG;
104 557 : poRG->SetUpdatable(m_bUpdatable);
105 557 : poRG->SetDirectoryName(m_osRootDirectoryName);
106 :
107 : const std::string osZarrayFilename(CPLFormFilenameSafe(
108 557 : m_osRootDirectoryName.c_str(), ".zarray", nullptr));
109 557 : const auto nErrorCount = CPLGetErrorCounter();
110 557 : if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
111 : {
112 530 : CPLJSONDocument oDoc;
113 265 : if (!oDoc.Load(osZarrayFilename))
114 0 : return nullptr;
115 530 : const auto oRoot = oDoc.GetRoot();
116 265 : if (oRoot["_NCZARR_ARRAY"].IsValid())
117 : {
118 : // If opening a NCZarr array, initialize its group from NCZarr
119 : // metadata.
120 : const std::string osGroupFilename(CPLFormFilenameSafe(
121 3 : CPLGetDirnameSafe(m_osRootDirectoryName.c_str()).c_str(),
122 3 : ".zgroup", nullptr));
123 3 : if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
124 : {
125 2 : CPLJSONDocument oDocGroup;
126 2 : if (oDocGroup.Load(osGroupFilename))
127 : {
128 2 : if (!poRG->InitFromZGroup(oDocGroup.GetRoot()))
129 1 : return nullptr;
130 : }
131 : }
132 : }
133 : const std::string osArrayName(
134 528 : CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
135 :
136 792 : if (!poRG->LoadArray(osArrayName, osZarrayFilename, oRoot, false,
137 792 : CPLJSONObject()))
138 39 : return nullptr;
139 :
140 225 : return poRG;
141 : }
142 293 : else if (CPLGetErrorCounter() > nErrorCount &&
143 1 : strstr(CPLGetLastErrorMsg(),
144 : "Generation of Kerchunk Parquet cache"))
145 : {
146 0 : return nullptr;
147 : }
148 :
149 : const std::string osZmetadataFilename(CPLFormFilenameSafe(
150 292 : m_osRootDirectoryName.c_str(), ".zmetadata", nullptr));
151 584 : if (CPLTestBool(CSLFetchNameValueDef(
152 292 : GetOpenOptions(), "USE_CONSOLIDATED_METADATA",
153 292 : CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
154 578 : "YES"))) &&
155 286 : VSIStatL(osZmetadataFilename.c_str(), &sStat) == 0)
156 : {
157 190 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::NONE)
158 : {
159 190 : CPLJSONDocument oDoc;
160 190 : if (!oDoc.Load(osZmetadataFilename))
161 0 : return nullptr;
162 :
163 190 : m_eConsolidatedMetadataKind =
164 : ConsolidatedMetadataKind::EXTERNAL;
165 190 : m_oObjConsolidatedMetadata = oDoc.GetRoot();
166 : }
167 :
168 190 : poRG->InitFromConsolidatedMetadata(m_oObjConsolidatedMetadata);
169 :
170 190 : return poRG;
171 : }
172 :
173 : const std::string osGroupFilename(CPLFormFilenameSafe(
174 102 : m_osRootDirectoryName.c_str(), ".zgroup", nullptr));
175 102 : if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
176 : {
177 184 : CPLJSONDocument oDoc;
178 92 : if (!oDoc.Load(osGroupFilename))
179 0 : return nullptr;
180 :
181 92 : if (!poRG->InitFromZGroup(oDoc.GetRoot()))
182 3 : return nullptr;
183 89 : return poRG;
184 : }
185 : }
186 :
187 : // Zarr v3
188 1870 : auto poRG_V3 = ZarrV3Group::Create(shared_from_this(), std::string(), "/",
189 3740 : m_osRootDirectoryName);
190 : // Prevents potential recursion
191 935 : m_poWeakRootGroup = poRG_V3;
192 935 : poRG_V3->SetUpdatable(m_bUpdatable);
193 :
194 935 : if (bHasZarrJson)
195 : {
196 1850 : CPLJSONDocument oDoc;
197 925 : if (!oDoc.Load(osZarrJsonFilename))
198 0 : return nullptr;
199 1850 : const auto oRoot = oDoc.GetRoot();
200 925 : if (oRoot.GetInteger("zarr_format") != 3)
201 : {
202 0 : CPLError(CE_Failure, CPLE_AppDefined,
203 : "Unhandled zarr_format value");
204 0 : return nullptr;
205 : }
206 :
207 : // Not yet adopted, but described at https://github.com/zarr-developers/zarr-specs/pull/309/files
208 : // and used for example by
209 : // 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
210 : const auto oConsolidatedMetadata =
211 2775 : oRoot.GetObj("consolidated_metadata");
212 161 : if (oConsolidatedMetadata.GetType() == CPLJSONObject::Type::Object &&
213 1086 : oConsolidatedMetadata.GetString("kind") == "inline" &&
214 322 : CPLTestBool(CSLFetchNameValueDef(
215 161 : GetOpenOptions(), "USE_CONSOLIDATED_METADATA",
216 161 : CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
217 : "YES"))))
218 : {
219 161 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::NONE)
220 : {
221 153 : CPLDebug("JSON", "Using consolidated_metadata");
222 153 : m_eConsolidatedMetadataKind =
223 : ConsolidatedMetadataKind::INTERNAL;
224 153 : m_oObjConsolidatedMetadata = oConsolidatedMetadata;
225 153 : m_oRootAttributes = oRoot.GetObj("attributes");
226 : }
227 :
228 161 : poRG_V3->InitFromConsolidatedMetadata(m_oObjConsolidatedMetadata,
229 161 : m_oRootAttributes);
230 : }
231 :
232 2775 : const std::string osNodeType = oRoot.GetString("node_type");
233 925 : if (osNodeType == "array")
234 : {
235 : const std::string osArrayName(
236 1416 : CPLGetBasenameSafe(m_osRootDirectoryName.c_str()));
237 708 : poRG_V3->SetExplored();
238 708 : if (!poRG_V3->LoadArray(osArrayName, osZarrJsonFilename, oRoot))
239 50 : return nullptr;
240 :
241 658 : return poRG_V3;
242 : }
243 217 : else if (osNodeType == "group")
244 : {
245 217 : return poRG_V3;
246 : }
247 : else
248 : {
249 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unhandled node_type value");
250 0 : return nullptr;
251 : }
252 : }
253 :
254 : // No explicit zarr.json in root directory ? Then recurse until we find
255 : // one.
256 10 : auto psDir = VSIOpenDir(m_osRootDirectoryName.c_str(), -1, nullptr);
257 10 : if (!psDir)
258 9 : return nullptr;
259 1 : bool bZarrJsonFound = false;
260 1 : while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
261 : {
262 0 : if (!VSI_ISDIR(psEntry->nMode) &&
263 0 : strcmp(CPLGetFilename(psEntry->pszName), "zarr.json") == 0)
264 : {
265 0 : bZarrJsonFound = true;
266 0 : break;
267 : }
268 0 : }
269 1 : VSICloseDir(psDir);
270 1 : if (bZarrJsonFound)
271 0 : return poRG_V3;
272 :
273 1 : return nullptr;
274 : }
275 :
276 : /************************************************************************/
277 : /* ZarrSharedResource::GetRootGroup() */
278 : /************************************************************************/
279 :
280 3255 : std::shared_ptr<ZarrGroupBase> ZarrSharedResource::GetRootGroup()
281 : {
282 3255 : auto poRootGroup = m_poWeakRootGroup.lock();
283 3255 : if (poRootGroup)
284 1773 : return poRootGroup;
285 1482 : poRootGroup = OpenRootGroup();
286 1482 : m_poWeakRootGroup = poRootGroup;
287 1482 : return poRootGroup;
288 : }
289 :
290 : /************************************************************************/
291 : /* ZarrSharedResource::InitConsolidatedMetadataIfNeeded() */
292 : /************************************************************************/
293 :
294 1127 : void ZarrSharedResource::InitConsolidatedMetadataIfNeeded()
295 : {
296 1127 : if (!m_oObjConsolidatedMetadata.IsValid())
297 : {
298 291 : m_oObjConsolidatedMetadata = CPLJSONObject();
299 291 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL)
300 : {
301 169 : m_oObjConsolidatedMetadata.Add("zarr_consolidated_format", 1);
302 169 : m_oObjConsolidatedMetadata.Add("metadata", CPLJSONObject());
303 : }
304 : else
305 : {
306 122 : m_oObjConsolidatedMetadata.Add("kind", "inline");
307 122 : m_oObjConsolidatedMetadata.Add("must_understand", false);
308 122 : m_oObjConsolidatedMetadata.Add("metadata", CPLJSONObject());
309 : }
310 : }
311 1127 : }
312 :
313 : /************************************************************************/
314 : /* ZarrSharedResource::SetZMetadataItem() */
315 : /************************************************************************/
316 :
317 1408 : void ZarrSharedResource::SetZMetadataItem(const std::string &osFilename,
318 : const CPLJSONObject &obj)
319 : {
320 1408 : if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
321 : {
322 1119 : InitConsolidatedMetadataIfNeeded();
323 :
324 1119 : CPLString osNormalizedFilename(osFilename);
325 1119 : osNormalizedFilename.replaceAll('\\', '/');
326 1119 : CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
327 : (m_osRootDirectoryName + '/').c_str()));
328 :
329 1119 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::INTERNAL)
330 : {
331 250 : const auto nPos = osNormalizedFilename.rfind('/');
332 250 : if (nPos == std::string::npos)
333 0 : return;
334 250 : osNormalizedFilename.resize(nPos);
335 : }
336 :
337 : const char *pszKey =
338 1119 : osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
339 1119 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL ||
340 250 : strcmp(pszKey, "zarr.json") != 0)
341 : {
342 994 : m_bConsolidatedMetadataModified = true;
343 1988 : auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
344 994 : oMetadata.DeleteNoSplitName(pszKey);
345 994 : oMetadata.AddNoSplitName(pszKey, obj);
346 : }
347 : }
348 : }
349 :
350 : /************************************************************************/
351 : /* ZarrSharedResource::DeleteZMetadataItemRecursive() */
352 : /************************************************************************/
353 :
354 12 : void ZarrSharedResource::DeleteZMetadataItemRecursive(
355 : const std::string &osFilename)
356 : {
357 12 : if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
358 : {
359 4 : InitConsolidatedMetadataIfNeeded();
360 :
361 4 : CPLString osNormalizedFilename(osFilename);
362 4 : osNormalizedFilename.replaceAll('\\', '/');
363 4 : CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
364 : (m_osRootDirectoryName + '/').c_str()));
365 :
366 4 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::INTERNAL)
367 : {
368 0 : const auto nPos = osNormalizedFilename.rfind('/');
369 0 : if (nPos == std::string::npos)
370 0 : return;
371 0 : osNormalizedFilename.resize(nPos);
372 : }
373 :
374 : const char *pszKey =
375 4 : osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
376 4 : if (m_eConsolidatedMetadataKind == ConsolidatedMetadataKind::EXTERNAL ||
377 0 : strcmp(pszKey, "zarr.json") != 0)
378 : {
379 4 : m_bConsolidatedMetadataModified = true;
380 12 : auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
381 28 : for (auto &item : oMetadata.GetChildren())
382 : {
383 24 : if (STARTS_WITH(item.GetName().c_str(), pszKey))
384 : {
385 14 : oMetadata.DeleteNoSplitName(item.GetName());
386 : }
387 : }
388 : }
389 : }
390 : }
391 :
392 : /************************************************************************/
393 : /* ZarrSharedResource::RenameZMetadataRecursive() */
394 : /************************************************************************/
395 :
396 12 : void ZarrSharedResource::RenameZMetadataRecursive(
397 : const std::string &osOldFilename, const std::string &osNewFilename)
398 : {
399 12 : if (m_eConsolidatedMetadataKind != ConsolidatedMetadataKind::NONE)
400 : {
401 4 : InitConsolidatedMetadataIfNeeded();
402 :
403 8 : CPLString osNormalizedOldFilename(osOldFilename);
404 4 : osNormalizedOldFilename.replaceAll('\\', '/');
405 4 : CPLAssert(STARTS_WITH(osNormalizedOldFilename.c_str(),
406 : (m_osRootDirectoryName + '/').c_str()));
407 :
408 8 : CPLString osNormalizedNewFilename(osNewFilename);
409 4 : osNormalizedNewFilename.replaceAll('\\', '/');
410 4 : CPLAssert(STARTS_WITH(osNormalizedNewFilename.c_str(),
411 : (m_osRootDirectoryName + '/').c_str()));
412 :
413 4 : m_bConsolidatedMetadataModified = true;
414 :
415 : const char *pszOldKeyRadix =
416 4 : osNormalizedOldFilename.c_str() + m_osRootDirectoryName.size() + 1;
417 : const char *pszNewKeyRadix =
418 4 : osNormalizedNewFilename.c_str() + m_osRootDirectoryName.size() + 1;
419 :
420 12 : auto oMetadata = m_oObjConsolidatedMetadata["metadata"];
421 28 : for (auto &item : oMetadata.GetChildren())
422 : {
423 24 : if (STARTS_WITH(item.GetName().c_str(), pszOldKeyRadix))
424 : {
425 11 : oMetadata.DeleteNoSplitName(item.GetName());
426 22 : std::string osNewKey(pszNewKeyRadix);
427 11 : osNewKey += (item.GetName().c_str() + strlen(pszOldKeyRadix));
428 11 : oMetadata.AddNoSplitName(osNewKey, item);
429 : }
430 : }
431 : }
432 12 : }
433 :
434 : /************************************************************************/
435 : /* ZarrSharedResource::UpdateDimensionSize() */
436 : /************************************************************************/
437 :
438 7 : void ZarrSharedResource::UpdateDimensionSize(
439 : const std::shared_ptr<GDALDimension> &poDim)
440 : {
441 14 : auto poRG = m_poWeakRootGroup.lock();
442 7 : if (!poRG)
443 0 : poRG = OpenRootGroup();
444 7 : if (poRG)
445 : {
446 7 : poRG->UpdateDimensionSize(poDim);
447 : }
448 : else
449 : {
450 0 : CPLError(CE_Failure, CPLE_AppDefined, "UpdateDimensionSize() failed");
451 : }
452 7 : poRG.reset();
453 7 : }
454 :
455 : /************************************************************************/
456 : /* ZarrSharedResource::AddArrayInLoading() */
457 : /************************************************************************/
458 :
459 1821 : bool ZarrSharedResource::AddArrayInLoading(const std::string &osZarrayFilename)
460 : {
461 : // Prevent too deep or recursive array loading
462 1821 : if (m_oSetArrayInLoading.find(osZarrayFilename) !=
463 3642 : m_oSetArrayInLoading.end())
464 : {
465 1 : CPLError(CE_Failure, CPLE_AppDefined,
466 : "Attempt at recursively loading %s", osZarrayFilename.c_str());
467 1 : return false;
468 : }
469 1820 : if (m_oSetArrayInLoading.size() == 32)
470 : {
471 1 : CPLError(CE_Failure, CPLE_AppDefined,
472 : "Too deep call stack in LoadArray()");
473 1 : return false;
474 : }
475 1819 : m_oSetArrayInLoading.insert(osZarrayFilename);
476 1819 : return true;
477 : }
478 :
479 : /************************************************************************/
480 : /* ZarrSharedResource::RemoveArrayInLoading() */
481 : /************************************************************************/
482 :
483 1819 : void ZarrSharedResource::RemoveArrayInLoading(
484 : const std::string &osZarrayFilename)
485 : {
486 1819 : m_oSetArrayInLoading.erase(osZarrayFilename);
487 1819 : }
|