libzarr
Header-only C++17 Zarr v2/v3, WASM-compatible
Loading...
Searching...
No Matches
v2.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2
3#ifndef LIBZARR_V2_HPP
4#define LIBZARR_V2_HPP
5
6#include <cstdint>
7#include <cstdlib>
8#include <limits>
9#include <optional>
10#include <string>
11#include <vector>
12
13#include "libzarr/detail/common.hpp"
14#include "libzarr/metadata.hpp"
15#include "libzarr/store.hpp"
16#include "libzarr/types.hpp"
17
23
24namespace zarr::v2 {
25
27inline constexpr const char* kArraySuffix = ".zarray";
29inline constexpr const char* kGroupSuffix = ".zgroup";
31inline constexpr const char* kAttrsSuffix = ".zattrs";
33inline constexpr const char* kConsolidatedKey = ".zmetadata";
34
36[[nodiscard]] inline std::string meta_key(const std::string& path, const char* suffix) {
37 return path.empty() ? suffix : path + "/" + suffix;
38}
39
40// ---- dtype ------------------------------------------------------------------
41
47 bool big_endian = false;
48};
49
50namespace detail_v2 {
51
52template <typename Fail>
53DataType int_dtype(bool is_signed, std::uint64_t size, const Fail& fail) {
54 DType kind{};
55 if (size == 1) {
56 kind = is_signed ? DType::int8 : DType::uint8;
57 } else if (size == 2) {
58 kind = is_signed ? DType::int16 : DType::uint16;
59 } else if (size == 4) {
60 kind = is_signed ? DType::int32 : DType::uint32;
61 } else if (size == 8) {
62 kind = is_signed ? DType::int64 : DType::uint64;
63 } else {
64 fail("integer size must be 1, 2, 4 or 8");
65 }
66 return DataType::of(kind);
67}
68
71template <typename Fail>
72DataType dtype_of_code(char code, std::uint64_t size, const Fail& fail) {
73 switch (code) {
74 case 'b':
75 if (size != 1) {
76 fail("bool must have size 1");
77 }
78 return DataType::of(DType::boolean);
79 case 'i':
80 case 'u':
81 return int_dtype(code == 'i', size, fail);
82 case 'f':
83 if (size == 2) {
84 return DataType::of(DType::float16);
85 }
86 if (size == 4) {
87 return DataType::of(DType::float32);
88 }
89 if (size == 8) {
90 return DataType::of(DType::float64);
91 }
92 fail("float size must be 2, 4 or 8");
93 break;
94 case 'V':
95 if (size == 0) {
96 fail("raw dtype must have a positive size");
97 }
98 return DataType::raw_bytes(static_cast<std::uint32_t>(size));
99 case 'c':
100 if (size == 8) {
101 return DataType::of(DType::complex64);
102 }
103 if (size == 16) {
104 return DataType::of(DType::complex128);
105 }
106 fail("complex size must be 8 or 16");
107 break;
108 case 'S':
109 case 'U':
110 fail("string dtypes are not supported");
111 break;
112 case 'm':
113 case 'M':
114 fail("datetime dtypes are not supported");
115 break;
116 default:
117 fail("unknown type code");
118 }
119 return {}; // unreachable: fail() always throws
120}
121
122} // namespace detail_v2
123
126[[nodiscard]] inline ParsedDType parse_data_type(const std::string& text, const std::string& ctx) {
127 const auto fail = [&](const char* why) { throw error(ctx + ": dtype '" + text + "': " + why); };
128 if (text.size() < 3) {
129 fail("expected the form '<f8', '|u1', ...");
130 }
131 const char order = text[0];
132 if (order != '<' && order != '>' && order != '|') {
133 fail("byte order must be '<', '>' or '|'");
134 }
135 std::uint64_t size = 0;
136 for (std::size_t i = 2; i < text.size(); ++i) {
137 if (text[i] < '0' || text[i] > '9' || size > 0xFFFFFF) {
138 fail("malformed item size");
139 }
140 size = size * 10 + static_cast<std::uint64_t>(text[i] - '0');
141 }
142 const DataType dtype = detail_v2::dtype_of_code(text[1], size, fail);
143 // Byte order only matters for multi-byte components; bool/raw never swap.
144 const bool multi_byte = dtype.kind != DType::boolean && dtype.kind != DType::raw && size > 1;
145 return {dtype, order == '>' && multi_byte};
146}
147
151[[nodiscard]] inline std::string emit_data_type(DataType dt, bool big_endian) {
152 const char order_multi = big_endian ? '>' : '<';
153 switch (dt.kind) {
154 case DType::boolean:
155 return "|b1";
156 case DType::int8:
157 return "|i1";
158 case DType::uint8:
159 return "|u1";
160 case DType::int16:
161 return std::string(1, order_multi) + "i2";
162 case DType::int32:
163 return std::string(1, order_multi) + "i4";
164 case DType::int64:
165 return std::string(1, order_multi) + "i8";
166 case DType::uint16:
167 return std::string(1, order_multi) + "u2";
168 case DType::uint32:
169 return std::string(1, order_multi) + "u4";
170 case DType::uint64:
171 return std::string(1, order_multi) + "u8";
172 case DType::float16:
173 return std::string(1, order_multi) + "f2";
174 case DType::float32:
175 return std::string(1, order_multi) + "f4";
176 case DType::float64:
177 return std::string(1, order_multi) + "f8";
178 case DType::complex64:
179 return std::string(1, order_multi) + "c8";
180 case DType::complex128:
181 return std::string(1, order_multi) + "c16";
182 case DType::raw:
183 return "|V" + std::to_string(dt.itemsize);
184 default:
185 throw error("v2 emission not implemented for this dtype");
186 }
187}
188
189// ---- fill_value ---------------------------------------------------------------
190
193[[nodiscard]] inline std::optional<Bytes> parse_fill(const json& v, DataType dt,
194 const std::string& ctx);
195
196namespace detail_v2 {
197
198inline std::optional<Bytes> non_finite_fill(const std::string& s, DType kind) {
199 // v2 spec: "NaN", "Infinity" and "-Infinity" are the sanctioned string
200 // encodings of non-finite floats. "+Infinity" appears in the wild (it is
201 // the v3 spelling); accept it on read.
202 if (s == "NaN") {
203 return detail::quiet_nan_bytes(kind);
204 }
205 if (s == "Infinity" || s == "+Infinity") {
206 return detail::infinity_bytes(kind, false);
207 }
208 if (s == "-Infinity") {
209 return detail::infinity_bytes(kind, true);
210 }
211 return std::nullopt;
212}
213
216inline Bytes complex_component_fill(const json& v, DType kind, const std::string& ctx) {
217 if (v.is_number()) {
218 return detail::fill_from_double(v.get<double>(), DataType::of(kind), ctx);
219 }
220 if (v.is_string()) {
221 if (auto fill = non_finite_fill(v.get<std::string>(), kind)) {
222 return *std::move(fill);
223 }
224 }
225 throw error(ctx + ": cannot interpret complex fill_value component " + v.dump());
226}
227
230inline std::optional<Bytes> numeric_string_fill(const std::string& s, DataType dt,
231 const std::string& ctx) {
232 if (s.empty()) {
233 return std::nullopt;
234 }
235 const char* begin = s.c_str();
236 char* end = nullptr;
237 if (is_float(dt.kind)) {
238 const double d = std::strtod(begin, &end);
239 if (end == begin + s.size()) {
240 return detail::fill_from_double(d, dt, ctx);
241 }
242 } else if (is_signed_int(dt.kind) || dt.kind == DType::boolean) {
243 const long long i = std::strtoll(begin, &end, 10);
244 if (end == begin + s.size()) {
245 return detail::fill_from_int(static_cast<std::int64_t>(i), dt, ctx);
246 }
247 } else if (is_unsigned_int(dt.kind) && s[0] != '-') {
248 const unsigned long long u = std::strtoull(begin, &end, 10);
249 if (end == begin + s.size()) {
250 return detail::fill_from_uint(static_cast<std::uint64_t>(u), dt, ctx);
251 }
252 }
253 return std::nullopt;
254}
255
256inline Bytes string_fill(const std::string& s, DataType dt, const std::string& ctx) {
257 if (is_float(dt.kind)) {
258 if (auto fill = non_finite_fill(s, dt.kind)) {
259 return *std::move(fill);
260 }
261 }
262 if (dt.kind == DType::raw) {
263 // v2 spec: raw ("V") fill values are base64-encoded.
264 Bytes decoded = detail::base64_decode(s, (ctx + ": fill_value").c_str());
265 if (decoded.size() != dt.itemsize) {
266 throw error(ctx + ": base64 fill_value decodes to " + std::to_string(decoded.size()) +
267 " bytes, dtype needs " + std::to_string(dt.itemsize));
268 }
269 return decoded;
270 }
271 if (auto fill = numeric_string_fill(s, dt, ctx)) {
272 return *std::move(fill);
273 }
274 throw error(ctx + ": cannot interpret fill_value '" + s + "' for this dtype");
275}
276
277} // namespace detail_v2
278
279[[nodiscard]] inline std::optional<Bytes> parse_fill(const json& v, DataType dt,
280 const std::string& ctx) {
281 if (v.is_null()) {
282 return std::nullopt; // v2 spec: null = fill value undefined (reads as zeros)
283 }
284 if (v.is_array()) {
285 if (is_complex(dt.kind) && v.size() == 2) {
286 // zarr-python encodes v2 complex fills as [re, im], like v3.
287 const DType component = dt.kind == DType::complex64 ? DType::float32 : DType::float64;
288 Bytes out = detail_v2::complex_component_fill(v[0], component, ctx);
289 const Bytes imag = detail_v2::complex_component_fill(v[1], component, ctx);
290 out.insert(out.end(), imag.begin(), imag.end());
291 return out;
292 }
293 // NCZarr 4.8.0 wraps fill_value in a 1-element array (read tolerance).
294 if (v.size() == 1) {
295 return parse_fill(v[0], dt, ctx);
296 }
297 throw error(ctx + ": fill_value must be a scalar, got an array of " + std::to_string(v.size()));
298 }
299 if (v.is_boolean()) {
300 if (dt.kind != DType::boolean) {
301 throw error(ctx + ": boolean fill_value for non-bool dtype");
302 }
303 return detail::scalar_bytes<std::uint8_t>(v.get<bool>() ? 1 : 0);
304 }
305 if (v.is_string()) {
306 return detail_v2::string_fill(v.get<std::string>(), dt, ctx);
307 }
308 if (v.is_number_unsigned()) {
309 return detail::fill_from_uint(v.get<std::uint64_t>(), dt, ctx);
310 }
311 if (v.is_number_integer()) {
312 return detail::fill_from_int(v.get<std::int64_t>(), dt, ctx);
313 }
314 if (v.is_number_float()) {
315 return detail::fill_from_double(v.get<double>(), dt, ctx);
316 }
317 throw error(ctx + ": unsupported fill_value " + v.dump());
318}
319
320// ---- .zarray ----------------------------------------------------------------
321
325namespace detail_v2 {
326
327inline char parse_separator(const json& j, const std::string& ctx) {
328 const auto it = j.find("dimension_separator");
329 if (it == j.end()) {
330 return '.';
331 }
332 if (!it->is_string() || (it->get<std::string>() != "." && it->get<std::string>() != "/")) {
333 throw error(ctx + R"(: 'dimension_separator' must be "." or "/")");
334 }
335 return it->get<std::string>()[0];
336}
337
341inline std::vector<CodecSpec> parse_filters(const json& j, const std::string& ctx) {
342 std::vector<CodecSpec> out;
343 const auto it = j.find("filters");
344 if (it == j.end() || it->is_null()) {
345 return out;
346 }
347 // Tolerance: no-filters is canonically null, but [] appears in the wild.
348 if (!it->is_array()) {
349 throw error(ctx + ": 'filters' must be null or an array");
350 }
351 for (const json& f : *it) {
352 if (!f.is_object() || !f.contains("id") || !f["id"].is_string()) {
353 throw error(ctx + ": each filter must be an object with an 'id'");
354 }
355 const auto id = f["id"].get<std::string>();
356 if (id == "shuffle") {
357 // NCZarr 4.9.x writes elementsize "0" (a string, and zero) meaning
358 // "the dtype's item size"; the resolved size is filled in at codec
359 // resolution.
360 const std::int64_t elementsize = detail::lenient_int(f, "elementsize", 0, ctx);
361 out.push_back(CodecSpec{"shuffle", {{"elementsize", elementsize}}});
362 } else {
363 std::string msg = ctx;
364 msg += ": unsupported v2 filter '";
365 msg += id;
366 msg += "'";
367 throw error(msg);
368 }
369 }
370 return out;
371}
372
374inline std::optional<CodecSpec> parse_compressor(const json& j, const std::string& ctx) {
375 const auto it = j.find("compressor");
376 if (it == j.end() || it->is_null()) {
377 return std::nullopt;
378 }
379 if (!it->is_object() || !it->contains("id") || !(*it)["id"].is_string()) {
380 throw error(ctx + ": 'compressor' must be null or an object with an 'id'");
381 }
382 const auto id = (*it)["id"].get<std::string>();
383 if (id == "zlib" || id == "gzip") {
384 // numcodecs defaults level to 1 when absent.
385 const std::int64_t level = detail::lenient_int(*it, "level", 1, ctx);
386 if (level < 0 || level > 9) {
387 throw error(ctx + ": compressor level must be in 0..9");
388 }
389 return CodecSpec{id, {{"level", level}}};
390 }
391 if (id == "blosc") {
392 // numcodecs Blosc: numeric shuffle (-1 = auto), no typesize member (the
393 // dtype's itemsize applies). Values are validated at codec resolution.
394 // Evaluated before the braced list: a .value() throw during json
395 // initializer-list construction leaks json_ref temporaries (fuzz+LSan).
396 const std::string cname = it->value("cname", "lz4");
397 const std::int64_t clevel = detail::lenient_int(*it, "clevel", 5, ctx);
398 const json shuffle = it->value("shuffle", json(1));
399 const std::int64_t blocksize = detail::lenient_int(*it, "blocksize", 0, ctx);
400 return CodecSpec{
401 "blosc",
402 {{"cname", cname}, {"clevel", clevel}, {"shuffle", shuffle}, {"blocksize", blocksize}}};
403 }
404 if (id == "zstd") {
405 // numcodecs Zstd (zarr-python 3's default for v2-format arrays).
406 const std::int64_t level = detail::lenient_int(*it, "level", 0, ctx);
407 return CodecSpec{"zstd", {{"level", level}}};
408 }
409 throw error(ctx + ": unsupported v2 compressor '" + id + "'");
410}
411
412} // namespace detail_v2
413
414namespace detail_v2 {
415
416inline ArrayMeta parse_array_meta_impl(const json& j, const std::string& ctx) {
417 if (!j.is_object()) {
418 throw error(ctx + ": expected a JSON object");
419 }
420 const auto require = [&](const char* name) -> const json& {
421 const auto it = j.find(name);
422 if (it == j.end()) {
423 throw error(ctx + ": missing required member '" + name + "'");
424 }
425 return *it;
426 };
427 if (detail::json_to_uint64(require("zarr_format"), ctx + ": zarr_format") != 2) {
428 throw error(ctx + ": zarr_format must be 2");
429 }
430
431 ArrayMeta meta;
432 meta.format = ZarrFormat::v2;
433 meta.shape = detail::parse_extents(require("shape"), "shape", ctx);
434 meta.chunk_shape = detail::parse_extents(require("chunks"), "chunks", ctx);
435 if (meta.chunk_shape.size() != meta.shape.size()) {
436 throw error(ctx + ": 'chunks' must be an array of the same rank as 'shape'");
437 }
438 for (const std::uint64_t c : meta.chunk_shape) {
439 if (c == 0) {
440 throw error(ctx + ": chunk extents must be positive");
441 }
442 }
443
444 const json& dtype = require("dtype");
445 if (!dtype.is_string()) {
446 throw error(ctx + ": structured dtypes are not supported");
447 }
448 const ParsedDType parsed = parse_data_type(dtype.get<std::string>(), ctx);
449 meta.dtype = parsed.dtype;
450
451 const json& order = require("order");
452 if (!order.is_string() || (order != "C" && order != "F")) {
453 throw error(ctx + R"(: 'order' must be "C" or "F")");
454 }
455
456 // Tolerance: fill_value/compressor are required members, but minimal
457 // writers omit them; missing reads as null.
458 const auto fill_it = j.find("fill_value");
459 meta.fill = fill_it == j.end() ? std::nullopt : parse_fill(*fill_it, meta.dtype, ctx);
460
461 meta.dimension_separator = parse_separator(j, ctx);
462
463 // Lowering into the normalized codec chain: array->array, bytes, then
464 // filters (numcodecs applies them before the compressor), then compressor.
465 if (order == "F" && meta.shape.size() >= 2) {
466 json perm = json::array();
467 for (std::size_t d = meta.shape.size(); d-- > 0;) {
468 perm.push_back(d);
469 }
470 meta.codecs.push_back({"transpose", {{"order", perm}}});
471 }
472 meta.codecs.push_back({"bytes", {{"endian", parsed.big_endian ? "big" : "little"}}});
473 for (CodecSpec& filter : parse_filters(j, ctx)) {
474 meta.codecs.push_back(std::move(filter));
475 }
476 if (auto compressor = parse_compressor(j, ctx)) {
477 meta.codecs.push_back(*std::move(compressor));
478 }
479 return meta;
480}
481
483inline json emit_filters(const std::vector<CodecSpec>& codecs) {
484 json filters = json::array();
485 for (const CodecSpec& codec : codecs) {
486 if (codec.name == "shuffle") {
487 filters.push_back(
488 {{"id", "shuffle"},
489 {"elementsize", codec.configuration.value("elementsize", std::int64_t{0})}});
490 }
491 }
492 return filters.empty() ? json(nullptr) : filters;
493}
494
495} // namespace detail_v2
496
500[[nodiscard]] inline ArrayMeta parse_array_meta(const json& j, const std::string& ctx) {
501 return detail::guard_json(ctx, [&] { return detail_v2::parse_array_meta_impl(j, ctx); });
502}
503
506[[nodiscard]] inline json emit_array_meta(const ArrayMeta& meta) {
507 json j;
508 j["zarr_format"] = 2;
509 j["shape"] = meta.shape;
510 j["chunks"] = meta.chunk_shape;
511 j["filters"] = detail_v2::emit_filters(meta.codecs);
512 j["fill_value"] = detail::fill_to_json(meta.fill, meta.dtype);
513 if (meta.dimension_separator == '/') {
514 j["dimension_separator"] = "/";
515 }
516
517 bool big_endian = false;
518 bool f_order = false;
519 j["compressor"] = nullptr;
520 for (const CodecSpec& codec : meta.codecs) {
521 if (codec.name == "bytes") {
522 big_endian = codec.configuration.value("endian", "little") == std::string("big");
523 } else if (codec.name == "transpose") {
524 // v2 can only express the full reversal (order:"F").
525 const json& perm = codec.configuration.at("order");
526 for (std::size_t i = 0; i < perm.size(); ++i) {
527 if (perm[i].get<std::uint64_t>() != perm.size() - 1 - i) {
528 throw error(R"(v2 cannot represent this transpose; only order:"F" (full reversal))");
529 }
530 }
531 f_order = true;
532 } else if (codec.name == "gzip" || codec.name == "zlib") {
533 j["compressor"] = {{"id", codec.name},
534 {"level", codec.configuration.value("level", std::int64_t{1})}};
535 } else if (codec.name == "blosc") {
536 // Canonical numcodecs form: numeric shuffle.
537 const json shuffle = codec.configuration.value("shuffle", json(1));
538 std::int64_t shuffle_num = 1;
539 if (shuffle.is_number_integer()) {
540 shuffle_num = shuffle.get<std::int64_t>();
541 } else if (shuffle == "noshuffle") {
542 shuffle_num = 0;
543 } else if (shuffle == "bitshuffle") {
544 shuffle_num = 2;
545 }
546 j["compressor"] = {{"id", "blosc"},
547 {"cname", codec.configuration.value("cname", "lz4")},
548 {"clevel", codec.configuration.value("clevel", std::int64_t{5})},
549 {"shuffle", shuffle_num},
550 {"blocksize", codec.configuration.value("blocksize", std::int64_t{0})}};
551 } else if (codec.name == "zstd") {
552 j["compressor"] = {{"id", "zstd"},
553 {"level", codec.configuration.value("level", std::int64_t{0})}};
554 } else if (codec.name == "shuffle") {
555 // already emitted into the filters member
556 } else {
557 throw error("v2 cannot represent codec '" + codec.name + "'");
558 }
559 }
560 j["dtype"] = emit_data_type(meta.dtype, big_endian);
561 j["order"] = f_order ? "F" : "C";
562 return j;
563}
564
565// ---- groups / chunk keys ------------------------------------------------------
566
568[[nodiscard]] inline json emit_group_meta() { return json{{"zarr_format", 2}}; }
569
571inline void check_group_meta(const json& j, const std::string& ctx) {
572 detail::guard_json(ctx, [&] {
573 if (!j.is_object() || j.find("zarr_format") == j.end() ||
574 detail::json_to_uint64(j.at("zarr_format"), ctx) != 2) {
575 throw error(ctx + ": not a v2 group (zarr_format must be 2)");
576 }
577 });
578}
579
582[[nodiscard]] inline std::string chunk_key(const std::vector<std::uint64_t>& index,
583 char separator) {
584 if (index.empty()) {
585 return "0";
586 }
587 std::string key = std::to_string(index[0]);
588 for (std::size_t d = 1; d < index.size(); ++d) {
589 key += separator;
590 key += std::to_string(index[d]);
591 }
592 return key;
593}
594
595// ---- consolidated metadata (.zmetadata) ---------------------------------------
596
600inline void write_meta_key(Store& store, const std::string& key, const json& value) {
601 store.write(key, canonical_json_bytes(value));
602 if (auto existing = store.read(kConsolidatedKey)) {
603 json c = detail::parse_json(*existing, kConsolidatedKey);
604 c["metadata"][key] = value;
606 }
607}
608
610inline void erase_meta_key(Store& store, const std::string& key) {
611 store.erase(key);
612 if (auto existing = store.read(kConsolidatedKey)) {
613 json c = detail::parse_json(*existing, kConsolidatedKey);
614 auto meta_it = c.find("metadata");
615 if (meta_it != c.end()) {
616 meta_it->erase(key);
617 }
619 }
620}
621
624inline void consolidate(Store& store) {
625 json metadata = json::object();
626 for (const std::string& key : store.list_prefix("")) {
627 const std::string_view k = key;
628 const auto leaf_is = [&](const char* suffix) {
629 return k == suffix || detail::ends_with(k, std::string("/") + suffix);
630 };
631 if (leaf_is(kArraySuffix) || leaf_is(kGroupSuffix) || leaf_is(kAttrsSuffix)) {
632 const auto bytes = store.read(key);
633 if (bytes) {
634 metadata[key] = detail::parse_json(*bytes, key);
635 }
636 }
637 }
638 const json c = {{"metadata", metadata}, {"zarr_consolidated_format", 1}};
640}
641
643[[nodiscard]] inline std::optional<json> read_consolidated(Store& store) {
644 const auto bytes = store.read(kConsolidatedKey);
645 if (!bytes) {
646 return std::nullopt;
647 }
648 json c = detail::parse_json(*bytes, kConsolidatedKey);
649 if (!c.is_object() || c.value("zarr_consolidated_format", std::int64_t{0}) != 1 ||
650 !c.contains("metadata") || !c["metadata"].is_object()) {
651 throw error(std::string(kConsolidatedKey) + ": unrecognized consolidated metadata format");
652 }
653 return c["metadata"];
654}
655
656} // namespace zarr::v2
657
658#endif // LIBZARR_V2_HPP
Definition store.hpp:83
virtual void write(std::string_view key, Bytes value)=0
Create or replace the value at key.
virtual std::vector< std::string > list_prefix(std::string_view prefix)=0
All keys starting with prefix ("" or ending in '/'), sorted.
virtual void erase(std::string_view key)=0
Remove key; removing an absent key is a no-op.
virtual std::optional< Bytes > read(std::string_view key)=0
Full value at key, or std::nullopt if the key is absent.
Definition types.hpp:36
nlohmann::json json
Definition metadata.hpp:30
Bytes canonical_json_bytes(const json &j)
Definition metadata.hpp:153
CodecSpec shuffle(int elementsize=0)
shuffle: byte-transposition filter. elementsize 0 means the dtype size.
Definition metadata.hpp:69
Definition metadata.hpp:98
std::vector< std::uint64_t > shape
Array shape; empty = 0-dimensional.
Definition metadata.hpp:102
char dimension_separator
Chunk-key separator ('.' or '/').
Definition metadata.hpp:113
DataType dtype
Element type.
Definition metadata.hpp:106
std::vector< std::uint64_t > chunk_shape
Chunk shape, same rank as shape; chunks may exceed the array extent.
Definition metadata.hpp:104
std::vector< CodecSpec > codecs
Definition metadata.hpp:119
std::optional< Bytes > fill
Definition metadata.hpp:109
Definition metadata.hpp:36
json configuration
Codec-specific configuration.
Definition metadata.hpp:40
A concrete element type: kind plus size (the size only varies for raw).
Definition types.hpp:122
std::uint32_t itemsize
Element size in bytes.
Definition types.hpp:126
static constexpr DataType raw_bytes(std::uint32_t size)
A raw byte-string type of size bytes (v2 |V<size>).
Definition types.hpp:139
DType kind
Element type kind.
Definition types.hpp:124
static constexpr DataType of(DType kind)
Definition types.hpp:131
A parsed v2 dtype string: the element type plus its stored byte order.
Definition v2.hpp:43
bool big_endian
Stored byte order (v2 '>' dtypes).
Definition v2.hpp:47
DataType dtype
Element type.
Definition v2.hpp:45
constexpr bool is_float(DType kind)
True for float16/float32/float64.
Definition types.hpp:71
constexpr bool is_unsigned_int(DType kind)
True for uint8..uint64.
Definition types.hpp:82
DType
Definition types.hpp:52
constexpr bool is_signed_int(DType kind)
True for int8..int64.
Definition types.hpp:76
std::vector< std::uint8_t > Bytes
Owned byte buffer used throughout the value-based public API.
Definition types.hpp:42
constexpr bool is_complex(DType kind)
True for complex64/complex128.
Definition types.hpp:88
std::optional< Bytes > parse_fill(const json &v, DataType dt, const std::string &ctx)
Definition v2.hpp:279
void check_group_meta(const json &j, const std::string &ctx)
Validates a .zgroup document.
Definition v2.hpp:571
constexpr const char * kGroupSuffix
v2 group metadata document name.
Definition v2.hpp:29
std::string meta_key(const std::string &path, const char *suffix)
Store key of a metadata document for the node at path ("" = root).
Definition v2.hpp:36
ParsedDType parse_data_type(const std::string &text, const std::string &ctx)
Definition v2.hpp:126
constexpr const char * kAttrsSuffix
v2 attributes document name.
Definition v2.hpp:31
void write_meta_key(Store &store, const std::string &key, const json &value)
Definition v2.hpp:600
constexpr const char * kArraySuffix
v2 array metadata document name.
Definition v2.hpp:27
std::optional< json > read_consolidated(Store &store)
Loads the consolidated metadata map if present and well-formed.
Definition v2.hpp:643
std::string chunk_key(const std::vector< std::uint64_t > &index, char separator)
Definition v2.hpp:582
std::string emit_data_type(DataType dt, bool big_endian)
Definition v2.hpp:151
json emit_array_meta(const ArrayMeta &meta)
Definition v2.hpp:506
void erase_meta_key(Store &store, const std::string &key)
Removes a metadata document, keeping .zmetadata in sync (see write_meta_key).
Definition v2.hpp:610
constexpr const char * kConsolidatedKey
v2 consolidated-metadata document (store root).
Definition v2.hpp:33
ArrayMeta parse_array_meta(const json &j, const std::string &ctx)
Definition v2.hpp:500
json emit_group_meta()
The (only) content of a v2 group document.
Definition v2.hpp:568
void consolidate(Store &store)
Definition v2.hpp:624