Coverage Report

Created: 2026-08-11 08:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/zarr/zarr_v2_group.cpp
Line
Count
Source
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
15
#include "cpl_minixml.h"
16
17
#include <algorithm>
18
#include <cassert>
19
#include <limits>
20
#include <map>
21
#include <set>
22
23
/************************************************************************/
24
/*                        ZarrV2Group::Create()                         */
25
/************************************************************************/
26
27
std::shared_ptr<ZarrV2Group>
28
ZarrV2Group::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
29
                    const std::string &osParentName, const std::string &osName)
30
272
{
31
272
    auto poGroup = std::shared_ptr<ZarrV2Group>(
32
272
        new ZarrV2Group(poSharedResource, osParentName, osName));
33
272
    poGroup->SetSelf(poGroup);
34
272
    return poGroup;
35
272
}
36
37
/************************************************************************/
38
/*                     ZarrV2Group::~ZarrV2Group()                      */
39
/************************************************************************/
40
41
ZarrV2Group::~ZarrV2Group()
42
272
{
43
272
    ZarrV2Group::Close();
44
272
}
45
46
/************************************************************************/
47
/*                               Close()                                */
48
/************************************************************************/
49
50
bool ZarrV2Group::Close()
51
272
{
52
272
    bool bRet = ZarrGroupBase::Close();
53
54
272
    if (m_bValid && m_oAttrGroup.IsModified())
55
0
    {
56
0
        CPLJSONDocument oDoc;
57
0
        oDoc.SetRoot(m_oAttrGroup.Serialize());
58
0
        const std::string osAttrFilename =
59
0
            CPLFormFilenameSafe(m_osDirectoryName.c_str(), ".zattrs", nullptr);
60
0
        bRet = oDoc.Save(osAttrFilename) && bRet;
61
0
        m_poSharedResource->SetZMetadataItem(osAttrFilename, oDoc.GetRoot());
62
0
    }
63
64
272
    return bRet;
65
272
}
66
67
/************************************************************************/
68
/*                          ExploreDirectory()                          */
69
/************************************************************************/
70
71
void ZarrV2Group::ExploreDirectory() const
72
0
{
73
0
    if (m_bDirectoryExplored || m_osDirectoryName.empty())
74
0
        return;
75
0
    m_bDirectoryExplored = true;
76
77
0
    const CPLStringList aosFiles(VSIReadDir(m_osDirectoryName.c_str()));
78
    // If the directory contains a .zarray, no need to recurse.
79
0
    for (int i = 0; i < aosFiles.size(); ++i)
80
0
    {
81
0
        if (strcmp(aosFiles[i], ".zarray") == 0)
82
0
            return;
83
0
    }
84
85
0
    for (int i = 0; i < aosFiles.size(); ++i)
86
0
    {
87
0
        if (aosFiles[i][0] != 0 && strcmp(aosFiles[i], ".") != 0 &&
88
0
            strcmp(aosFiles[i], "..") != 0 &&
89
0
            strcmp(aosFiles[i], ".zgroup") != 0 &&
90
0
            strcmp(aosFiles[i], ".zattrs") != 0 &&
91
            // Exclude filenames ending with '/'. This can happen on some
92
            // object storage like S3 where a "foo" file and a "foo/" directory
93
            // can coexist. The ending slash is only appended in that situation
94
            // where both a file and directory have the same name. So we can
95
            // safely ignore the one with an ending slash, as we will also
96
            // encounter its version without slash. Cf use case of
97
            // https://github.com/OSGeo/gdal/issues/8192
98
0
            aosFiles[i][strlen(aosFiles[i]) - 1] != '/')
99
0
        {
100
0
            const std::string osSubDir = CPLFormFilenameSafe(
101
0
                m_osDirectoryName.c_str(), aosFiles[i], nullptr);
102
0
            VSIStatBufL sStat;
103
0
            std::string osFilename =
104
0
                CPLFormFilenameSafe(osSubDir.c_str(), ".zarray", nullptr);
105
0
            if (VSIStatL(osFilename.c_str(), &sStat) == 0)
106
0
            {
107
0
                if (!cpl::contains(m_oSetArrayNames, aosFiles[i]))
108
0
                {
109
0
                    m_oSetArrayNames.insert(aosFiles[i]);
110
0
                    m_aosArrays.emplace_back(aosFiles[i]);
111
0
                }
112
0
            }
113
0
            else
114
0
            {
115
0
                osFilename =
116
0
                    CPLFormFilenameSafe(osSubDir.c_str(), ".zgroup", nullptr);
117
0
                if (VSIStatL(osFilename.c_str(), &sStat) == 0 &&
118
0
                    !cpl::contains(m_oSetGroupNames, aosFiles[i]))
119
0
                {
120
0
                    m_oSetGroupNames.insert(aosFiles[i]);
121
0
                    m_aosGroups.emplace_back(aosFiles[i]);
122
0
                }
123
0
            }
124
0
        }
125
0
    }
126
0
}
127
128
/************************************************************************/
129
/*                           OpenZarrArray()                            */
130
/************************************************************************/
131
132
std::shared_ptr<ZarrArray> ZarrV2Group::OpenZarrArray(const std::string &osName,
133
                                                      CSLConstList) const
