libzarr
Header-only C++17 Zarr v2/v3, WASM-compatible
Loading...
Searching...
No Matches
group.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2
3#ifndef LIBZARR_GROUP_HPP
4#define LIBZARR_GROUP_HPP
5
6#include <memory>
7#include <string>
8#include <utility>
9#include <vector>
10
11#include "libzarr/array.hpp"
12#include "libzarr/metadata.hpp"
13#include "libzarr/store.hpp"
14#include "libzarr/types.hpp"
15#include "libzarr/v2.hpp"
16#include "libzarr/v3.hpp"
17
23
24namespace zarr {
25
29 std::vector<std::string> arrays;
31 std::vector<std::string> groups;
32};
33
35class Group {
36 public:
43 static Group create(std::shared_ptr<Store> store, const std::string& path = "",
44 ZarrFormat format = ZarrFormat::v2) {
45 if (!store) {
46 throw error("Group::create: null store");
47 }
48 detail::validate_path(path);
49 write_group_chain(*store, path, format);
50 return {std::move(store), path, json::object(), nullptr, format, OpenOptions{}};
51 }
52
57 [[nodiscard]] static Group open(std::shared_ptr<Store> store, const std::string& path = "",
58 OpenOptions options = {}) {
59 if (!store) {
60 throw error("Group::open: null store");
61 }
62 detail::validate_path(path);
63
64 // v3 probe.
65 const std::string v3_key = v3::meta_key(path);
66 if (const auto bytes = store->read(v3_key)) {
67 const json doc = detail::parse_json(*bytes, v3_key);
68 if (doc.is_object() && doc.value("node_type", "") == std::string("array")) {
69 throw error("'" + path + "' is an array, not a group");
70 }
71 v3::GroupMeta meta = v3::parse_group_meta(doc, v3_key, options.lenient);
72 std::shared_ptr<const json> consolidated;
73 if (meta.consolidated) {
74 // The inline map is node-path -> document; rekey by store key so
75 // child opens look up uniformly for both formats.
76 json by_key = json::object();
77 for (const auto& item : meta.consolidated->items()) {
78 const std::string child = path.empty() ? item.key() : path + "/" + item.key();
79 by_key[v3::meta_key(child)] = item.value();
80 }
81 by_key[v3_key] = doc;
82 consolidated = std::make_shared<const json>(std::move(by_key));
83 }
84 return {std::move(store), path, std::move(meta.attributes),
85 std::move(consolidated), ZarrFormat::v3, options};
86 }
87
88 // v2 path, reading through .zmetadata at the store root when present.
89 std::shared_ptr<const json> consolidated;
90 if (const auto c = v2::read_consolidated(*store)) {
91 consolidated = std::make_shared<const json>(*c);
92 }
93 return open_with(std::move(store), path, std::move(consolidated), options);
94 }
95
97 [[nodiscard]] const std::string& path() const { return path_; }
98
100 [[nodiscard]] const json& attributes() const { return attributes_; }
101
105 attributes_ = std::move(attributes);
106 if (format_ == ZarrFormat::v3) {
107 const std::string key = v3::meta_key(path_);
108 const auto bytes = store_->read(key);
109 if (!bytes) {
110 throw error(key + ": metadata disappeared");
111 }
112 json doc = detail::parse_json(*bytes, key);
113 if (attributes_.empty()) {
114 doc.erase("attributes");
115 } else {
116 doc["attributes"] = attributes_;
117 }
118 store_->write(key, canonical_json_bytes(doc));
119 return;
120 }
121 const std::string key = v2::meta_key(path_, v2::kAttrsSuffix);
122 if (attributes_.empty()) {
123 v2::erase_meta_key(*store_, key); // canonical: no empty .zattrs documents
124 } else {
125 v2::write_meta_key(*store_, key, attributes_);
126 }
127 }
128
131 Group create_group(const std::string& name) { return create(store_, child_path(name), format_); }
132
135 Array create_array(const std::string& name, ArraySpec spec) {
136 spec.format = format_;
137 const std::string target = child_path(name);
138 const std::size_t slash = target.rfind('/');
139 if (slash != std::string::npos) {
140 write_group_chain(*store_, target.substr(0, slash), format_);
141 }
142 return Array::create(store_, target, spec);
143 }
144
146 [[nodiscard]] Group open_group(const std::string& name) const {
147 const std::string target = child_path(name);
148 if (format_ == ZarrFormat::v3) {
149 return open_v3_child_group(target);
150 }
151 return open_with(store_, target, consolidated_, options_);
152 }
153
155 [[nodiscard]] Array open_array(const std::string& name) const {
156 return Array::open_impl(store_, child_path(name), options_, consolidated_);
157 }
158
160 [[nodiscard]] GroupChildren children() const {
161 GroupChildren out;
162 const std::string prefix = path_.empty() ? "" : path_ + "/";
163 for (const std::string& child : store_->list_dir(prefix).prefixes) {
164 const std::string child_full = prefix + child;
165 if (store_->exists(child_full + "/" + v2::kArraySuffix)) {
166 out.arrays.push_back(child);
167 } else if (store_->exists(child_full + "/" + v2::kGroupSuffix)) {
168 out.groups.push_back(child);
169 } else if (const auto doc = read_doc(v3::meta_key(child_full))) {
170 // v3: the node kind lives inside zarr.json.
171 const std::string node_type = doc->is_object() ? doc->value("node_type", "") : "";
172 if (node_type == "array") {
173 out.arrays.push_back(child);
174 } else if (node_type == "group") {
175 out.groups.push_back(child);
176 }
177 }
178 // Other directories are not Zarr nodes; ignore them.
179 }
180 return out;
181 }
182
183 private:
184 Group(std::shared_ptr<Store> store, std::string path, json attributes,
185 std::shared_ptr<const json> consolidated, ZarrFormat format = ZarrFormat::v2,
186 OpenOptions options = {})
187 : store_(std::move(store)),
188 path_(std::move(path)),
189 attributes_(std::move(attributes)),
190 consolidated_(std::move(consolidated)),
191 format_(format),
192 options_(options) {}
193
194 [[nodiscard]] std::optional<json> read_doc(const std::string& key) const {
195 if (consolidated_) {
196 const auto it = consolidated_->find(key);
197 if (it == consolidated_->end()) {
198 return std::nullopt;
199 }
200 return *it;
201 }
202 const auto bytes = store_->read(key);
203 if (!bytes) {
204 return std::nullopt;
205 }
206 return detail::parse_json(*bytes, key);
207 }
208
209 [[nodiscard]] Group open_v3_child_group(const std::string& target) const {
210 const std::string key = v3::meta_key(target);
211 const auto doc = read_doc(key);
212 if (!doc) {
213 throw error("no group at '" + target + "' (" + key + " not found)");
214 }
215 if (doc->is_object() && doc->value("node_type", "") == std::string("array")) {
216 throw error("'" + target + "' is an array, not a group");
217 }
218 v3::GroupMeta meta = v3::parse_group_meta(*doc, key, options_.lenient);
219 // Children of this group keep reading through the root's consolidated map.
220 return {store_, target, std::move(meta.attributes), consolidated_, ZarrFormat::v3, options_};
221 }
222
223 static Group open_with(std::shared_ptr<Store> store, const std::string& path,
224 std::shared_ptr<const json> consolidated, OpenOptions options) {
225 const auto read_doc = [&](const std::string& key) -> std::optional<json> {
226 if (consolidated) {
227 const auto it = consolidated->find(key);
228 if (it == consolidated->end()) {
229 return std::nullopt;
230 }
231 return *it;
232 }
233 const auto bytes = store->read(key);
234 if (!bytes) {
235 return std::nullopt;
236 }
237 return detail::parse_json(*bytes, key);
238 };
239
240 const std::string group_key = v2::meta_key(path, v2::kGroupSuffix);
241 const auto doc = read_doc(group_key);
242 if (!doc) {
243 if (store->exists(v2::meta_key(path, v2::kArraySuffix))) {
244 throw error("'" + path + "' is an array, not a group");
245 }
246 throw error("no group at '" + path + "' (neither " + v3::meta_key(path) + " nor " +
247 group_key + " found)");
248 }
249 v2::check_group_meta(*doc, group_key);
250 json attributes = json::object();
251 if (const auto attrs = read_doc(v2::meta_key(path, v2::kAttrsSuffix))) {
252 attributes = *attrs;
253 }
254 return {std::move(store), path, std::move(attributes),
255 std::move(consolidated), ZarrFormat::v2, options};
256 }
257
259 static void write_group_chain(Store& store, const std::string& path, ZarrFormat format) {
260 std::vector<std::string> chain;
261 chain.emplace_back("");
262 std::size_t start = 0;
263 while (!path.empty()) {
264 const std::size_t slash = path.find('/', start);
265 if (slash == std::string::npos) {
266 chain.push_back(path);
267 break;
268 }
269 chain.push_back(path.substr(0, slash));
270 start = slash + 1;
271 }
272 for (const std::string& node : chain) {
273 if (store.exists(v2::meta_key(node, v2::kArraySuffix))) {
274 throw error("'" + node + "' is an array; cannot create a group inside it");
275 }
276 if (format == ZarrFormat::v3) {
277 const std::string key = v3::meta_key(node);
278 if (const auto bytes = store.read(key)) {
279 const json doc = detail::parse_json(*bytes, key);
280 if (doc.is_object() && doc.value("node_type", "") == std::string("array")) {
281 throw error("'" + node + "' is an array; cannot create a group inside it");
282 }
283 continue; // existing group document: leave it (and its attributes) alone
284 }
285 store.write(key, canonical_json_bytes(v3::emit_group_meta(json::object())));
286 } else {
287 const std::string key = v2::meta_key(node, v2::kGroupSuffix);
288 if (!store.exists(key)) {
289 v2::write_meta_key(store, key, v2::emit_group_meta());
290 }
291 }
292 }
293 }
294
295 [[nodiscard]] std::string child_path(const std::string& name) const {
296 detail::validate_path(name);
297 if (name.empty()) {
298 throw error("child name must not be empty");
299 }
300 return path_.empty() ? name : path_ + "/" + name;
301 }
302
303 std::shared_ptr<Store> store_;
304 std::string path_;
305 json attributes_;
306 std::shared_ptr<const json> consolidated_;
307 ZarrFormat format_ = ZarrFormat::v2;
308 OpenOptions options_;
309};
310
311} // namespace zarr
312
313#endif // LIBZARR_GROUP_HPP
Definition array.hpp:121
static Array create(std::shared_ptr< Store > store, const std::string &path, const ArraySpec &spec)
Definition array.hpp:126
A Zarr group bound to a Store.
Definition group.hpp:35
static Group create(std::shared_ptr< Store > store, const std::string &path="", ZarrFormat format=ZarrFormat::v2)
Definition group.hpp:43
Array open_array(const std::string &name) const
Opens a child (possibly nested) array.
Definition group.hpp:155
static Group open(std::shared_ptr< Store > store, const std::string &path="", OpenOptions options={})
Definition group.hpp:57
Group create_group(const std::string &name)
Definition group.hpp:131
void set_attributes(json attributes)
Definition group.hpp:104
Group open_group(const std::string &name) const
Opens a child (possibly nested) group.
Definition group.hpp:146
const json & attributes() const
User attributes (.zattrs).
Definition group.hpp:100
Array create_array(const std::string &name, ArraySpec spec)
Definition group.hpp:135
const std::string & path() const
Node path within the store ("" = root).
Definition group.hpp:97
GroupChildren children() const
Lists immediate children, classified by their metadata documents.
Definition group.hpp:160
Definition types.hpp:36
nlohmann::json json
Definition metadata.hpp:30
Bytes canonical_json_bytes(const json &j)
Definition metadata.hpp:153
Parameters for Array::create.
Definition array.hpp:91
ZarrFormat format
Storage format version to write.
Definition array.hpp:93
Immediate children of a group, by kind.
Definition group.hpp:27
std::vector< std::string > groups
Names of child groups, sorted.
Definition group.hpp:31
std::vector< std::string > arrays
Names of child arrays, sorted.
Definition group.hpp:29
Options for opening arrays and groups.
Definition metadata.hpp:144
bool lenient
Definition metadata.hpp:148
ZarrFormat
Zarr storage format version.
Definition types.hpp:45