Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/zarr/zarr_v3_codec_sequence.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Zarr driver, ZarrV3CodecSequence class
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "zarr_v3_codec.h"
14
15
/************************************************************************/
16
/*                     ZarrV3CodecSequence::Clone()                     */
17
/************************************************************************/
18
19
std::unique_ptr<ZarrV3CodecSequence> ZarrV3CodecSequence::Clone() const
20
0
{
21
0
    auto poClone = std::make_unique<ZarrV3CodecSequence>(m_oInputArrayMetadata);
22
0
    for (const auto &poCodec : m_apoCodecs)
23
0
        poClone->m_apoCodecs.emplace_back(poCodec->Clone());
24
0
    poClone->m_oCodecArray = m_oCodecArray.Clone();
25
0
    poClone->m_bPartialDecodingPossible = m_bPartialDecodingPossible;
26
0
    return poClone;
27
0
}
28
29
/************************************************************************/
30
/*                 ZarrV3CodecSequence::InitFromJson()                  */
31
/************************************************************************/
32
33
bool ZarrV3CodecSequence::InitFromJson(const std::string &osArrayName,
34
                                       const CPLJSONObject &oCodecs,
35
                                       ZarrArrayMetadata &oOutputArrayMetadata)
36
166
{
37
166
    if (oCodecs.GetType() != CPLJSONObject::Type::Array)
38
0
    {
39
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s: codecs is not an array",
40
0
                 osArrayName.c_str());
41
0
        return false;
42
0
    }
43
166
    auto oCodecsArray = oCodecs.ToArray();
44
45
166
    ZarrArrayMetadata oInputArrayMetadata = m_oInputArrayMetadata;
46
166
    ZarrV3Codec::IOType eLastType = ZarrV3Codec::IOType::ARRAY;
47
166
    std::string osLastCodec;
48
49
166
    const auto InsertImplicitEndianCodecIfNeeded =
50
166
        [this, &oInputArrayMetadata, &eLastType, &osLastCodec]()
51
166
    {
52
166
        CPL_IGNORE_RET_VAL(this);
53
166
        if (eLastType == ZarrV3Codec::IOType::ARRAY &&
54
0
            oInputArrayMetadata.oElt.nativeSize > 1 &&
55
0
            oInputArrayMetadata.oElt.nativeType !=
56
0
                DtypeElt::NativeType::STRING_ASCII &&
57
0
            oInputArrayMetadata.oElt.nativeType !=
58
0
                DtypeElt::NativeType::STRING_UNICODE)
59
0
        {
60
0
            CPLError(CE_Warning, CPLE_AppDefined,
61
0
                     "'bytes' codec missing. Assuming little-endian storage, "
62
0
                     "but such tolerance may be removed in future versions");
63
0
            auto poEndianCodec = std::make_unique<ZarrV3CodecBytes>();
64
0
            ZarrArrayMetadata oTmpOutputArrayMetadata;
65
0
            poEndianCodec->InitFromConfiguration(
66
0
                std::string(), ZarrV3CodecBytes::GetConfiguration(true),
67
0
                oInputArrayMetadata, oTmpOutputArrayMetadata,
68
0
                /* bEmitWarnings = */ true);
69
0
            oInputArrayMetadata = std::move(oTmpOutputArrayMetadata);
70
0
            eLastType = poEndianCodec->GetOutputType();
71
0
            osLastCodec = poEndianCodec->GetName();
72
            if constexpr (!CPL_IS_LSB)
73
            {
74
                // Insert a little endian codec if we are on a big endian target
75
                m_apoCodecs.emplace_back(std::move(poEndianCodec));
76
            }
77
0
        }
78
166
    };
79
80
166
    bool bShardingFound = false;
81
166
    std::vector<size_t> anBlockSizesBeforeSharding;
82
166
    for (const auto &oCodec : oCodecsArray)