134
0
{
135
0
    if (!CheckValidAndErrorOutIfNot())
136
0
        return nullptr;
137
138
0
    auto oIter = m_oMapMDArrays.find(osName);
139
0
    if (oIter != m_oMapMDArrays.end())
140
0
        return oIter->second;
141
142
0
    if (!m_bReadFromConsolidatedMetadata && !m_osDirectoryName.empty())
143
0
    {
144
0
        const std::string osSubDir = CPLFormFilenameSafe(
145
0
            m_osDirectoryName.c_str(), osName.c_str(), nullptr);
146
0
        VSIStatBufL sStat;
147
0
        const std::string osZarrayFilename =
148
0
            CPLFormFilenameSafe(osSubDir.c_str(), ".zarray", nullptr);
149
0
        if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
150
0
        {
151
0
            CPLJSONDocument oDoc;
152
0
            if (!oDoc.Load(osZarrayFilename))
153
0
                return nullptr;
154
0
            const auto oRoot = oDoc.GetRoot();
155
0
            return LoadArray(osName, osZarrayFilename, oRoot, false,
156
0
                             CPLJSONObject());
157
0
        }
158
0
    }
159
160
0
    return nullptr;
161
0
}
162
163
/************************************************************************/
164
/*                           OpenZarrGroup()                            */
165
/************************************************************************/
166
167
std::shared_ptr<ZarrGroupBase>
168
ZarrV2Group::OpenZarrGroup(const std::string &osName, CSLConstList) const
169
0
{
170
0
    if (!CheckValidAndErrorOutIfNot())
171
0
        return nullptr;
172
173
0
    auto oIter = m_oMapGroups.find(osName);
174
0
    if (oIter != m_oMapGroups.end())
175
0
        return oIter->second;
176
177
0
    if (!m_bReadFromConsolidatedMetadata && !m_osDirectoryName.empty())
178
0
    {
179
0
        const std::string osSubDir = CPLFormFilenameSafe(
180
0
            m_osDirectoryName.c_str(), osName.c_str(), nullptr);
181
0
        VSIStatBufL sStat;
182
0
        const std::string osZgroupFilename =
183
0
            CPLFormFilenameSafe(osSubDir.c_str(), ".zgroup", nullptr);
184
0
        if (VSIStatL(osZgroupFilename.c_str(), &sStat) == 0)
185
0
        {
186
0
            CPLJSONDocument oDoc;
187
0
            if (!oDoc.Load(osZgroupFilename))
188
0
                return nullptr;
189
190
0
            auto poSubGroup =
191
0
                ZarrV2Group::Create(m_poSharedResource, GetFullName(), osName);
192
0
            poSubGroup->m_poParent =
193
0
                std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
194
0
            poSubGroup->SetUpdatable(m_bUpdatable);
195
0
            poSubGroup->SetDirectoryName(osSubDir);
196
0
            m_oMapGroups[osName] = poSubGroup;
197
198
            // Must be done after setting m_oMapGroups, to avoid infinite
199
            // recursion when opening NCZarr datasets with indexing variables
200
            // of dimensions
201
0
            poSubGroup->InitFromZGroup(oDoc.GetRoot());
202
203
0
            return poSubGroup;
204
0
        }
205
0
    }
206
207
0
    return nullptr;
208
0
}
209
210
/************************************************************************/
211
/*                    ZarrV2Group::LoadAttributes()                     */
212
/************************************************************************/
213
214
void ZarrV2Group::LoadAttributes() const
215
0
{
216
0
    if (m_bAttributesLoaded || m_osDirectoryName.empty())
217
0
        return;
218
0
    m_bAttributesLoaded = true;
219
220
0
    CPLJSONDocument oDoc;
221
0
    const std::string osZattrsFilename(
222
0
        CPLFormFilenameSafe(m_osDirectoryName.c_str(), ".zattrs", nullptr));
223
0
    CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
224
0
    if (!oDoc.Load(osZattrsFilename))
225
0
        return;
226
0
    auto oRoot = oDoc.GetRoot();
227
0
    m_oAttrGroup.Init(oRoot, m_bUpdatable);
228
0
}
229
230
/************************************************************************/
231
/*                  ZarrV2Group::GetOrCreateSubGroup()                  */
232
/************************************************************************/
233
234
std::shared_ptr<ZarrV2Group>
235
ZarrV2Group::GetOrCreateSubGroup(const std::string &osSubGroupFullname)
236
0
{
237
0
    auto poSubGroup = std::dynamic_pointer_cast<ZarrV2Group>(
238
0
        OpenGroupFromFullname(osSubGroupFullname));
239
0
    if (poSubGroup)
240
0
    {
241
0
        return poSubGroup;
242
0
    }
243
244
0
    const auto nLastSlashPos = osSubGroupFullname.rfind('/');
245
0
    auto poBelongingGroup =
246
0
        (nLastSlashPos == 0)
247
0
            ? this
248
0
            : GetOrCreateSubGroup(osSubGroupFullname.substr(0, nLastSlashPos))
249
0
                  .get();
250
251
0
    poSubGroup =
252
0
        ZarrV2Group::Create(m_poSharedResource, poBelongingGroup->GetFullName(),
253
0
                            osSubGroupFullname.substr(nLastSlashPos + 1));
254
0
    poSubGroup->m_poParent = std::dynamic_pointer_cast<ZarrGroupBase>(
255
0
        poBelongingGroup->m_pSelf.lock());
256
0
    poSubGroup->SetDirectoryName(
257
0
        CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
258
0
                            poSubGroup->GetName().c_str(), nullptr));
259
0
    poSubGroup->m_bDirectoryExplored = true;
260
0
    poSubGroup->m_bAttributesLoaded = true;
261
0
    poSubGroup->m_bReadFromConsolidatedMetadata = true;
262
0
    poSubGroup->SetUpdatable(m_bUpdatable);
263
264
0
    poBelongingGroup->m_oMapGroups[poSubGroup->GetName()] = poSubGroup;
265
0
    poBelongingGroup->m_oSetGroupNames.insert(poSubGroup->GetName());
266
0
    poBelongingGroup->m_aosGroups.emplace_back(poSubGroup->GetName());
267
0
    return poSubGroup;
