129 throw error(
"Array::create: null store");
131 detail::validate_path(
path);
132 const bool v3 = spec.
format == ZarrFormat::v3;
133 const std::string ctx = v3 ? v3::meta_key(
path) : v2::meta_key(
path, v2::kArraySuffix);
135 throw error(ctx +
": chunks rank " + std::to_string(spec.
chunks.size()) +
" != shape rank " +
136 std::to_string(spec.
shape.size()));
138 for (
const std::uint64_t c : spec.
chunks) {
140 throw error(ctx +
": chunk extents must be positive");
144 throw error(ctx +
": dimension_separator must be '.' or '/'");
153 apply_format_members(spec,
meta, ctx);
156 throw error(ctx +
": fill is " + std::to_string(spec.
fill->size()) +
157 " bytes, dtype needs " + std::to_string(spec.
dtype.
itemsize));
163 meta.
codecs.push_back({
"bytes", {{
"endian",
"little"}}});
170 array.store_->write(v3::meta_key(
path),
173 v2::write_meta_key(*array.store_, array.meta_store_key(), v2::emit_array_meta(array.meta_));
174 array.write_attributes();
181 [[nodiscard]]
static Array open(std::shared_ptr<Store> store,
const std::string&
path,
183 return open_impl(std::move(store),
path, options,
nullptr);
189 [[nodiscard]]
const std::string&
path()
const {
return path_; }
193 [[nodiscard]] std::uint64_t
nbytes()
const {
204 return filled_chunk();
206 return pipeline_.
decode(std::move(*stored));
211 void write_chunk(
const std::vector<std::uint64_t>& index,
const void* data, std::size_t size) {
212 Bytes chunk(
static_cast<const std::uint8_t*
>(data),
213 static_cast<const std::uint8_t*
>(data) + size);
215 chunk_store_->flush();
224 std::uint64_t element_offset,
225 std::uint64_t element_count)
const {
227 throw error(
"byte-range chunk reads need an uncompressed, untransposed layout");
231 if (element_count > chunk_elements || element_offset > chunk_elements - element_count) {
232 throw error(
"chunk range [" + std::to_string(element_offset) +
", +" +
233 std::to_string(element_count) +
") exceeds " + std::to_string(chunk_elements) +
236 auto stored = chunk_store_->read_range(
240 Bytes out(detail::checked_size(element_count * itemsize,
"chunk range"));
241 detail::fill_elements(out.data(), element_count, meta_.
fill ? meta_.
fill->data() :
nullptr,
250 void read(
void* dst, std::size_t size)
const {
256 void write(
const void* src, std::size_t size) {
264 const std::vector<std::uint64_t>& shape,
void* dst, std::size_t size)
const {
265 validate_region(origin, shape, size,
"read_region");
269 auto* out =
static_cast<std::uint8_t*
>(dst);
270 for_each_region_chunk(origin, shape, [&](
const RegionChunk& rc) {
272 detail::copy_box(chunk.data(), meta_.
chunk_shape, rc.origin_in_chunk, out, shape,
282 const std::vector<std::uint64_t>& shape,
const void* src, std::size_t size) {
283 validate_region(origin, shape, size,
"write_region");
287 const auto* in =
static_cast<const std::uint8_t*
>(src);
288 for_each_region_chunk(origin, shape, [&](
const RegionChunk& rc) {
290 detail::copy_box(in, shape, rc.origin_in_region, chunk.data(), meta_.
chunk_shape,
294 chunk_store_->flush();
304 if (meta_.
format == ZarrFormat::v3) {
305 const std::string key = v3::meta_key(path_);
306 const auto bytes = store_->read(key);
308 throw error(key +
": metadata disappeared");
310 json doc = detail::parse_json(*bytes, key);
312 doc.erase(
"attributes");
323 [[nodiscard]] std::string
chunk_store_key(
const std::vector<std::uint64_t>& index)
const {
325 if (index.size() != grid.size()) {
326 throw error(
"chunk index rank " + std::to_string(index.size()) +
" != array rank " +
327 std::to_string(grid.size()));
329 for (std::size_t d = 0; d < grid.size(); ++d) {
330 if (index[d] >= grid[d]) {
331 throw error(
"chunk index " + std::to_string(index[d]) +
" out of range for dimension " +
332 std::to_string(d) +
" (grid extent " + std::to_string(grid[d]) +
")");
335 const std::string relative = meta_.
key_encoding == ChunkKeyKind::v3_default
338 return path_.empty() ? relative : path_ +
"/" + relative;
347 static Array open_impl(std::shared_ptr<Store> store,
const std::string&
path,
OpenOptions options,
348 const std::shared_ptr<const json>& consolidated) {
350 throw error(
"Array::open: null store");
352 detail::validate_path(
path);
353 const auto read_doc = [&](
const std::string& key) -> std::optional<json> {
355 const auto it = consolidated->find(key);
356 if (it == consolidated->end()) {
361 const auto bytes = store->read(key);
365 return detail::parse_json(*bytes, key);
369 const std::string v3_key = v3::meta_key(
path);
370 if (
const auto doc = read_doc(v3_key)) {
371 if (doc->is_object() && doc->value(
"node_type",
"") == std::string(
"group")) {
372 throw error(
"'" +
path +
"' is a group, not an array");
374 return {std::move(store),
path, v3::parse_array_meta(*doc, v3_key, options.
lenient)};
377 const std::string
meta_key = v2::meta_key(
path, v2::kArraySuffix);
378 auto doc = read_doc(meta_key);
380 if (store->exists(v2::meta_key(
path, v2::kGroupSuffix))) {
381 throw error(
"'" +
path +
"' is a group, not an array");
383 throw error(
"no array at '" +
path +
"' (neither " + v3_key +
" nor " + meta_key +
" found)");
385 ArrayMeta
meta = v2::parse_array_meta(*doc, meta_key);
386 if (
const auto attrs = read_doc(v2::meta_key(
path, v2::kAttrsSuffix))) {
389 return {std::move(store),
path, std::move(
meta)};
394 static void apply_format_members(
const ArraySpec& spec, ArrayMeta&
meta,
const std::string& ctx) {
395 if (spec.format != ZarrFormat::v3) {
397 if (!spec.dimension_names.is_null()) {
398 throw error(ctx +
": dimension_names is a v3 feature");
400 if (!spec.shards.empty()) {
401 throw error(ctx +
": sharding is a v3 feature");
408 if (spec.dimension_names.is_array()) {
409 if (spec.dimension_names.size() != spec.shape.size()) {
410 throw error(ctx +
": dimension_names must have rank length");
414 if (spec.shards.empty()) {
417 if (spec.shards.size() != spec.chunks.size()) {
418 throw error(ctx +
": shards rank must match chunks rank");
420 for (std::size_t d = 0; d < spec.shards.size(); ++d) {
422 if (spec.shards[d] == 0 || spec.shards[d] % spec.chunks[d] != 0) {
423 throw error(ctx +
": each shard extent must be a positive multiple of the chunk extent");
427 level.shard_shape = spec.shards;
428 level.index_codecs = {{
"bytes", {{
"endian",
"little"}}}, {
"crc32c", {}}};
432 Array(std::shared_ptr<Store> store, std::string
path, ArrayMeta
meta)
433 : store_(std::move(store)),
434 path_(std::move(
path)),
435 meta_(std::move(
meta)),
436 pipeline_(CodecPipeline::resolve(meta_)),
437 chunk_store_(wrap_shards(store_, meta_, path_)) {
445 static std::shared_ptr<Store> wrap_shards(std::shared_ptr<Store> store,
const ArrayMeta&
meta,
446 const std::string&
path) {
447 std::shared_ptr<Store> chunks = std::move(store);
448 const std::string prefix =
path.empty() ?
"" :
path +
"/";
450 chunks = std::make_shared<detail_shard::ShardStore>(
451 std::move(chunks), detail_shard::params_for_level(
meta, i, prefix));
456 [[nodiscard]] std::string meta_store_key()
const {
return v2::meta_key(path_, v2::kArraySuffix); }
458 void write_attributes() {
459 const std::string key = v2::meta_key(path_, v2::kAttrsSuffix);
461 v2::erase_meta_key(*store_, key);
463 v2::write_meta_key(*store_, key, meta_.
attributes);
467 [[nodiscard]]
Bytes filled_chunk()
const {
470 meta_.
fill ? meta_.
fill->data() : nullptr, meta_.dtype.itemsize);
476 std::vector<std::uint64_t> index;
477 std::vector<std::uint64_t> origin_in_chunk;
478 std::vector<std::uint64_t> origin_in_region;
479 std::vector<std::uint64_t> box;
485 void validate_region(
const std::vector<std::uint64_t>& origin,
486 const std::vector<std::uint64_t>& shape, std::size_t size,
487 const char* what)
const {
488 const std::size_t rank = meta_.
shape.size();
489 if (origin.size() != rank || shape.size() != rank) {
490 throw error(std::string(what) +
": origin/shape rank must be " + std::to_string(rank));
492 for (std::size_t d = 0; d < rank; ++d) {
493 if (shape[d] > meta_.
shape[d] || origin[d] > meta_.
shape[d] - shape[d]) {
494 throw error(std::string(what) +
": region [" + std::to_string(origin[d]) +
", " +
495 std::to_string(origin[d]) +
"+" + std::to_string(shape[d]) +
496 ") exceeds dimension " + std::to_string(d) +
" (extent " +
497 std::to_string(meta_.
shape[d]) +
")");
500 const std::uint64_t bytes = detail::checked_product(shape, what) * meta_.
dtype.
itemsize;
501 if (size != detail::checked_size(bytes, what)) {
502 throw error(std::string(what) +
": buffer is " + std::to_string(size) +
503 " bytes, region needs " + std::to_string(bytes));
514 template <
typename Fn>
515 void for_each_region_chunk(
const std::vector<std::uint64_t>& origin,
516 const std::vector<std::uint64_t>& shape,
const Fn& fn)
const {
517 const std::size_t rank = meta_.
shape.size();
518 std::vector<std::uint64_t> first(rank, 0);
519 std::vector<std::uint64_t> last(rank, 0);
520 for (std::size_t d = 0; d < rank; ++d) {
522 last[d] = (origin[d] + shape[d] - 1) / meta_.
chunk_shape[d];
526 rc.origin_in_chunk.assign(rank, 0);
527 rc.origin_in_region.assign(rank, 0);
528 rc.box.assign(rank, 0);
529 visit_shard_major(0, first, last, [&](
const std::vector<std::uint64_t>& index) {
532 for (std::size_t d = 0; d < rank; ++d) {
533 const std::uint64_t chunk_start = rc.index[d] * meta_.
chunk_shape[d];
534 const std::uint64_t valid_end =
536 const std::uint64_t begin = std::max(chunk_start, origin[d]);
537 const std::uint64_t end = std::min(valid_end, origin[d] + shape[d]);
538 rc.origin_in_chunk[d] = begin - chunk_start;
539 rc.origin_in_region[d] = begin - origin[d];
540 rc.box[d] = end - begin;
541 rc.covered = rc.covered && begin == chunk_start && end == valid_end;
551 template <
typename Leaf>
552 void visit_shard_major(std::size_t level,
const std::vector<std::uint64_t>& lo,
553 const std::vector<std::uint64_t>& hi,
const Leaf& leaf)
const {
554 const std::size_t rank = lo.size();
558 std::vector<std::uint64_t> per(rank, 1);
559 std::vector<std::uint64_t> shard_lo(rank, 0);
560 std::vector<std::uint64_t> shard_hi(rank, 0);
561 for (std::size_t d = 0; d < rank; ++d) {
563 shard_lo[d] = lo[d] / per[d];
564 shard_hi[d] = hi[d] / per[d];
566 std::vector<std::uint64_t> shard = shard_lo;
567 std::vector<std::uint64_t> sub_lo(rank, 0);
568 std::vector<std::uint64_t> sub_hi(rank, 0);
570 for (std::size_t d = 0; d < rank; ++d) {
571 sub_lo[d] = std::max(lo[d], shard[d] * per[d]);
572 sub_hi[d] = std::min(hi[d], shard[d] * per[d] + per[d] - 1);
574 visit_shard_major(level + 1, sub_lo, sub_hi, leaf);
575 if (!detail::next_index_box(shard, shard_lo, shard_hi)) {
580 std::vector<std::uint64_t> index = lo;
583 if (!detail::next_index_box(index, lo, hi)) {
589 std::shared_ptr<Store> store_;
592 CodecPipeline pipeline_;
593 std::shared_ptr<Store> chunk_store_;