83
166
    {
84
166
        if (oCodec.GetType() != CPLJSONObject::Type::Object)
85
0
        {
86
0
            CPLError(CE_Failure, CPLE_AppDefined, "codecs[] is not an object");
87
0
            return false;
88
0
        }
89
166
        const auto osName = oCodec["name"].ToString();
90
166
        std::unique_ptr<ZarrV3Codec> poCodec;
91
166
        if (osName == ZarrV3CodecGZip::NAME)
92
0
            poCodec = std::make_unique<ZarrV3CodecGZip>();
93
166
        else if (osName == ZarrV3CodecBlosc::NAME)
94
0
            poCodec = std::make_unique<ZarrV3CodecBlosc>();
95
166
        else if (osName == ZarrV3CodecZstd::NAME)
96
0
            poCodec = std::make_unique<ZarrV3CodecZstd>();
97
166
        else if (osName == ZarrV3CodecBytes::NAME ||
98
0
                 osName == "endian" /* endian is the old name */)
99
166
            poCodec = std::make_unique<ZarrV3CodecBytes>();
100
0
        else if (osName == ZarrV3CodecTranspose::NAME)
101
0
            poCodec = std::make_unique<ZarrV3CodecTranspose>();
102
0
        else if (osName == ZarrV3CodecCRC32C::NAME)
103
0
            poCodec = std::make_unique<ZarrV3CodecCRC32C>();
104
0
        else if (osName == ZarrV3CodecVLenUTF8::NAME)
105
0
            poCodec = std::make_unique<ZarrV3CodecVLenUTF8>();
106
0
        else if (osName == ZarrV3CodecShardingIndexed::NAME)
107
0
        {
108
0
            bShardingFound = true;
109
0
            poCodec = std::make_unique<ZarrV3CodecShardingIndexed>();
110
0
        }
111
0
        else if (osName == ZarrV3CodecPcodec::NAME)
112
0
        {
113
#ifdef HAVE_PCODEC
114
            constexpr bool bHasPcodec = true;
115
#else
116
0
            constexpr bool bHasPcodec = false;
117
0
#endif
118
            if constexpr (bHasPcodec)
119
            {
120
                poCodec = std::make_unique<ZarrV3CodecPcodec>();
121
            }
122
            else
123
0
            {
124
0
                CPLError(
125
0
                    CE_Failure, CPLE_NotSupported,
126
0
                    "%s: Codec '%s' is supported by GDAL, but not in this "
127
0
                    "particular build. Requires building GDAL with "
128
0
                    "-DGDAL_USE_PCODEC=ON and with a Rust toolchain available",
129
0
                    osArrayName.c_str(), osName.c_str());
130
0
                return false;
131
0
            }
132
0
        }
133
0
        else
134
0
        {
135
0
            CPLError(CE_Failure, CPLE_NotSupported, "%s: Unsupported codec: %s",
136
0
                     osArrayName.c_str(), osName.c_str());
137
0
            return false;
138
0
        }
139
140
166
        if (poCodec->GetInputType() == ZarrV3Codec::IOType::ARRAY)
141
166
        {
142
166
            if (eLastType == ZarrV3Codec::IOType::BYTES)
143
0
            {
144
0
                CPLError(CE_Failure, CPLE_AppDefined,
145
0
                         "Cannot chain codec %s with %s",
146
0
                         poCodec->GetName().c_str(), osLastCodec.c_str());
147
0
                return false;
148
0
            }
149
166
        }
150
0
        else
151
0
        {
152
0
            InsertImplicitEndianCodecIfNeeded();
153
0
        }
154
155
166
        ZarrArrayMetadata oStepOutputArrayMetadata;
156
166
        if (osName == ZarrV3CodecShardingIndexed::NAME)
157
0
        {
158
0
            anBlockSizesBeforeSharding = oInputArrayMetadata.anBlockSizes;
159
0
        }
160
166
        if (!poCodec->InitFromConfiguration(
161
166
                osArrayName, oCodec["configuration"], oInputArrayMetadata,
162
166
                oStepOutputArrayMetadata,
163
166
                /* bEmitWarnings = */ true))
164
0
        {
165
0
            return false;
166
0
        }
167
166
        oInputArrayMetadata = std::move(oStepOutputArrayMetadata);
168
166
        eLastType = poCodec->GetOutputType();
169
166
        osLastCodec = poCodec->GetName();
170
171
166
        if (!poCodec->IsNoOp())
172
0
            m_apoCodecs.emplace_back(std::move(poCodec));
173
166
    }