268
0
}
269
270
/************************************************************************/
271
/*             ZarrV2Group::InitFromConsolidatedMetadata()              */
272
/************************************************************************/
273
274
void ZarrV2Group::InitFromConsolidatedMetadata(const CPLJSONObject &obj)
275
0
{
276
0
    m_bDirectoryExplored = true;
277
0
    m_bAttributesLoaded = true;
278
0
    m_bReadFromConsolidatedMetadata = true;
279
280
0
    const auto metadata = obj["metadata"];
281
0
    if (metadata.GetType() != CPLJSONObject::Type::Object)
282
0
        return;
283
0
    const auto children = metadata.GetChildren();
284
0
    std::map<std::string, const CPLJSONObject *> oMapArrays;
285
286
    // First pass to create groups and collect arrays
287
0
    for (const auto &child : children)
288
0
    {
289
0
        const std::string osName(child.GetName());
290
0
        if (std::count(osName.begin(), osName.end(), '/') > 32)
291
0
        {
292
            // Avoid too deep recursion in GetOrCreateSubGroup()
293
0
            continue;
294
0
        }
295
0
        if (osName == ".zattrs")
296
0
        {
297
0
            m_oAttrGroup.Init(child, m_bUpdatable);
298
0
        }
299
0
        else if (osName.size() > strlen("/.zgroup") &&
300
0
                 osName.substr(osName.size() - strlen("/.zgroup")) ==
301
0
                     "/.zgroup")
302
0
        {
303
0
            GetOrCreateSubGroup(
304
0
                "/" + osName.substr(0, osName.size() - strlen("/.zgroup")));
305
0
        }
306
0
        else if (osName.size() > strlen("/.zarray") &&
307
0
                 osName.substr(osName.size() - strlen("/.zarray")) ==
308
0
                     "/.zarray")
309
0
        {
310
0
            auto osArrayFullname =
311
0
                osName.substr(0, osName.size() - strlen("/.zarray"));
312
0
            oMapArrays[osArrayFullname] = &child;
313
0
        }
314
0
    }
315
316
0
    const auto CreateArray = [this](const std::string &osArrayFullname,
317
0
                                    const CPLJSONObject &oArray,
318
0
                                    const CPLJSONObject &oAttributes)
319
0
    {
320
0
        const auto nLastSlashPos = osArrayFullname.rfind('/');
321
0
        auto poBelongingGroup =
322
0
            (nLastSlashPos == std::string::npos)
323
0
                ? this
324
0
                : GetOrCreateSubGroup("/" +
325
0
                                      osArrayFullname.substr(0, nLastSlashPos))
326
0
                      .get();
327
0
        const auto osArrayName =
328
0
            nLastSlashPos == std::string::npos
329
0
                ? osArrayFullname
330
0
                : osArrayFullname.substr(nLastSlashPos + 1);
331
0
        const std::string osZarrayFilename = CPLFormFilenameSafe(
332
0
            CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
333
0
                                osArrayName.c_str(), nullptr)
334
0
                .c_str(),
335
0
            ".zarray", nullptr);
336
0
        poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray, true,
337
0
                                    oAttributes);
338
0
    };
339
340
0
    struct ArrayDesc
341
0
    {
342
0
        std::string osArrayFullname{};
343
0
        const CPLJSONObject *poArray = nullptr;
344
0
        const CPLJSONObject *poAttrs = nullptr;
345
0
    };
346
347
0
    std::vector<ArrayDesc> aoRegularArrays;
348
349
    // Second pass to read attributes and create arrays that are indexing
350
    // variable
351
0
    for (const auto &child : children)
352
0
    {
353
0
        const std::string osName(child.GetName());
354
0
        if (osName.size() > strlen("/.zattrs") &&
355
0
            osName.substr(osName.size() - strlen("/.zattrs")) == "/.zattrs")
356
0
        {
357
0
            std::string osObjectFullnameNoLeadingSlash =
358
0
                osName.substr(0, osName.size() - strlen("/.zattrs"));
359
0
            auto poSubGroup = std::dynamic_pointer_cast<ZarrV2Group>(
360
0
                OpenGroupFromFullname('/' + osObjectFullnameNoLeadingSlash));
361
0
            if (poSubGroup)
362
0
            {
363
0
                poSubGroup->m_oAttrGroup.Init(child, m_bUpdatable);
364
0
            }
365
0
            else
366
0
            {
367
0
                auto oIter = oMapArrays.find(osObjectFullnameNoLeadingSlash);
368
0
                if (oIter != oMapArrays.end())
369
0
                {
370
0
                    const auto nLastSlashPos =
371
0
                        osObjectFullnameNoLeadingSlash.rfind('/');
372
0
                    const std::string osArrayName =
373
0
                        (nLastSlashPos == std::string::npos)
374
0
                            ? osObjectFullnameNoLeadingSlash
375
0
                            : osObjectFullnameNoLeadingSlash.substr(
376
0
                                  nLastSlashPos + 1);
377
0
                    const auto arrayDimensions =
378
0
                        child["_ARRAY_DIMENSIONS"].ToArray();
379
0
                    if (arrayDimensions.IsValid() &&
380
0
                        arrayDimensions.Size() == 1 &&
381
0
                        arrayDimensions[0].ToString() == osArrayName)
382
0
                    {
383
0
                        CreateArray(osObjectFullnameNoLeadingSlash,
384
0
                                    *(oIter->second), child);
385
0
                        oMapArrays.erase(oIter);
386
0
                    }
387
0
                    else
388
0
                    {
389
0
                        ArrayDesc desc;
390
0
                        desc.osArrayFullname =
391
0
                            std::move(osObjectFullnameNoLeadingSlash);
392
0
                        desc.poArray = oIter->second;
393
0
                        desc.poAttrs = &child;
394
0
                        aoRegularArrays.emplace_back(std::move(desc));
395
0
                    }
396
0
                }
397
0
            }
398
0
        }
399
0
    }
400
401
    // Third pass to create non-indexing arrays with attributes
402
0
    for (const auto &desc : aoRegularArrays)
403
0
    {
404
0
        CreateArray(desc.osArrayFullname, *(desc.poArray), *(desc.poAttrs));
405
0
        oMapArrays.erase(desc.osArrayFullname);
406
0
    }
407
408
    // Fourth pass to create arrays without attributes
409
0
    for (const auto &kv : oMapArrays)
410
0
    {
411
0
        CreateArray(kv.first, *(kv.second), CPLJSONObject());
412
0
    }
