1use std::collections::HashMap;
4
5use crate::error::{IoError, Result};
6
7use super::codecs::{BytesCodec, CodecPipeline, Endian, ZstdCodec};
8use super::metadata::{ArrayMetadataV2, ArrayMetadataV3, CodecMetadata, CompressorV2};
9use super::store::{chunk_key_v2, chunk_key_v3, DirectoryStore};
10use super::{DataType, ZarrVersion};
11
12#[derive(Debug)]
14pub struct ZarrArray {
15 store: DirectoryStore,
16 path: String,
18 version: ZarrVersion,
20 shape: Vec<u64>,
22 chunks: Vec<u64>,
24 dtype: DataType,
26 fill_value: f64,
28 pipeline: CodecPipeline,
30 order: String,
32 dim_separator: String,
34 v3_separator: String,
36 compressed: bool,
38}
39
40impl ZarrArray {
41 pub fn create(
45 store: DirectoryStore,
46 path: &str,
47 shape: Vec<u64>,
48 chunks: Vec<u64>,
49 dtype: DataType,
50 version: ZarrVersion,
51 ) -> Result<Self> {
52 Self::create_with_options(store, path, shape, chunks, dtype, version, 0.0, true)
53 }
54
55 pub fn create_with_options(
57 store: DirectoryStore,
58 path: &str,
59 shape: Vec<u64>,
60 chunks: Vec<u64>,
61 dtype: DataType,
62 version: ZarrVersion,
63 fill_value: f64,
64 compress: bool,
65 ) -> Result<Self> {
66 if shape.len() != chunks.len() {
67 return Err(IoError::FormatError(format!(
68 "Shape ndim ({}) != chunks ndim ({})",
69 shape.len(),
70 chunks.len()
71 )));
72 }
73
74 let pipeline = build_pipeline(dtype, compress);
75
76 let arr = Self {
77 store,
78 path: path.to_string(),
79 version,
80 shape: shape.clone(),
81 chunks: chunks.clone(),
82 dtype,
83 fill_value,
84 pipeline,
85 order: "C".to_string(),
86 dim_separator: ".".to_string(),
87 v3_separator: "/".to_string(),
88 compressed: compress,
89 };
90
91 match version {
93 ZarrVersion::V2 => arr.write_v2_metadata()?,
94 ZarrVersion::V3 => arr.write_v3_metadata()?,
95 }
96
97 Ok(arr)
98 }
99
100 pub fn open(store: DirectoryStore, path: &str) -> Result<Self> {
102 let v3_key = if path.is_empty() {
104 "zarr.json".to_string()
105 } else {
106 format!("{path}/zarr.json")
107 };
108 if store.exists(&v3_key) {
109 let data = store.get(&v3_key)?;
110 let meta = ArrayMetadataV3::from_json(&data)?;
111 return Self::from_v3_metadata(store, path, &meta);
112 }
113
114 let v2_key = if path.is_empty() {
116 ".zarray".to_string()
117 } else {
118 format!("{path}/.zarray")
119 };
120 if store.exists(&v2_key) {
121 let data = store.get(&v2_key)?;
122 let meta = ArrayMetadataV2::from_json(&data)?;
123 return Self::from_v2_metadata(store, path, &meta);
124 }
125
126 Err(IoError::FileNotFound(format!(
127 "No Zarr array metadata found at path '{path}'"
128 )))
129 }
130
131 fn from_v2_metadata(store: DirectoryStore, path: &str, meta: &ArrayMetadataV2) -> Result<Self> {
132 let dtype = meta.data_type()?;
133 let compressed = meta.compressor.is_some();
134 let pipeline = build_pipeline(dtype, compressed);
135 let fill_value = meta.fill_value.as_f64().unwrap_or(0.0);
136
137 Ok(Self {
138 store,
139 path: path.to_string(),
140 version: ZarrVersion::V2,
141 shape: meta.shape.clone(),
142 chunks: meta.chunks.clone(),
143 dtype,
144 fill_value,
145 pipeline,
146 order: meta.order.clone(),
147 dim_separator: meta.dimension_separator.clone(),
148 v3_separator: "/".to_string(),
149 compressed,
150 })
151 }
152
153 fn from_v3_metadata(store: DirectoryStore, path: &str, meta: &ArrayMetadataV3) -> Result<Self> {
154 let dtype = meta.data_type_parsed()?;
155 let chunk_shape = meta.chunk_shape()?.to_vec();
156 let compressed = meta
157 .codecs
158 .iter()
159 .any(|c| c.name == "zstd" || c.name == "gzip");
160 let pipeline = build_pipeline(dtype, compressed);
161 let fill_value = meta
162 .fill_value
163 .as_ref()
164 .and_then(|v| v.as_f64())
165 .unwrap_or(0.0);
166
167 let separator = meta
168 .chunk_key_encoding
169 .as_ref()
170 .and_then(|e| e.configuration.as_ref())
171 .map(|c| c.separator.clone())
172 .unwrap_or_else(|| "/".to_string());
173
174 Ok(Self {
175 store,
176 path: path.to_string(),
177 version: ZarrVersion::V3,
178 shape: meta.shape.clone(),
179 chunks: chunk_shape,
180 dtype,
181 fill_value,
182 pipeline,
183 order: "C".to_string(),
184 dim_separator: ".".to_string(),
185 v3_separator: separator,
186 compressed,
187 })
188 }
189
190 fn write_v2_metadata(&self) -> Result<()> {
191 let compressor = if self.compressed {
192 Some(CompressorV2 {
193 id: "zstd".to_string(),
194 level: Some(3),
195 extra: HashMap::new(),
196 })
197 } else {
198 None
199 };
200
201 let meta = ArrayMetadataV2::new(
202 self.shape.clone(),
203 self.chunks.clone(),
204 self.dtype,
205 compressor,
206 serde_json::json!(self.fill_value),
207 );
208 let json = meta.to_json()?;
209 let key = if self.path.is_empty() {
210 ".zarray".to_string()
211 } else {
212 format!("{}/.zarray", self.path)
213 };
214 self.store.set(&key, &json)
215 }
216
217 fn write_v3_metadata(&self) -> Result<()> {
218 let mut codecs = vec![CodecMetadata {
219 name: "bytes".to_string(),
220 configuration: Some(serde_json::json!({"endian": "little"})),
221 }];
222 if self.compressed {
223 codecs.push(CodecMetadata {
224 name: "zstd".to_string(),
225 configuration: Some(serde_json::json!({"level": 3})),
226 });
227 }
228
229 let meta = ArrayMetadataV3::new_array(
230 self.shape.clone(),
231 self.chunks.clone(),
232 self.dtype,
233 serde_json::json!(self.fill_value),
234 codecs,
235 );
236 let json = meta.to_json()?;
237 let key = if self.path.is_empty() {
238 "zarr.json".to_string()
239 } else {
240 format!("{}/zarr.json", self.path)
241 };
242 self.store.set(&key, &json)
243 }
244
245 pub fn shape(&self) -> &[u64] {
249 &self.shape
250 }
251
252 pub fn chunk_shape(&self) -> &[u64] {
254 &self.chunks
255 }
256
257 pub fn data_type(&self) -> DataType {
259 self.dtype
260 }
261
262 pub fn version(&self) -> ZarrVersion {
264 self.version
265 }
266
267 pub fn ndim(&self) -> usize {
269 self.shape.len()
270 }
271
272 pub fn num_chunks(&self) -> Vec<u64> {
274 self.shape
275 .iter()
276 .zip(self.chunks.iter())
277 .map(|(&s, &c)| (s + c - 1) / c)
278 .collect()
279 }
280
281 fn chunk_key(&self, coords: &[u64]) -> String {
285 match self.version {
286 ZarrVersion::V2 => chunk_key_v2(&self.path, coords, &self.dim_separator),
287 ZarrVersion::V3 => chunk_key_v3(&self.path, coords, &self.v3_separator),
288 }
289 }
290
291 fn chunk_num_elements(&self) -> usize {
293 self.chunks.iter().map(|&c| c as usize).product()
294 }
295
296 pub fn read_chunk(&self, coords: &[u64]) -> Result<Vec<f64>> {
298 let key = self.chunk_key(coords);
299 let n = self.chunk_num_elements();
300
301 if !self.store.exists(&key) {
302 return Ok(vec![self.fill_value; n]);
303 }
304
305 let raw = self.store.get(&key)?;
306 let decoded = self.pipeline.decode(&raw)?;
307 bytes_to_f64(&decoded, self.dtype, n)
308 }
309
310 pub fn write_chunk(&self, coords: &[u64], data: &[f64]) -> Result<()> {
312 let n = self.chunk_num_elements();
313 if data.len() != n {
314 return Err(IoError::FormatError(format!(
315 "Chunk data length {} != expected {}",
316 data.len(),
317 n
318 )));
319 }
320 let raw = f64_to_bytes(data, self.dtype)?;
321 let encoded = self.pipeline.encode(&raw)?;
322 let key = self.chunk_key(coords);
323 self.store.set(&key, &encoded)
324 }
325
326 pub fn get(&self, start: &[u64], end: &[u64]) -> Result<Vec<f64>> {
333 let ndim = self.ndim();
334 if start.len() != ndim || end.len() != ndim {
335 return Err(IoError::FormatError(format!(
336 "Selection dimensionality mismatch: ndim={ndim}, start.len={}, end.len={}",
337 start.len(),
338 end.len()
339 )));
340 }
341 for d in 0..ndim {
342 if start[d] >= end[d] || end[d] > self.shape[d] {
343 return Err(IoError::FormatError(format!(
344 "Invalid selection range in dim {d}: [{}, {}) out of [0, {})",
345 start[d], end[d], self.shape[d]
346 )));
347 }
348 }
349
350 let sel_shape: Vec<u64> = (0..ndim).map(|d| end[d] - start[d]).collect();
351 let total: usize = sel_shape.iter().map(|&s| s as usize).product();
352 let mut result = vec![0.0f64; total];
353
354 let chunk_start: Vec<u64> = (0..ndim).map(|d| start[d] / self.chunks[d]).collect();
356 let chunk_end: Vec<u64> = (0..ndim)
357 .map(|d| (end[d] + self.chunks[d] - 1) / self.chunks[d])
358 .collect();
359
360 let mut chunk_coords = chunk_start.clone();
361 loop {
362 let chunk_data = self.read_chunk(&chunk_coords)?;
363
364 let c_start: Vec<u64> = (0..ndim)
366 .map(|d| chunk_coords[d] * self.chunks[d])
367 .collect();
368
369 let overlap_start: Vec<u64> = (0..ndim).map(|d| start[d].max(c_start[d])).collect();
370 let overlap_end: Vec<u64> = (0..ndim)
371 .map(|d| end[d].min(c_start[d] + self.chunks[d]))
372 .collect();
373
374 let overlap_shape: Vec<u64> = (0..ndim)
376 .map(|d| overlap_end[d] - overlap_start[d])
377 .collect();
378 let overlap_total: usize = overlap_shape.iter().map(|&s| s as usize).product();
379
380 for linear in 0..overlap_total {
381 let mut multi = vec![0u64; ndim];
383 let mut rem = linear;
384 for d in (0..ndim).rev() {
385 multi[d] = (rem % overlap_shape[d] as usize) as u64;
386 rem /= overlap_shape[d] as usize;
387 }
388
389 let chunk_idx: Vec<u64> = (0..ndim)
391 .map(|d| overlap_start[d] + multi[d] - c_start[d])
392 .collect();
393 let chunk_linear = c_order_index(&chunk_idx, &self.chunks);
394
395 let sel_idx: Vec<u64> = (0..ndim)
397 .map(|d| overlap_start[d] + multi[d] - start[d])
398 .collect();
399 let sel_linear = c_order_index(&sel_idx, &sel_shape);
400
401 result[sel_linear] = chunk_data[chunk_linear];
402 }
403
404 if !advance_coords(&mut chunk_coords, &chunk_start, &chunk_end, ndim) {
406 break;
407 }
408 }
409
410 Ok(result)
411 }
412
413 pub fn set(&self, start: &[u64], end: &[u64], data: &[f64]) -> Result<()> {
418 let ndim = self.ndim();
419 if start.len() != ndim || end.len() != ndim {
420 return Err(IoError::FormatError(format!(
421 "Selection dimensionality mismatch: ndim={ndim}, start.len={}, end.len={}",
422 start.len(),
423 end.len()
424 )));
425 }
426 let sel_shape: Vec<u64> = (0..ndim).map(|d| end[d] - start[d]).collect();
427 let total: usize = sel_shape.iter().map(|&s| s as usize).product();
428 if data.len() != total {
429 return Err(IoError::FormatError(format!(
430 "Data length {} != selection size {}",
431 data.len(),
432 total
433 )));
434 }
435
436 let chunk_start: Vec<u64> = (0..ndim).map(|d| start[d] / self.chunks[d]).collect();
437 let chunk_end: Vec<u64> = (0..ndim)
438 .map(|d| (end[d] + self.chunks[d] - 1) / self.chunks[d])
439 .collect();
440
441 let mut chunk_coords = chunk_start.clone();
442 loop {
443 let mut chunk_data = self.read_chunk(&chunk_coords)?;
445
446 let c_start: Vec<u64> = (0..ndim)
447 .map(|d| chunk_coords[d] * self.chunks[d])
448 .collect();
449 let overlap_start: Vec<u64> = (0..ndim).map(|d| start[d].max(c_start[d])).collect();
450 let overlap_end: Vec<u64> = (0..ndim)
451 .map(|d| end[d].min(c_start[d] + self.chunks[d]))
452 .collect();
453 let overlap_shape: Vec<u64> = (0..ndim)
454 .map(|d| overlap_end[d] - overlap_start[d])
455 .collect();
456 let overlap_total: usize = overlap_shape.iter().map(|&s| s as usize).product();
457
458 for linear in 0..overlap_total {
459 let mut multi = vec![0u64; ndim];
460 let mut rem = linear;
461 for d in (0..ndim).rev() {
462 multi[d] = (rem % overlap_shape[d] as usize) as u64;
463 rem /= overlap_shape[d] as usize;
464 }
465
466 let chunk_idx: Vec<u64> = (0..ndim)
467 .map(|d| overlap_start[d] + multi[d] - c_start[d])
468 .collect();
469 let chunk_linear = c_order_index(&chunk_idx, &self.chunks);
470
471 let sel_idx: Vec<u64> = (0..ndim)
472 .map(|d| overlap_start[d] + multi[d] - start[d])
473 .collect();
474 let sel_linear = c_order_index(&sel_idx, &sel_shape);
475
476 chunk_data[chunk_linear] = data[sel_linear];
477 }
478
479 self.write_chunk(&chunk_coords, &chunk_data)?;
480
481 if !advance_coords(&mut chunk_coords, &chunk_start, &chunk_end, ndim) {
482 break;
483 }
484 }
485
486 Ok(())
487 }
488}
489
490fn c_order_index(indices: &[u64], shape: &[u64]) -> usize {
494 let mut linear = 0usize;
495 let mut stride = 1usize;
496 for d in (0..indices.len()).rev() {
497 linear += indices[d] as usize * stride;
498 stride *= shape[d] as usize;
499 }
500 linear
501}
502
503fn advance_coords(coords: &mut [u64], start: &[u64], end: &[u64], ndim: usize) -> bool {
505 for d in (0..ndim).rev() {
506 coords[d] += 1;
507 if coords[d] < end[d] {
508 return true;
509 }
510 coords[d] = start[d];
511 }
512 false
513}
514
515fn build_pipeline(dtype: DataType, compress: bool) -> CodecPipeline {
517 let mut pipeline = CodecPipeline::new();
518 pipeline.push(BytesCodec::new(Endian::Little, dtype.byte_size()));
519 if compress {
520 pipeline.push(ZstdCodec::default());
521 }
522 pipeline
523}
524
525fn bytes_to_f64(data: &[u8], dtype: DataType, n: usize) -> Result<Vec<f64>> {
527 let elem = dtype.byte_size();
528 if data.len() < n * elem {
529 return Err(IoError::FormatError(format!(
530 "Chunk data too short: {} bytes for {} elements of {} bytes each",
531 data.len(),
532 n,
533 elem
534 )));
535 }
536 let mut out = Vec::with_capacity(n);
537 for i in 0..n {
538 let offset = i * elem;
539 let val = match dtype {
540 DataType::Bool => {
541 if data[offset] != 0 {
542 1.0
543 } else {
544 0.0
545 }
546 }
547 DataType::Int8 => data[offset] as i8 as f64,
548 DataType::Int16 => {
549 let b = [data[offset], data[offset + 1]];
550 i16::from_le_bytes(b) as f64
551 }
552 DataType::Int32 => {
553 let mut b = [0u8; 4];
554 b.copy_from_slice(&data[offset..offset + 4]);
555 i32::from_le_bytes(b) as f64
556 }
557 DataType::Int64 => {
558 let mut b = [0u8; 8];
559 b.copy_from_slice(&data[offset..offset + 8]);
560 i64::from_le_bytes(b) as f64
561 }
562 DataType::UInt8 => data[offset] as f64,
563 DataType::UInt16 => {
564 let b = [data[offset], data[offset + 1]];
565 u16::from_le_bytes(b) as f64
566 }
567 DataType::UInt32 => {
568 let mut b = [0u8; 4];
569 b.copy_from_slice(&data[offset..offset + 4]);
570 u32::from_le_bytes(b) as f64
571 }
572 DataType::UInt64 => {
573 let mut b = [0u8; 8];
574 b.copy_from_slice(&data[offset..offset + 8]);
575 u64::from_le_bytes(b) as f64
576 }
577 DataType::Float32 => {
578 let mut b = [0u8; 4];
579 b.copy_from_slice(&data[offset..offset + 4]);
580 f32::from_le_bytes(b) as f64
581 }
582 DataType::Float64 => {
583 let mut b = [0u8; 8];
584 b.copy_from_slice(&data[offset..offset + 8]);
585 f64::from_le_bytes(b)
586 }
587 };
588 out.push(val);
589 }
590 Ok(out)
591}
592
593fn f64_to_bytes(data: &[f64], dtype: DataType) -> Result<Vec<u8>> {
595 let elem = dtype.byte_size();
596 let mut out = Vec::with_capacity(data.len() * elem);
597 for &val in data {
598 match dtype {
599 DataType::Bool => out.push(if val != 0.0 { 1 } else { 0 }),
600 DataType::Int8 => out.push(val as i8 as u8),
601 DataType::Int16 => out.extend_from_slice(&(val as i16).to_le_bytes()),
602 DataType::Int32 => out.extend_from_slice(&(val as i32).to_le_bytes()),
603 DataType::Int64 => out.extend_from_slice(&(val as i64).to_le_bytes()),
604 DataType::UInt8 => out.push(val as u8),
605 DataType::UInt16 => out.extend_from_slice(&(val as u16).to_le_bytes()),
606 DataType::UInt32 => out.extend_from_slice(&(val as u32).to_le_bytes()),
607 DataType::UInt64 => out.extend_from_slice(&(val as u64).to_le_bytes()),
608 DataType::Float32 => out.extend_from_slice(&(val as f32).to_le_bytes()),
609 DataType::Float64 => out.extend_from_slice(&val.to_le_bytes()),
610 }
611 }
612 Ok(out)
613}
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618 use std::fs;
619
620 fn temp_store(name: &str) -> (DirectoryStore, std::path::PathBuf) {
621 let dir = std::env::temp_dir().join(format!("zarr_array_test_{name}"));
622 let _ = fs::remove_dir_all(&dir);
623 let store = DirectoryStore::open(&dir).expect("open store");
624 (store, dir)
625 }
626
627 #[test]
628 fn test_create_and_write_read_chunk_v2() {
629 let (store, dir) = temp_store("v2_chunk");
630 let arr = ZarrArray::create(
631 store,
632 "data",
633 vec![10, 20],
634 vec![5, 10],
635 DataType::Float64,
636 ZarrVersion::V2,
637 )
638 .expect("create");
639
640 assert_eq!(arr.shape(), &[10, 20]);
641 assert_eq!(arr.ndim(), 2);
642 assert_eq!(arr.num_chunks(), vec![2, 2]);
643
644 let chunk_data: Vec<f64> = (0..50).map(|i| i as f64).collect();
646 arr.write_chunk(&[0, 0], &chunk_data).expect("write chunk");
647
648 let read = arr.read_chunk(&[0, 0]).expect("read chunk");
650 assert_eq!(read, chunk_data);
651
652 let fill = arr.read_chunk(&[1, 1]).expect("read missing");
654 assert!(fill.iter().all(|&v| v == 0.0));
655 assert_eq!(fill.len(), 50);
656
657 let _ = fs::remove_dir_all(&dir);
658 }
659
660 #[test]
661 fn test_create_and_write_read_chunk_v3() {
662 let (store, dir) = temp_store("v3_chunk");
663 let arr = ZarrArray::create(
664 store,
665 "arr3",
666 vec![8],
667 vec![4],
668 DataType::Int32,
669 ZarrVersion::V3,
670 )
671 .expect("create");
672
673 let data: Vec<f64> = vec![10.0, 20.0, 30.0, 40.0];
674 arr.write_chunk(&[0], &data).expect("write");
675 let read = arr.read_chunk(&[0]).expect("read");
676 assert_eq!(read, data);
677
678 let _ = fs::remove_dir_all(&dir);
679 }
680
681 #[test]
682 fn test_open_existing_v2() {
683 let (store, dir) = temp_store("v2_open");
684 {
685 let arr = ZarrArray::create(
686 store,
687 "myarr",
688 vec![100],
689 vec![25],
690 DataType::Float32,
691 ZarrVersion::V2,
692 )
693 .expect("create");
694 let chunk: Vec<f64> = (0..25).map(|i| i as f64 * 0.5).collect();
695 arr.write_chunk(&[2], &chunk).expect("write");
696 }
697
698 let store2 = DirectoryStore::open(&dir).expect("reopen");
700 let arr2 = ZarrArray::open(store2, "myarr").expect("open");
701 assert_eq!(arr2.shape(), &[100]);
702 assert_eq!(arr2.data_type(), DataType::Float32);
703 let read = arr2.read_chunk(&[2]).expect("read");
704 assert_eq!(read.len(), 25);
706 assert!((read[0] - 0.0).abs() < 1e-6);
707 assert!((read[1] - 0.5).abs() < 1e-6);
708
709 let _ = fs::remove_dir_all(&dir);
710 }
711
712 #[test]
713 fn test_open_existing_v3() {
714 let (store, dir) = temp_store("v3_open");
715 {
716 let arr = ZarrArray::create(
717 store,
718 "v3arr",
719 vec![6, 4],
720 vec![3, 2],
721 DataType::Float64,
722 ZarrVersion::V3,
723 )
724 .expect("create");
725 let chunk: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
726 arr.write_chunk(&[0, 0], &chunk).expect("write");
727 }
728
729 let store2 = DirectoryStore::open(&dir).expect("reopen");
730 let arr2 = ZarrArray::open(store2, "v3arr").expect("open");
731 assert_eq!(arr2.shape(), &[6, 4]);
732 assert_eq!(arr2.version(), ZarrVersion::V3);
733 let read = arr2.read_chunk(&[0, 0]).expect("read");
734 assert_eq!(read, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
735
736 let _ = fs::remove_dir_all(&dir);
737 }
738
739 #[test]
740 fn test_slice_read_within_chunk() {
741 let (store, dir) = temp_store("slice_within");
742 let arr = ZarrArray::create(
743 store,
744 "s",
745 vec![10],
746 vec![5],
747 DataType::Float64,
748 ZarrVersion::V2,
749 )
750 .expect("create");
751
752 let chunk0: Vec<f64> = vec![10.0, 11.0, 12.0, 13.0, 14.0];
753 arr.write_chunk(&[0], &chunk0).expect("write");
754
755 let slice = arr.get(&[1], &[4]).expect("get");
756 assert_eq!(slice, vec![11.0, 12.0, 13.0]);
757
758 let _ = fs::remove_dir_all(&dir);
759 }
760
761 #[test]
762 fn test_slice_read_across_chunks() {
763 let (store, dir) = temp_store("slice_across");
764 let arr = ZarrArray::create(
765 store,
766 "x",
767 vec![10],
768 vec![4],
769 DataType::Float64,
770 ZarrVersion::V2,
771 )
772 .expect("create");
773
774 arr.write_chunk(&[0], &[0.0, 1.0, 2.0, 3.0]).expect("w0");
776 arr.write_chunk(&[1], &[4.0, 5.0, 6.0, 7.0]).expect("w1");
777 arr.write_chunk(&[2], &[8.0, 9.0, 0.0, 0.0]).expect("w2");
778
779 let slice = arr.get(&[2], &[7]).expect("get across");
780 assert_eq!(slice, vec![2.0, 3.0, 4.0, 5.0, 6.0]);
781
782 let _ = fs::remove_dir_all(&dir);
783 }
784
785 #[test]
786 fn test_slice_write_across_chunks() {
787 let (store, dir) = temp_store("slice_write");
788 let arr = ZarrArray::create_with_options(
789 store,
790 "w",
791 vec![8],
792 vec![4],
793 DataType::Float64,
794 ZarrVersion::V2,
795 -1.0,
796 false,
797 )
798 .expect("create");
799
800 arr.set(&[2], &[6], &[10.0, 20.0, 30.0, 40.0]).expect("set");
802
803 let all = arr.get(&[0], &[8]).expect("get all");
804 assert_eq!(all, vec![-1.0, -1.0, 10.0, 20.0, 30.0, 40.0, -1.0, -1.0]);
805
806 let _ = fs::remove_dir_all(&dir);
807 }
808
809 #[test]
810 fn test_2d_slice_across_chunks() {
811 let (store, dir) = temp_store("2d_slice");
812 let arr = ZarrArray::create(
813 store,
814 "m",
815 vec![6, 6],
816 vec![3, 3],
817 DataType::Float64,
818 ZarrVersion::V2,
819 )
820 .expect("create");
821
822 let c00: Vec<f64> = (0..9).map(|i| (i + 1) as f64).collect();
824 arr.write_chunk(&[0, 0], &c00).expect("write 0,0");
825
826 let c01: Vec<f64> = (10..19).map(|i| i as f64).collect();
828 arr.write_chunk(&[0, 1], &c01).expect("write 0,1");
829
830 let slice = arr.get(&[0, 1], &[2, 5]).expect("get 2d");
832 assert_eq!(slice, vec![2.0, 3.0, 10.0, 11.0, 5.0, 6.0, 13.0, 14.0]);
836
837 let _ = fs::remove_dir_all(&dir);
838 }
839
840 #[test]
841 fn test_uint8_dtype() {
842 let (store, dir) = temp_store("uint8");
843 let arr = ZarrArray::create(
844 store,
845 "u8",
846 vec![4],
847 vec![4],
848 DataType::UInt8,
849 ZarrVersion::V2,
850 )
851 .expect("create");
852
853 arr.write_chunk(&[0], &[0.0, 127.0, 200.0, 255.0])
854 .expect("write");
855 let read = arr.read_chunk(&[0]).expect("read");
856 assert_eq!(read, vec![0.0, 127.0, 200.0, 255.0]);
857
858 let _ = fs::remove_dir_all(&dir);
859 }
860
861 #[test]
862 fn test_fill_value_custom() {
863 let (store, dir) = temp_store("fill_custom");
864 let arr = ZarrArray::create_with_options(
865 store,
866 "f",
867 vec![10],
868 vec![5],
869 DataType::Float64,
870 ZarrVersion::V2,
871 f64::NAN,
872 false,
873 )
874 .expect("create");
875
876 let read = arr.read_chunk(&[0]).expect("read missing");
877 assert!(read.iter().all(|v| v.is_nan()));
878
879 let _ = fs::remove_dir_all(&dir);
880 }
881
882 #[test]
883 fn test_uncompressed_array() {
884 let (store, dir) = temp_store("no_compress");
885 let arr = ZarrArray::create_with_options(
886 store,
887 "nc",
888 vec![4],
889 vec![4],
890 DataType::Float64,
891 ZarrVersion::V2,
892 0.0,
893 false,
894 )
895 .expect("create");
896
897 let data = vec![1.0, 2.0, 3.0, 4.0];
898 arr.write_chunk(&[0], &data).expect("write");
899 let read = arr.read_chunk(&[0]).expect("read");
900 assert_eq!(read, data);
901
902 let _ = fs::remove_dir_all(&dir);
903 }
904}