174
175
166
    if (bShardingFound)
176
0
    {
177
0
        m_bPartialDecodingPossible =
178
0
            (m_apoCodecs.back()->GetName() == ZarrV3CodecShardingIndexed::NAME);
179
0
        if (!m_bPartialDecodingPossible)
180
0
        {
181
0
            m_bPartialDecodingPossible = false;
182
            // This is not an implementation limitation, but the result of a
183
            // badly thought dataset. Zarr-Python also emits a similar warning.
184
0
            CPLError(
185
0
                CE_Warning, CPLE_AppDefined,
186
0
                "Sharding codec found, but not in last position. Consequently "
187
0
                "partial shard decoding will not be possible");
188
0
            oInputArrayMetadata.anBlockSizes =
189
0
                std::move(anBlockSizesBeforeSharding);
190
0
        }
191
0
    }
192
193
166
    InsertImplicitEndianCodecIfNeeded();
194
195
166
    m_oCodecArray = oCodecs.Clone();
196
166
    oOutputArrayMetadata = std::move(oInputArrayMetadata);
197
166
    return true;
198
166
}
199
200
/************************************************************************/
201
/*                  ZarrV3CodecBytes::AllocateBuffer()                  */
202
/************************************************************************/
203
204
bool ZarrV3CodecSequence::AllocateBuffer(ZarrByteVectorQuickResize &abyBuffer,
205
                                         size_t nEltCount)
206
37.7k
{
207
37.7k
    if (!m_apoCodecs.empty())
208
0
    {
209
0
        const size_t nRawSize =
210
0
            nEltCount * m_oInputArrayMetadata.oElt.nativeSize;
211
        // Grow the temporary buffer a bit beyond the uncompressed size
212
0
        const size_t nMaxSize = nRawSize + nRawSize / 3 + 64;
213
0
        try
214
0
        {
215
0
            m_abyTmp.resize(nMaxSize);
216
0
        }
217
0
        catch (const std::exception &e)
218
0
        {
219
0
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
220
0
            return false;
221
0
        }
222
0
        m_abyTmp.resize(nRawSize);
223
224
        // Grow the input/output buffer too if we have several steps
225
0
        if (m_apoCodecs.size() >= 2 && abyBuffer.capacity() < nMaxSize)
226
0
        {
227
0
            const size_t nSize = abyBuffer.size();
228
0
            try
229
0
            {
230
0
                abyBuffer.resize(nMaxSize);
231
0
            }
232
0
            catch (const std::exception &e)
233
0
            {
234
0
                CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
235
0
                return false;
236
0
            }
237
0
            abyBuffer.resize(nSize);
238
0
        }
239
0
    }
240
37.7k
    return true;
241
37.7k
}
242
243
/************************************************************************/
244
/*                    ZarrV3CodecSequence::Encode()                     */
245
/************************************************************************/
246
247
bool ZarrV3CodecSequence::Encode(ZarrByteVectorQuickResize &abyBuffer)
248
37.7k
{
249
37.7k
    if (!AllocateBuffer(abyBuffer,
250
37.7k
                        MultiplyElements(m_oInputArrayMetadata.anBlockSizes)))
251
0
        return false;
252
37.7k
    for (const auto &poCodec : m_apoCodecs)
253
0
    {
254
0
        if (!poCodec->Encode(abyBuffer, m_abyTmp))
255
0
            return false;
256
0
        std::swap(abyBuffer, m_abyTmp);
257
0
    }
258
37.7k
    return true;
259
37.7k
}
260
261
/************************************************************************/
262
/*                    ZarrV3CodecSequence::Decode()                     */
263
/************************************************************************/
264
265
bool ZarrV3CodecSequence::Decode(ZarrByteVectorQuickResize &abyBuffer)
266
0
{
267
0
    if (!AllocateBuffer(abyBuffer,
268
0
                        MultiplyElements(m_oInputArrayMetadata.anBlockSizes)))
269
0
        return false;
270
0
    for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
271
0
    {
272
0
        const auto &poCodec = *iter;
273
0
        if (!poCodec->Decode(abyBuffer, m_abyTmp))
274
0
            return false;
275
0
        std::swap(abyBuffer, m_abyTmp);
276
0
    }
277
0
    return true;
278
0
}
279
280
/************************************************************************/
281
/*                 ZarrV3CodecSequence::DecodePartial()                 */
282
/************************************************************************/
283
284
bool ZarrV3CodecSequence::DecodePartial(VSIVirtualHandle *poFile,
285
                                        ZarrByteVectorQuickResize &abyBuffer,
286
                                        const std::vector<size_t> &anStartIdxIn,
287
                                        const std::vector<size_t> &anCountIn)