413
0
}
414
415
/************************************************************************/
416
/*                    ZarrV2Group::InitFromZGroup()                     */
417
/************************************************************************/
418
419
bool ZarrV2Group::InitFromZGroup(const CPLJSONObject &obj)
420
0
{
421
    // Parse potential NCZarr (V2) extensions:
422
    // https://www.unidata.ucar.edu/software/netcdf/documentation/NUG/nczarr_head.html
423
0
    const auto nczarrGroup = obj["_NCZARR_GROUP"];
424
0
    if (nczarrGroup.GetType() == CPLJSONObject::Type::Object)
425
0
    {
426
0
        if (m_bUpdatable)
427
0
        {
428
0
            CPLError(CE_Failure, CPLE_NotSupported,
429
0
                     "Update of NCZarr datasets is not supported");
430
0
            return false;
431
0
        }
432
0
        m_bDirectoryExplored = true;
433
434
        // If not opening from the root of the dataset, walk up to it
435
0
        if (!obj["_NCZARR_SUPERBLOCK"].IsValid() &&
436
0
            m_poParent.lock() == nullptr)
437
0
        {
438
0
            const std::string osParentGroupFilename(CPLFormFilenameSafe(
439
0
                CPLGetPathSafe(m_osDirectoryName.c_str()).c_str(), ".zgroup",
440
0
                nullptr));
441
0
            VSIStatBufL sStat;
442
0
            if (VSIStatL(osParentGroupFilename.c_str(), &sStat) == 0)
443
0
            {
444
0
                CPLJSONDocument oDoc;
445
0
                if (oDoc.Load(osParentGroupFilename))
446
0
                {
447
0
                    auto poParent = ZarrV2Group::Create(
448
0
                        m_poSharedResource, std::string(), std::string());
449
0
                    poParent->m_bDirectoryExplored = true;
450
0
                    poParent->SetDirectoryName(
451
0
                        CPLGetPathSafe(m_osDirectoryName.c_str()));
452
0
                    poParent->InitFromZGroup(oDoc.GetRoot());
453
0
                    m_poParentStrongRef = poParent;
454
0
                    m_poParent = poParent;
455
456
                    // Patch our name and fullname
457
0
                    m_osName = CPLGetFilename(m_osDirectoryName.c_str());
458
0
                    m_osFullName =
459
0
                        poParent->GetFullName() == "/"
460
0
                            ? m_osName
461
0
                            : poParent->GetFullName() + "/" + m_osName;
462
0
                }
463
0
            }
464
0
        }
465
466
0
        const auto IsValidName = [](const std::string &s)
467
0
        {
468
0
            return !s.empty() && s != "." && s != ".." &&
469
0
                   s.find("/") == std::string::npos &&
470
0
                   s.find("\\") == std::string::npos;
471
0
        };
472
473
        // Create dimensions first, as they will be potentially patched
474
        // by the OpenMDArray() later
475
0
        const auto dims = nczarrGroup["dims"];
476
0
        for (const auto &jDim : dims.GetChildren())
477
0
        {
478
0
            const auto osName = jDim.GetName();
479
0
            const GUInt64 nSize = jDim.ToLong();
480
0
            if (!IsValidName(osName))
481
0
            {
482
0
                CPLError(CE_Failure, CPLE_AppDefined,
483
0
                         "Invalid dimension name for %s", osName.c_str());
484
0
            }
485
0
            else if (nSize == 0)
486
0
            {
487
0
                CPLError(CE_Failure, CPLE_AppDefined,
488
0
                         "Invalid dimension size for %s", osName.c_str());
489
0
            }
490
0
            else
491
0
            {
492
0
                CreateDimension(osName,
493
0
                                std::string(),  // type
494
0
                                std::string(),  // direction,
495
0
                                nSize, nullptr);
496
0
            }
497
0
        }
498
499
0
        const auto vars = nczarrGroup["vars"].ToArray();
500
501
        // This is to protect against corruped/hostile datasets
502
0
        std::set<std::string> alreadyExploredArray;
503
0
        int nCountInvalid = 0;
504
505
        // open first indexing variables
506
0
        for (const auto &var : vars)
507
0
        {
508
0
            const auto osVarName = var.ToString();
509
0
            if (IsValidName(osVarName) &&
510
0
                cpl::contains(m_oMapDimensions, osVarName) &&
511
0
                !cpl::contains(m_oSetArrayNames, osVarName) &&
512
0
                !cpl::contains(alreadyExploredArray, osVarName))
513
0
            {
514
0
                alreadyExploredArray.insert(osVarName);
515
0
                if (!OpenMDArray(osVarName))
516
0
                {
517
0
                    if (++nCountInvalid > 100)
518
0
                    {
519
0
                        CPLError(CE_Failure, CPLE_AppDefined,
520
0
                                 "Too many invalid arrays in NCZarr 'vars' "
521
0
                                 "array. Giving up");
522
0
                        return false;
523
0
                    }
524
0
                }
525
0
            }
526
0
        }
527
528
        // add regular arrays
529
0
        for (const auto &var : vars)
530
0
        {
531
0
            const auto osVarName = var.ToString();
532
0
            if (IsValidName(osVarName) &&
533
0
                !cpl::contains(m_oMapDimensions, osVarName) &&
534
0
                !cpl::contains(m_oSetArrayNames, osVarName) &&
535
0
                !cpl::contains(alreadyExploredArray, osVarName))
536
0
            {
537
0
                m_oSetArrayNames.insert(osVarName);
538
0
                m_aosArrays.emplace_back(osVarName);
539
0
            }
540
0
        }
541
542
        // Finally list groups
543
0
        const auto groups = nczarrGroup["groups"].ToArray();
544
0
        for (const auto &group : groups)
545
0
        {
546
0
            const auto osGroupName = group.ToString();
547
0
            if (IsValidName(osGroupName) &&
548
0
                !cpl::contains(m_oSetGroupNames, osGroupName))
549
0
            {
550
0
                m_oSetGroupNames.insert(osGroupName);
551
0
                m_aosGroups.emplace_back(osGroupName);
552
0
            }
553
0
        }
554
0
    }
555
0
    return true;
556
0
}
557
558
/************************************************************************/
559
/*                     ZarrV2Group::CreateOnDisk()                      */
560
/************************************************************************/
561
562
std::shared_ptr<ZarrV2Group> ZarrV2Group::CreateOnDisk(
563
    const std::shared_ptr<ZarrSharedResource> &poSharedResource,
564
    const std::string &osParentName, const std::string &osName,
565
    const std::string &osDirectoryName)
566
0
{
567
0
    if (VSIMkdir(osDirectoryName.c_str(), 0755) != 0)
568
0
    {
569
0
        VSIStatBufL sStat;
570
0
        if (VSIStatL(osDirectoryName.c_str(), &sStat) == 0)
571
0
        {
572
0
            CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
573
0
                     osDirectoryName.c_str());
574
0
        }
575
0
        else
576
0
        {
577
0
            CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
578
0
                     osDirectoryName.c_str());
579
0
        }
580
0
        return nullptr;
581
0
    }
582
583
0
    const std::string osZgroupFilename(
584
0
        CPLFormFilenameSafe(osDirectoryName.c_str(), ".zgroup", nullptr));
585
0
    VSILFILE *fp = VSIFOpenL(osZgroupFilename.c_str(), "wb");
586
0
    if (!fp)
587
0
    {
588
0
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
589
0
                 osZgroupFilename.c_str());
590
0
        return nullptr;
591
0
    }
592
0
    VSIFPrintfL(fp, "{\n  \"zarr_format\": 2\n}\n");
593
0
    VSIFCloseL(fp);
594
595
0
    auto poGroup = ZarrV2Group::Create(poSharedResource, osParentName, osName);
596
0
    poGroup->SetDirectoryName(osDirectoryName);
597
0
    poGroup->SetUpdatable(true);
598
0
    poGroup->m_bDirectoryExplored = true;
599
600
0
    CPLJSONObject oObj;
601
0
    oObj.Add("zarr_format", 2);
602
0
    poSharedResource->SetZMetadataItem(osZgroupFilename, oObj);
603
604
0
    return poGroup;
605
0
}
606
607
/************************************************************************/
608
/*                      ZarrV2Group::CreateGroup()                      */
609
/************************************************************************/
610
611
std::shared_ptr<GDALGroup>
612
ZarrV2Group::CreateGroup(const std::string &osName,
613
                         CSLConstList /* papszOptions */)
614
0
{
615
0
    if (!CheckValidAndErrorOutIfNot())
616
0
        return nullptr;
617
618
0
    if (!m_bUpdatable)
619
0
    {
620
0
        CPLError(CE_Failure, CPLE_NotSupported,
621
0
                 "Dataset not open in update mode");
622
0
        return nullptr;
623
0
    }
624
0
    if (!IsValidObjectName(osName))
625
0
    {
626
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
627
0
        return nullptr;
628
0
    }
629
630
0
    GetGroupNames();
631
632
0
    if (cpl::contains(m_oSetGroupNames, osName))
633
0
    {
634
0
        CPLError(CE_Failure, CPLE_AppDefined,
635
0
                 "A group with same name (%s) already exists in group %s",
636
0
                 osName.c_str(), GetFullName().c_str());
637
0
        return nullptr;
638
0
    }
639
640
0
    const std::string osDirectoryName =
641
0
        CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
642
0
    auto poGroup = CreateOnDisk(m_poSharedResource, GetFullName(), osName,
643
0
                                osDirectoryName);
644
0
    if (!poGroup)
645
0
        return nullptr;
646
0
    poGroup->m_poParent =
647
0
        std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
648
0
    m_oMapGroups[osName] = poGroup;
649
0
    m_oSetGroupNames.insert(osName);
650
0
    m_aosGroups.emplace_back(osName);
651
0
    return poGroup;
652
0
}
653
654
/************************************************************************/
655
/*                           FillDTypeElts()                            */
656
/************************************************************************/
657
658
static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
659
                                   size_t nGDALStartOffset,
660
                                   std::vector<DtypeElt> &aoDtypeElts,
661
                                   bool bUseUnicode)