288
0
{
289
0
    CPLAssert(anStartIdxIn.size() == m_oInputArrayMetadata.anBlockSizes.size());
290
0
    CPLAssert(anStartIdxIn.size() == anCountIn.size());
291
292
0
    if (!AllocateBuffer(abyBuffer, MultiplyElements(anCountIn)))
293
0
        return false;
294
295
    // anStartIdxIn and anCountIn are expressed in the shape *before* encoding
296
    // We need to apply the potential transpositions before submitting them
297
    // to the decoder of the Array->Bytes decoder
298
0
    std::vector<size_t> anStartIdx(anStartIdxIn);
299
0
    std::vector<size_t> anCount(anCountIn);
300
0
    for (auto &poCodec : m_apoCodecs)
301
0
    {
302
0
        poCodec->ChangeArrayShapeForward(anStartIdx, anCount);
303
0
    }
304
305
0
    for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
306
0
    {
307
0
        const auto &poCodec = *iter;
308
309
0
        if (!poCodec->DecodePartial(poFile, abyBuffer, m_abyTmp, anStartIdx,
310
0
                                    anCount))
311
0
            return false;
312
0
        std::swap(abyBuffer, m_abyTmp);
313
0
    }
314
0
    return true;
315
0
}
316
317
/************************************************************************/
318
/*              ZarrV3CodecSequence::BatchDecodePartial()               */
319
/************************************************************************/
320
321
bool ZarrV3CodecSequence::BatchDecodePartial(
322
    VSIVirtualHandle *poFile, const char *pszFilename,
323
    const std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>>
324
        &anRequests,
325
    std::vector<ZarrByteVectorQuickResize> &aResults)
326
0
{
327
    // Only batch-decode when sharding is the sole codec. If other codecs
328
    // (e.g. transpose) precede it, indices and output need codec-specific
329
    // transformations that BatchDecodePartial does not handle.
330
0
    if (m_apoCodecs.size() == 1)
331
0
    {
332
0
        auto *poSharding = dynamic_cast<ZarrV3CodecShardingIndexed *>(
333
0
            m_apoCodecs.back().get());
334
0
        if (poSharding)
335
0
        {
336
0
            return poSharding->BatchDecodePartial(poFile, pszFilename,
337
0
                                                  anRequests, aResults);
338
0
        }
339
0
    }
340
341
    // Fallback: sequential DecodePartial for non-sharding codec chains
342
0
    aResults.resize(anRequests.size());
343
0
    for (size_t i = 0; i < anRequests.size(); ++i)
344
0
    {
345
0
        if (!DecodePartial(poFile, aResults[i], anRequests[i].first,
346
0
                           anRequests[i].second))
347
0
            return false;
348
0
    }
349
0
    return true;
350
0
}
351
352
/************************************************************************/
353
/*             ZarrV3CodecSequence::GetInnerMostBlockSize()             */
354
/************************************************************************/
355
356
std::vector<size_t> ZarrV3CodecSequence::GetInnerMostBlockSize(
357
    const std::vector<size_t> &anOuterBlockSize) const
358
166
{
359
166
    auto chunkSize = anOuterBlockSize;
360
166
    for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
361
0
    {
362
0
        const auto &poCodec = *iter;
363
0
        if (m_bPartialDecodingPossible ||
364
0
            poCodec->GetName() != ZarrV3CodecShardingIndexed::NAME)
365
0
        {
366
0
            chunkSize = poCodec->GetInnerMostBlockSize(chunkSize);
367
0
        }
368
0
    }
369
166
    return chunkSize;
370
166
}