662
0
{
663
0
    CPLJSONObject dtype;
664
0
    const auto eClass = oDataType.GetClass();
665
0
    const size_t nNativeStartOffset =
666
0
        aoDtypeElts.empty()
667
0
            ? 0
668
0
            : aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize;
669
0
    const std::string dummy("dummy");
670
671
0
    switch (eClass)
672
0
    {
673
0
        case GEDTC_STRING:
674
0
        {
675
0
            if (oDataType.GetMaxStringLength() == 0)
676
0
            {
677
0
                CPLError(CE_Failure, CPLE_NotSupported,
678
0
                         "String arrays of unlimited size are not supported");
679
0
                dtype = CPLJSONObject();
680
0
                dtype.Deinit();
681
0
                return dtype;
682
0
            }
683
0
            DtypeElt elt;
684
0
            elt.nativeOffset = nNativeStartOffset;
685
0
            if (bUseUnicode)
686
0
            {
687
0
                elt.nativeType = DtypeElt::NativeType::STRING_UNICODE;
688
0
                elt.nativeSize = oDataType.GetMaxStringLength() * 4;
689
#ifdef CPL_MSB
690
                elt.needByteSwapping = true;
691
#endif
692
0
                dtype.Set(
693
0
                    dummy,
694
0
                    CPLSPrintf("<U%d", static_cast<int>(
695
0
                                           oDataType.GetMaxStringLength())));
696
0
            }
697
0
            else
698
0
            {
699
0
                elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
700
0
                elt.nativeSize = oDataType.GetMaxStringLength();
701
0
                dtype.Set(
702
0
                    dummy,
703
0
                    CPLSPrintf("|S%d", static_cast<int>(
704
0
                                           oDataType.GetMaxStringLength())));
705
0
            }
706
0
            elt.gdalOffset = nGDALStartOffset;
707
0
            elt.gdalSize = sizeof(char *);
708
0
            aoDtypeElts.emplace_back(elt);
709
0
            break;
710
0
        }
711
712
0
        case GEDTC_NUMERIC:
713
0
        {
714
0
            const auto eDT = oDataType.GetNumericDataType();
715
0
            DtypeElt elt;
716
0
            bool bUnsupported = false;
717
0
            switch (eDT)
718
0
            {
719
0
                case GDT_UInt8:
720
0
                {
721
0
                    elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
722
0
                    dtype.Set(dummy, "|u1");
723
0
                    break;
724
0
                }
725
0
                case GDT_Int8:
726
0
                {
727
0
                    elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
728
0
                    dtype.Set(dummy, "|i1");
729
0
                    break;
730
0
                }
731
0
                case GDT_UInt16:
732
0
                {
733
0
                    elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
734
0
                    dtype.Set(dummy, "<u2");
735
0
                    break;
736
0
                }
737
0
                case GDT_Int16:
738
0
                {
739
0
                    elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
740
0
                    dtype.Set(dummy, "<i2");
741
0
                    break;
742
0
                }
743
0
                case GDT_UInt32:
744
0
                {
745
0
                    elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
746
0
                    dtype.Set(dummy, "<u4");
747
0
                    break;
748
0
                }
749
0
                case GDT_Int32:
750
0
                {
751
0
                    elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
752
0
                    dtype.Set(dummy, "<i4");
753
0
                    break;
754
0
                }
755
0
                case GDT_UInt64:
756
0
                {
757
0
                    elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
758
0
                    dtype.Set(dummy, "<u8");
759
0
                    break;
760
0
                }
761
0
                case GDT_Int64:
762
0
                {
763
0
                    elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
764
0
                    dtype.Set(dummy, "<i8");
765
0
                    break;
766
0
                }
767
0
                case GDT_Float16:
768
0
                {
769
0
                    elt.nativeType = DtypeElt::NativeType::IEEEFP;
770
0
                    dtype.Set(dummy, "<f2");
771
0
                    break;
772
0
                }
773
0
                case GDT_Float32:
774
0
                {
775
0
                    elt.nativeType = DtypeElt::NativeType::IEEEFP;
776
0
                    dtype.Set(dummy, "<f4");
777
0
                    break;
778
0
                }
779
0
                case GDT_Float64:
780
0
                {
781
0
                    elt.nativeType = DtypeElt::NativeType::IEEEFP;
782
0
                    dtype.Set(dummy, "<f8");
783
0
                    break;
784
0
                }
785
0
                case GDT_Unknown:
786
0
                case GDT_CInt16:
787
0
                case GDT_CInt32:
788
0
                {
789
0
                    bUnsupported = true;
790
0
                    break;
791
0
                }
792
0
                case GDT_CFloat16:
793
0
                {
794
0
                    elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
795
0
                    dtype.Set(dummy, "<c4");
796
0
                    break;
797
0
                }
798
0
                case GDT_CFloat32:
799
0
                {
800
0
                    elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
801
0
                    dtype.Set(dummy, "<c8");
802
0
                    break;
803
0
                }
804
0
                case GDT_CFloat64:
805
0
                {
806
0
                    elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
807
0
                    dtype.Set(dummy, "<c16");
808
0
                    break;
809
0
                }
810
0
                case GDT_TypeCount:
811
0
                {
812
0
                    static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
813
0
                                  "GDT_TypeCount == GDT_CFloat16 + 1");
814
0
                    break;
815
0
                }
816
0
            }
817
0
            if (bUnsupported)
818
0
            {
819
0
                CPLError(CE_Failure, CPLE_NotSupported,
820
0
                         "Unsupported data type: %s", GDALGetDataTypeName(eDT));
821
0
                dtype = CPLJSONObject();
822
0
                dtype.Deinit();
823
0
                return dtype;
824
0
            }
825
0
            elt.nativeOffset = nNativeStartOffset;
826
0
            elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
827
0
            elt.gdalOffset = nGDALStartOffset;
828
0
            elt.gdalSize = elt.nativeSize;
829
#ifdef CPL_MSB
830
            elt.needByteSwapping = elt.nativeSize > 1;
831
#endif
832
0
            aoDtypeElts.emplace_back(elt);
833
0
            break;
834
0
        }
835
836
0
        case GEDTC_COMPOUND:
837
0
        {
838
0
            const auto &comps = oDataType.GetComponents();
839
0
            CPLJSONArray array;
840
0
            for (const auto &comp : comps)
841
0
            {
842
0
                CPLJSONArray subArray;
843
0
                subArray.Add(comp->GetName());
844
0
                const auto subdtype = FillDTypeElts(
845
0
                    comp->GetType(), nGDALStartOffset + comp->GetOffset(),
846
0
                    aoDtypeElts, bUseUnicode);
847
0
                if (!subdtype.IsValid())
848
0
                {
849
0
                    dtype = CPLJSONObject();
850
0
                    dtype.Deinit();
851
0
                    return dtype;
852
0
                }
853
0
                if (subdtype.GetType() == CPLJSONObject::Type::Object)
854
0
                    subArray.Add(subdtype["dummy"]);
855
0
                else
856
0
                    subArray.Add(subdtype);
857
0
                array.Add(subArray);
858
0
            }
859
0
            dtype = std::move(array);
860
0
            break;
861
0
        }
862
0
    }
863
0
    return dtype;
864
0
}
865
866
/************************************************************************/
867
/*                     ZarrV2Group::CreateMDArray()                     */
868
/************************************************************************/
869
870
std::shared_ptr<GDALMDArray> ZarrV2Group::CreateMDArray(
871
    const std::string &osName,
872
    const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
873
    const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
874
0
{
875
0
    if (!CheckValidAndErrorOutIfNot())
876
0
        return nullptr;
877
878
0
    if (!m_bUpdatable)
879
0
    {
880
0
        CPLError(CE_Failure, CPLE_NotSupported,
881
0
                 "Dataset not open in update mode");
882
0
        return nullptr;
883
0
    }
884
0
    if (!IsValidObjectName(osName))
885
0
    {
886
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
887
0
        return nullptr;
888
0
    }
889
890
0
    std::vector<DtypeElt> aoDtypeElts;
891
0
    const bool bUseUnicode =
892
0
        EQUAL(CSLFetchNameValueDef(papszOptions, "STRING_FORMAT", "ASCII"),
893
0
              "UNICODE");
894
0
    const auto dtype = FillDTypeElts(oDataType, 0, aoDtypeElts, bUseUnicode);
895
0
    if (!dtype.IsValid() || aoDtypeElts.empty())
896
0
        return nullptr;
897
898
0
    GetMDArrayNames();
899
900
0
    if (cpl::contains(m_oSetArrayNames, osName))
901
0
    {
902
0
        CPLError(CE_Failure, CPLE_AppDefined,
903
0
                 "An array with same name (%s) already exists in group %s",
904
0
                 osName.c_str(), GetFullName().c_str());
905
0
        return nullptr;
906
0
    }
907
908
0
    CPLJSONObject oCompressor;
909
0
    oCompressor.Deinit();
910
0
    const char *pszCompressor =
911
0
        CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
912
0
    const CPLCompressor *psCompressor = nullptr;
913
0
    const CPLCompressor *psDecompressor = nullptr;
914
0
    if (!EQUAL(pszCompressor, "NONE"))
915
0
    {
916
0
        psCompressor = CPLGetCompressor(pszCompressor);
917
0
        psDecompressor = CPLGetDecompressor(pszCompressor);
918
0
        if (psCompressor == nullptr || psDecompressor == nullptr)
919
0
        {
920
0
            CPLError(CE_Failure, CPLE_NotSupported,
921
0
                     "Compressor/decompressor for %s not available",
922
0
                     pszCompressor);
923
0
            return nullptr;
924
0
        }
925
0
        const char *pszOptions =
926
0
            CSLFetchNameValue(psCompressor->papszMetadata, "OPTIONS");
927
0
        if (pszOptions)
928
0
        {
929
0
            CPLXMLTreeCloser oTree(CPLParseXMLString(pszOptions));
930
0
            const auto psRoot =
931
0
                oTree.get() ? CPLGetXMLNode(oTree.get(), "=Options") : nullptr;
932
0
            if (psRoot)
933
0
            {
934
0
                for (const CPLXMLNode *psNode = psRoot->psChild;
935
0
                     psNode != nullptr; psNode = psNode->psNext)
936
0
                {
937
0
                    if (psNode->eType == CXT_Element &&
938
0
                        strcmp(psNode->pszValue, "Option") == 0)
939
0
                    {
940
0
                        const char *pszName =
941
0
                            CPLGetXMLValue(psNode, "name", nullptr);
942
0
                        const char *pszType =
943
0
                            CPLGetXMLValue(psNode, "type", nullptr);
944
0
                        if (pszName && pszType)
945
0
                        {
946
0
                            const char *pszVal = CSLFetchNameValueDef(
947
0
                                papszOptions,
948
0
                                (std::string(pszCompressor) + '_' + pszName)
949
0
                                    .c_str(),
950
0
                                CPLGetXMLValue(psNode, "default", nullptr));
951
0
                            if (pszVal)
952
0
                            {
953
0
                                if (EQUAL(pszName, "SHUFFLE") &&
954
0
                                    EQUAL(pszVal, "BYTE"))
955
0
                                {
956
0
                                    pszVal = "1";
957
0
                                    pszType = "integer";
958
0
                                }
959
960
0
                                if (!oCompressor.IsValid())
961
0
                                {
962
0
                                    oCompressor = CPLJSONObject();
963
0
                                    oCompressor.Add(
964
0
                                        "id",
965
0
                                        CPLString(pszCompressor).tolower());
966
0
                                }
967
968
0
                                std::string osOptName(
969
0
                                    CPLString(pszName).tolower());
970
0
                                if (STARTS_WITH(pszType, "int"))
971
0
                                    oCompressor.Add(osOptName, atoi(pszVal));
972
0
                                else
973
0
                                    oCompressor.Add(osOptName, pszVal);
974
0
                            }
975
0
                        }
976
0
                    }
977
0
                }
978
0
            }
979
0
        }
980
0
    }
981
982
0
    CPLJSONArray oFilters;
983
0
    const char *pszFilter =
984
0
        CSLFetchNameValueDef(papszOptions, "FILTER", "NONE");
985
0
    if (!EQUAL(pszFilter, "NONE"))
986
0
    {
987
0
        const auto psFilterCompressor = CPLGetCompressor(pszFilter);
988
0
        const auto psFilterDecompressor = CPLGetCompressor(pszFilter);
989
0
        if (psFilterCompressor == nullptr || psFilterDecompressor == nullptr)
990
0
        {
991
0
            CPLError(CE_Failure, CPLE_NotSupported,
992
0
                     "Compressor/decompressor for filter %s not available",
993
0
                     pszFilter);
994
0
            return nullptr;
995
0
        }
996
997
0
        CPLJSONObject oFilter;
998
0
        oFilter.Add("id", CPLString(pszFilter).tolower());
999
0
        oFilters.Add(oFilter);
1000
1001
0
        const char *pszOptions =
1002
0
            CSLFetchNameValue(psFilterCompressor->papszMetadata, "OPTIONS");
1003
0
        if (pszOptions)
1004
0
        {
1005
0
            CPLXMLTreeCloser oTree(CPLParseXMLString(pszOptions));
1006
0
            const auto psRoot =
1007
0
                oTree.get() ? CPLGetXMLNode(oTree.get(), "=Options") : nullptr;
1008
0
            if (psRoot)
1009
0
            {
1010
0
                for (const CPLXMLNode *psNode = psRoot->psChild;
1011
0
                     psNode != nullptr; psNode = psNode->psNext)
1012
0
                {
1013
0
                    if (psNode->eType == CXT_Element &&
1014
0
                        strcmp(psNode->pszValue, "Option") == 0)
1015
0
                    {
1016
0
                        const char *pszName =
1017
0
                            CPLGetXMLValue(psNode, "name", nullptr);
1018
0
                        const char *pszType =
1019
0
                            CPLGetXMLValue(psNode, "type", nullptr);
1020
0
                        if (pszName && pszType)
1021
0
                        {
1022
0
                            const char *pszVal = CSLFetchNameValueDef(
1023
0
                                papszOptions,
1024
0
                                (std::string(pszFilter) + '_' + pszName)
1025
0
                                    .c_str(),
1026
0
                                CPLGetXMLValue(psNode, "default", nullptr));
1027
0
                            if (pszVal)
1028
0
                            {
1029
0
                                std::string osOptName(
1030
0
                                    CPLString(pszName).tolower());
1031
0
                                if (STARTS_WITH(pszType, "int"))
1032
0
                                    oFilter.Add(osOptName, atoi(pszVal));
1033
0
                                else
1034
0
                                    oFilter.Add(osOptName, pszVal);
1035
0
                            }
1036
0
                        }
1037
0
                    }
1038
0
                }
1039
0
            }
1040
0
        }
1041
1042
0
        if (EQUAL(pszFilter, "delta") &&
1043
0
            CSLFetchNameValue(papszOptions, "DELTA_DTYPE") == nullptr)
1044
0
        {
1045
0
            if (oDataType.GetClass() != GEDTC_NUMERIC)
1046
0
            {
1047
0
                CPLError(CE_Failure, CPLE_NotSupported,
1048
0
                         "DELTA_DTYPE option must be specified");
1049
0
                return nullptr;
1050
0
            }
1051
0
            switch (oDataType.GetNumericDataType())
1052
0
            {
1053
0
                case GDT_Unknown:
1054
0
                    break;
1055
0
                case GDT_UInt8:
1056
0
                    oFilter.Add("dtype", "u1");
1057
0
                    break;
1058
0
                case GDT_Int8:
1059
0
                    oFilter.Add("dtype", "i1");
1060
0
                    break;
1061
0
                case GDT_UInt16:
1062
0
                    oFilter.Add("dtype", "<u2");
1063
0
                    break;
1064
0
                case GDT_Int16:
1065
0
                    oFilter.Add("dtype", "<i2");
1066
0
                    break;
1067
0
                case GDT_UInt32:
1068
0
                    oFilter.Add("dtype", "<u4");
1069
0
                    break;
1070
0
                case GDT_Int32:
1071
0
                    oFilter.Add("dtype", "<i4");
1072
0
                    break;
1073
0
                case GDT_UInt64:
1074
0
                    oFilter.Add("dtype", "<u8");
1075
0
                    break;
1076
0
                case GDT_Int64:
1077
0
                    oFilter.Add("dtype", "<i8");
1078
0
                    break;
1079
0
                case GDT_Float16:
1080
0
                    oFilter.Add("dtype", "<f2");
1081
0
                    break;
1082
0
                case GDT_Float32:
1083
0
                    oFilter.Add("dtype", "<f4");
1084
0
                    break;
1085
0
                case GDT_Float64:
1086
0
                    oFilter.Add("dtype", "<f8");
1087
0
                    break;
1088
0
                case GDT_CInt16:
1089
0
                    oFilter.Add("dtype", "<i2");
1090
0
                    break;
1091
0
                case GDT_CInt32:
1092
0
                    oFilter.Add("dtype", "<i4");
1093
0
                    break;
1094
0
                case GDT_CFloat16:
1095
0
                    oFilter.Add("dtype", "<f2");
1096
0
                    break;
1097
0
                case GDT_CFloat32:
1098
0
                    oFilter.Add("dtype", "<f4");
1099
0
                    break;
1100
0
                case GDT_CFloat64:
1101
0
                    oFilter.Add("dtype", "<f8");
1102
0
                    break;
1103
0
                case GDT_TypeCount:
1104
0
                    break;
1105
0
            }
1106
0
        }
1107
0
    }
1108
1109
0
    const std::string osZarrayDirectory =
1110
0
        CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
1111
0
    if (VSIMkdir(osZarrayDirectory.c_str(), 0755) != 0)
1112
0
    {
1113
0
        VSIStatBufL sStat;
1114
0
        if (VSIStatL(osZarrayDirectory.c_str(), &sStat) == 0)
1115
0
        {
1116
0
            CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
1117
0
                     osZarrayDirectory.c_str());
1118
0
        }
1119
0
        else
1120
0
        {
1121
0
            CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
1122
0
                     osZarrayDirectory.c_str());
1123
0
        }
1124
0
        return nullptr;
1125
0
    }
1126
1127
0
    std::vector<GUInt64> anBlockSize;
1128
0
    if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anBlockSize,
1129
0
                                  papszOptions))
1130
0
        return nullptr;
1131
1132
0
    const bool bFortranOrder = EQUAL(
1133
0
        CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
1134
1135
0
    const char *pszDimSeparator =
1136
0
        CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", ".");
1137
1138
0
    auto poArray =
1139
0
        ZarrV2Array::Create(m_poSharedResource, Self(), osName, aoDimensions,
1140
0
                            oDataType, aoDtypeElts, anBlockSize, bFortranOrder);
1141
1142
0
    if (!poArray)
1143
0
        return nullptr;
1144
0
    const std::string osZarrayFilename =
1145
0
        CPLFormFilenameSafe(osZarrayDirectory.c_str(), ".zarray", nullptr);
1146
0
    poArray->SetNew(true);
1147
0
    poArray->SetFilename(osZarrayFilename);
1148
0
    poArray->SetDimSeparator(pszDimSeparator);
1149
0
    poArray->SetDtype(dtype);
1150
0
    poArray->SetCompressorDecompressor(pszCompressor, psCompressor,
1151
0
                                       psDecompressor);
1152
0
    if (oCompressor.IsValid())
1153
0
        poArray->SetCompressorJson(oCompressor);
1154
0
    poArray->SetFilters(oFilters);
1155
0
    poArray->SetCreationOptions(papszOptions);
1156
0
    poArray->SetUpdatable(true);
1157
0
    poArray->SetDefinitionModified(true);
1158
0
    if (!cpl::starts_with(osZarrayFilename, "/vsi") && !poArray->Flush())
1159
0
        return nullptr;
1160
0
    RegisterArray(poArray);
1161
1162
0
    return poArray;
1163
0
}