1use crate::attribute::AttrBuilder;
8use crate::error::{Hdf5Error, Result};
9use crate::file::{borrow_inner, borrow_inner_mut, clone_inner, H5FileInner, SharedInner};
10use crate::format::messages::datatype::DatatypeMessage;
11use crate::types::H5Type;
12
13pub struct DatasetBuilder<T: H5Type> {
30 file_inner: SharedInner,
31 shape: Option<Vec<usize>>,
32 chunk_dims: Option<Vec<usize>>,
33 max_shape: Option<Vec<Option<usize>>>,
34 deflate_level: Option<u32>,
35 shuffle_deflate_level: Option<u32>,
36 custom_pipeline: Option<crate::format::messages::filter::FilterPipeline>,
37 group_path: Option<String>,
38 fill_value: Option<Vec<u8>>,
39 datatype_override: Option<crate::format::messages::datatype::DatatypeMessage>,
40 _marker: std::marker::PhantomData<T>,
41}
42
43impl<T: H5Type> DatasetBuilder<T> {
44 pub(crate) fn new(file_inner: SharedInner) -> Self {
45 Self {
46 file_inner,
47 shape: None,
48 chunk_dims: None,
49 max_shape: None,
50 deflate_level: None,
51 shuffle_deflate_level: None,
52 custom_pipeline: None,
53 group_path: None,
54 fill_value: None,
55 datatype_override: None,
56 _marker: std::marker::PhantomData,
57 }
58 }
59
60 pub(crate) fn new_in_group(file_inner: SharedInner, group_path: String) -> Self {
61 Self {
62 file_inner,
63 shape: None,
64 chunk_dims: None,
65 max_shape: None,
66 deflate_level: None,
67 shuffle_deflate_level: None,
68 custom_pipeline: None,
69 group_path: Some(group_path),
70 fill_value: None,
71 datatype_override: None,
72 _marker: std::marker::PhantomData,
73 }
74 }
75
76 #[must_use]
81 pub fn shape<S: AsRef<[usize]>>(mut self, dims: S) -> Self {
82 self.shape = Some(dims.as_ref().to_vec());
83 self
84 }
85
86 #[must_use]
88 pub fn scalar(mut self) -> Self {
89 self.shape = Some(vec![]);
90 self
91 }
92
93 #[must_use]
99 pub fn chunk(mut self, chunk_dims: &[usize]) -> Self {
100 self.chunk_dims = Some(chunk_dims.to_vec());
101 self
102 }
103
104 #[must_use]
108 pub fn resizable(mut self) -> Self {
109 self.max_shape = Some(vec![None; self.shape.as_ref().map_or(0, |s| s.len())]);
110 self
111 }
112
113 #[must_use]
115 pub fn max_shape(mut self, max: &[Option<usize>]) -> Self {
116 self.max_shape = Some(max.to_vec());
117 self
118 }
119
120 #[must_use]
125 pub fn deflate(mut self, level: u32) -> Self {
126 self.deflate_level = Some(level);
127 self
128 }
129
130 #[must_use]
136 pub fn shuffle_deflate(mut self, level: u32) -> Self {
137 self.shuffle_deflate_level = Some(level);
138 self
139 }
140
141 #[must_use]
145 pub fn zstd(mut self, level: u32) -> Self {
146 self.custom_pipeline = Some(crate::format::messages::filter::FilterPipeline::zstd(level));
147 self
148 }
149
150 #[must_use]
155 pub fn filter_pipeline(
156 mut self,
157 pipeline: crate::format::messages::filter::FilterPipeline,
158 ) -> Self {
159 self.custom_pipeline = Some(pipeline);
160 self
161 }
162
163 #[must_use]
176 pub fn datatype(mut self, dt: crate::format::messages::datatype::DatatypeMessage) -> Self {
177 self.datatype_override = Some(dt);
178 self
179 }
180
181 #[must_use]
198 pub fn fill_value(mut self, value: T) -> Self {
199 let es = T::element_size();
200 let raw = unsafe { std::slice::from_raw_parts(&value as *const T as *const u8, es) };
204 self.fill_value = Some(raw.to_vec());
205 self
206 }
207
208 pub fn create(self, name: &str) -> Result<H5Dataset> {
213 let shape = self.shape.ok_or_else(|| {
214 Hdf5Error::InvalidState("shape must be set before calling create()".into())
215 })?;
216
217 let full_name = if let Some(ref gp) = self.group_path {
219 if gp == "/" {
220 name.to_string()
221 } else {
222 let trimmed = gp.trim_start_matches('/');
223 format!("{}/{}", trimmed, name)
224 }
225 } else {
226 name.to_string()
227 };
228 let group_path = self.group_path.clone();
229 let fill_value = self.fill_value.clone();
230
231 let dims_u64: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
232 let datatype = self.datatype_override.clone().unwrap_or_else(T::hdf5_type);
233 let element_size = datatype.element_size() as usize;
240
241 let wants_filter = self.custom_pipeline.is_some()
248 || self.shuffle_deflate_level.is_some()
249 || self.deflate_level.is_some();
250 let auto_chunk: Option<Vec<usize>> =
251 if self.chunk_dims.is_none() && wants_filter && !shape.is_empty() {
252 Some(shape.iter().map(|&d| d.max(1)).collect())
253 } else {
254 None
255 };
256
257 if let Some(chunk_dims) = self.chunk_dims.as_ref().or(auto_chunk.as_ref()) {
258 let chunk_u64: Vec<u64> = chunk_dims.iter().map(|&d| d as u64).collect();
260 let max_u64: Vec<u64> = if let Some(ref max) = self.max_shape {
261 max.iter()
262 .map(|m| m.map_or(u64::MAX, |v| v as u64))
263 .collect()
264 } else {
265 dims_u64.clone()
267 };
268
269 let n_unlimited = max_u64.iter().filter(|&&m| m == u64::MAX).count();
273 let is_btree2 = n_unlimited >= 2;
274 let is_fixed_array = n_unlimited == 0;
275
276 let index = {
277 let inner = borrow_inner(&self.file_inner);
278 match &*inner {
279 H5FileInner::Writer(writer) => {
280 let idx = if is_btree2 {
281 if wants_filter {
282 return Err(Hdf5Error::InvalidState(
283 "compression of v2 B-tree (multi-unlimited-dimension) \
284 datasets is not yet supported"
285 .into(),
286 ));
287 }
288 writer.create_btree_v2_dataset(
289 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64,
290 )?
291 } else if is_fixed_array {
292 if wants_filter {
298 let pipeline = if let Some(p) = self.custom_pipeline {
299 p
300 } else if let Some(level) = self.shuffle_deflate_level {
301 crate::format::messages::filter::FilterPipeline::shuffle_deflate(
302 T::element_size() as u32,
303 level,
304 )
305 } else {
306 crate::format::messages::filter::FilterPipeline::deflate(
308 self.deflate_level.unwrap(),
309 )
310 };
311 writer.create_fixed_array_dataset_with_pipeline(
312 &full_name, datatype, &dims_u64, &chunk_u64, pipeline,
313 )?
314 } else {
315 writer.create_fixed_array_dataset(
316 &full_name, datatype, &dims_u64, &chunk_u64,
317 )?
318 }
319 } else if let Some(pipeline) = self.custom_pipeline {
320 writer.create_chunked_dataset_with_pipeline(
321 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64, pipeline,
322 )?
323 } else if let Some(level) = self.shuffle_deflate_level {
324 let pipeline =
325 crate::format::messages::filter::FilterPipeline::shuffle_deflate(
326 T::element_size() as u32,
327 level,
328 );
329 writer.create_chunked_dataset_with_pipeline(
330 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64, pipeline,
331 )?
332 } else if let Some(level) = self.deflate_level {
333 writer.create_chunked_dataset_compressed(
334 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64, level,
335 )?
336 } else {
337 writer.create_chunked_dataset(
338 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64,
339 )?
340 };
341 if let Some(ref gp) = group_path {
342 if gp != "/" {
343 writer.assign_dataset_to_group(gp, idx)?;
344 }
345 }
346 if let Some(ref fv) = fill_value {
347 writer.set_dataset_fill_value(idx, fv.clone())?;
348 }
349 idx
350 }
351 H5FileInner::Reader(_) => {
352 return Err(Hdf5Error::InvalidState(
353 "cannot create a dataset in read mode".into(),
354 ));
355 }
356 H5FileInner::Closed => {
357 return Err(Hdf5Error::InvalidState("file is closed".into()));
358 }
359 }
360 };
361
362 Ok(H5Dataset {
363 file_inner: clone_inner(&self.file_inner),
364 info: DatasetInfo::Writer {
365 index,
366 shape,
367 element_size,
368 chunked: true,
369 btree2: is_btree2,
370 fixed_array: is_fixed_array,
371 },
372 })
373 } else {
374 let index = {
376 let inner = borrow_inner(&self.file_inner);
377 match &*inner {
378 H5FileInner::Writer(writer) => {
379 let idx = writer.create_dataset(&full_name, datatype, &dims_u64)?;
380 if let Some(ref gp) = group_path {
381 if gp != "/" {
382 writer.assign_dataset_to_group(gp, idx)?;
383 }
384 }
385 if let Some(ref fv) = fill_value {
386 writer.set_dataset_fill_value(idx, fv.clone())?;
387 }
388 idx
389 }
390 H5FileInner::Reader(_) => {
391 return Err(Hdf5Error::InvalidState(
392 "cannot create a dataset in read mode".into(),
393 ));
394 }
395 H5FileInner::Closed => {
396 return Err(Hdf5Error::InvalidState("file is closed".into()));
397 }
398 }
399 };
400
401 Ok(H5Dataset {
402 file_inner: clone_inner(&self.file_inner),
403 info: DatasetInfo::Writer {
404 index,
405 shape,
406 element_size,
407 chunked: false,
408 btree2: false,
409 fixed_array: false,
410 },
411 })
412 }
413 }
414}
415
416enum DatasetInfo {
422 Writer {
424 index: usize,
426 shape: Vec<usize>,
428 element_size: usize,
430 chunked: bool,
432 btree2: bool,
434 fixed_array: bool,
436 },
437 Reader {
439 name: String,
441 shape: Vec<usize>,
443 element_size: usize,
445 },
446}
447
448pub struct H5Dataset {
458 file_inner: SharedInner,
459 info: DatasetInfo,
460}
461
462impl H5Dataset {
463 pub(crate) fn new_reader(
465 file_inner: SharedInner,
466 name: String,
467 shape: Vec<usize>,
468 element_size: usize,
469 ) -> Self {
470 Self {
471 file_inner,
472 info: DatasetInfo::Reader {
473 name,
474 shape,
475 element_size,
476 },
477 }
478 }
479
480 pub(crate) fn new_writer(
486 file_inner: SharedInner,
487 index: usize,
488 shape: Vec<usize>,
489 element_size: usize,
490 chunked: bool,
491 btree2: bool,
492 fixed_array: bool,
493 ) -> Self {
494 Self {
495 file_inner,
496 info: DatasetInfo::Writer {
497 index,
498 shape,
499 element_size,
500 chunked,
501 btree2,
502 fixed_array,
503 },
504 }
505 }
506
507 pub fn shape(&self) -> Vec<usize> {
509 match &self.info {
510 DatasetInfo::Writer { shape, .. } => shape.clone(),
511 DatasetInfo::Reader { shape, .. } => shape.clone(),
512 }
513 }
514
515 pub fn ndims(&self) -> usize {
517 match &self.info {
518 DatasetInfo::Writer { shape, .. } => shape.len(),
519 DatasetInfo::Reader { shape, .. } => shape.len(),
520 }
521 }
522
523 pub fn total_elements(&self) -> usize {
525 match &self.info {
526 DatasetInfo::Writer { shape, .. } => shape.iter().product(),
527 DatasetInfo::Reader { shape, .. } => shape.iter().product(),
528 }
529 }
530
531 pub fn element_size(&self) -> usize {
533 match &self.info {
534 DatasetInfo::Writer { element_size, .. } => *element_size,
535 DatasetInfo::Reader { element_size, .. } => *element_size,
536 }
537 }
538
539 pub fn datatype(&self) -> Result<DatatypeMessage> {
570 match &self.info {
571 DatasetInfo::Reader { name, .. } => {
572 let inner = borrow_inner(&self.file_inner);
573 match &*inner {
574 H5FileInner::Reader(reader) => reader
575 .dataset_info(name)
576 .map(|info| info.datatype.clone())
577 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
578 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
579 }
580 }
581 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
582 "datatype() is only available in read mode".into(),
583 )),
584 }
585 }
586
587 pub fn chunk_dims(&self) -> Option<Vec<usize>> {
589 match &self.info {
590 DatasetInfo::Reader { name, .. } => {
591 let inner = borrow_inner(&self.file_inner);
592 if let H5FileInner::Reader(reader) = &*inner {
593 if let Some(info) = reader.dataset_info(name) {
594 use crate::format::messages::data_layout::DataLayoutMessage;
595 let chunk_dims = match &info.layout {
596 DataLayoutMessage::ChunkedV4 { chunk_dims, .. }
597 | DataLayoutMessage::ChunkedV3 { chunk_dims, .. } => Some(chunk_dims),
598 _ => None,
599 };
600 if let Some(chunk_dims) = chunk_dims {
601 return Some(
603 chunk_dims[..chunk_dims.len() - 1]
604 .iter()
605 .map(|&d| d as usize)
606 .collect(),
607 );
608 }
609 }
610 }
611 None
612 }
613 DatasetInfo::Writer { .. } => None,
614 }
615 }
616
617 pub fn is_chunked(&self) -> bool {
619 match &self.info {
620 DatasetInfo::Writer { chunked, .. } => *chunked,
621 DatasetInfo::Reader { name, .. } => {
622 let inner = borrow_inner(&self.file_inner);
623 match &*inner {
624 H5FileInner::Reader(reader) => {
625 if let Some(info) = reader.dataset_info(name) {
626 use crate::format::messages::data_layout::DataLayoutMessage;
627 matches!(
628 info.layout,
629 DataLayoutMessage::ChunkedV4 { .. }
630 | DataLayoutMessage::ChunkedV3 { .. }
631 )
632 } else {
633 false
634 }
635 }
636 _ => false,
637 }
638 }
639 }
640 }
641
642 pub fn attr_names(&self) -> Result<Vec<String>> {
644 match &self.info {
645 DatasetInfo::Reader { name, .. } => {
646 let inner = borrow_inner(&self.file_inner);
647 match &*inner {
648 H5FileInner::Reader(reader) => Ok(reader.dataset_attr_names(name)?),
649 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
650 }
651 }
652 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
653 "attr_names not available in write mode".into(),
654 )),
655 }
656 }
657
658 pub fn attr(&self, attr_name: &str) -> Result<crate::attribute::H5Attribute> {
660 match &self.info {
661 DatasetInfo::Reader { name, .. } => {
662 let inner = borrow_inner(&self.file_inner);
663 match &*inner {
664 H5FileInner::Reader(reader) => {
665 let attr_msg = reader.dataset_attr(name, attr_name)?.clone();
666 Ok(crate::attribute::H5Attribute::new_reader(
667 clone_inner(&self.file_inner),
668 attr_msg,
669 ))
670 }
671 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
672 }
673 }
674 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
675 "attr() not available in write mode".into(),
676 )),
677 }
678 }
679
680 pub fn new_attr<T: 'static>(&self) -> AttrBuilder<'_, T> {
696 let ds_index = match &self.info {
697 DatasetInfo::Writer { index, .. } => *index,
698 DatasetInfo::Reader { .. } => {
699 usize::MAX
702 }
703 };
704 AttrBuilder::new(&self.file_inner, ds_index)
705 }
706
707 pub fn write_raw<T: H5Type>(&self, data: &[T]) -> Result<()> {
719 match &self.info {
720 DatasetInfo::Writer {
721 index,
722 shape,
723 element_size,
724 chunked,
725 btree2,
726 fixed_array,
727 } => {
728 let total_elements: usize = shape.iter().product();
729 if data.len() != total_elements {
730 return Err(Hdf5Error::InvalidState(format!(
731 "data length {} does not match dataset size {}",
732 data.len(),
733 total_elements,
734 )));
735 }
736
737 if T::element_size() != *element_size {
739 return Err(Hdf5Error::TypeMismatch(format!(
740 "write type has element size {} but dataset expects {}",
741 T::element_size(),
742 element_size,
743 )));
744 }
745
746 let byte_len = data.len() * T::element_size();
750 let raw =
751 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) };
752
753 if *chunked {
754 return self.write_full_image_chunked(
758 *index,
759 *btree2,
760 *fixed_array,
761 raw,
762 *element_size,
763 );
764 }
765
766 let inner = borrow_inner(&self.file_inner);
767 match &*inner {
768 H5FileInner::Writer(writer) => {
769 writer.write_dataset_raw(*index, raw)?;
770 Ok(())
771 }
772 _ => Err(Hdf5Error::InvalidState(
773 "file is no longer in write mode".into(),
774 )),
775 }
776 }
777 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
778 "cannot write to a dataset opened in read mode".into(),
779 )),
780 }
781 }
782
783 pub fn write_raw_bytes(&self, bytes: &[u8]) -> Result<()> {
818 match &self.info {
819 DatasetInfo::Writer {
820 index,
821 shape,
822 element_size,
823 chunked,
824 btree2,
825 fixed_array,
826 } => {
827 let expected: usize = shape.iter().product::<usize>() * *element_size;
828 if bytes.len() != expected {
829 return Err(Hdf5Error::InvalidState(format!(
830 "raw byte length {} does not match dataset size {} \
831 (product(shape) * element_size {})",
832 bytes.len(),
833 expected,
834 element_size,
835 )));
836 }
837 if *chunked {
838 return self.write_full_image_chunked(
841 *index,
842 *btree2,
843 *fixed_array,
844 bytes,
845 *element_size,
846 );
847 }
848 let inner = borrow_inner(&self.file_inner);
849 match &*inner {
850 H5FileInner::Writer(writer) => {
851 writer.write_dataset_raw(*index, bytes)?;
852 Ok(())
853 }
854 _ => Err(Hdf5Error::InvalidState(
855 "file is no longer in write mode".into(),
856 )),
857 }
858 }
859 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
860 "cannot write to a dataset opened in read mode".into(),
861 )),
862 }
863 }
864
865 fn write_full_image_chunked(
875 &self,
876 index: usize,
877 btree2: bool,
878 fixed_array: bool,
879 bytes: &[u8],
880 element_size: usize,
881 ) -> Result<()> {
882 let inner = borrow_inner(&self.file_inner);
883 let writer = match &*inner {
884 H5FileInner::Writer(w) => w,
885 _ => {
886 return Err(Hdf5Error::InvalidState(
887 "file is no longer in write mode".into(),
888 ))
889 }
890 };
891 let chunk_dims = writer
892 .dataset_chunk_dims(index)
893 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
894 .to_vec();
895 let dims = writer.dataset_dims(index).to_vec();
896 let rank = dims.len();
897
898 let mut grid = vec![0u64; rank];
900 for d in 0..rank {
901 grid[d] = if chunk_dims[d] > 0 {
902 dims[d].div_ceil(chunk_dims[d])
903 } else {
904 0
905 };
906 }
907 let total_chunks: u64 = grid.iter().product();
908
909 for linear in 0..total_chunks {
910 let mut rem = linear;
912 let mut coords = vec![0u64; rank];
913 for d in (0..rank).rev() {
914 coords[d] = rem % grid[d];
915 rem /= grid[d];
916 }
917 let chunk_buf = Self::gather_chunk(bytes, &dims, &chunk_dims, &coords, element_size);
918 if fixed_array {
919 writer.write_chunk_fixed_array(index, &coords, &chunk_buf)?;
920 } else if btree2 {
921 writer.write_chunk_btree_v2(index, &coords, &chunk_buf)?;
922 } else {
923 writer.write_chunk(index, linear, &chunk_buf)?;
926 }
927 }
928 Ok(())
929 }
930
931 fn gather_chunk(
937 source: &[u8],
938 dims: &[u64],
939 chunk_dims: &[u64],
940 coords: &[u64],
941 element_size: usize,
942 ) -> Vec<u8> {
943 let rank = dims.len();
944 let chunk_elems: u64 = chunk_dims.iter().product();
945 let mut out = vec![0u8; chunk_elems as usize * element_size];
946 if rank == 0 {
947 if source.len() >= element_size {
949 out[..element_size].copy_from_slice(&source[..element_size]);
950 }
951 return out;
952 }
953
954 let mut extent = vec![0u64; rank];
957 for d in 0..rank {
958 let start = coords[d] * chunk_dims[d];
959 let end = ((coords[d] + 1) * chunk_dims[d]).min(dims[d]);
960 extent[d] = end.saturating_sub(start);
961 }
962 if extent.contains(&0) {
963 return out; }
965
966 let mut src_stride = vec![1u64; rank];
969 let mut dst_stride = vec![1u64; rank];
970 for d in (0..rank - 1).rev() {
971 src_stride[d] = src_stride[d + 1] * dims[d + 1];
972 dst_stride[d] = dst_stride[d + 1] * chunk_dims[d + 1];
973 }
974
975 let last = rank - 1;
977 let run = extent[last] as usize * element_size;
978 let outer: u64 = extent[..last].iter().product::<u64>().max(1);
979 let mut idx = vec![0u64; rank]; for _ in 0..outer {
981 let mut src_off = 0u64;
982 let mut dst_off = 0u64;
983 for d in 0..rank {
984 let global = coords[d] * chunk_dims[d] + idx[d];
985 src_off += global * src_stride[d];
986 dst_off += idx[d] * dst_stride[d];
987 }
988 let s = src_off as usize * element_size;
989 let dpos = dst_off as usize * element_size;
990 out[dpos..dpos + run].copy_from_slice(&source[s..s + run]);
991
992 let mut d = last;
995 while d > 0 {
996 d -= 1;
997 idx[d] += 1;
998 if idx[d] < extent[d] {
999 break;
1000 }
1001 idx[d] = 0;
1002 }
1003 }
1004 out
1005 }
1006
1007 pub fn write_chunk(&self, chunk_idx: usize, data: &[u8]) -> Result<()> {
1015 match &self.info {
1016 DatasetInfo::Writer {
1017 index,
1018 chunked,
1019 btree2,
1020 fixed_array,
1021 ..
1022 } => {
1023 if !*chunked {
1024 return Err(Hdf5Error::InvalidState(
1025 "write_chunk is only for chunked datasets".into(),
1026 ));
1027 }
1028 if *btree2 {
1029 return Err(Hdf5Error::InvalidState(
1030 "this dataset uses a v2 B-tree chunk index; use write_chunk_at \
1031 with the chunk's grid coordinates"
1032 .into(),
1033 ));
1034 }
1035
1036 let inner = borrow_inner(&self.file_inner);
1037 match &*inner {
1038 H5FileInner::Writer(writer) => {
1039 if *fixed_array {
1040 let chunk_dims = writer
1043 .dataset_chunk_dims(*index)
1044 .ok_or_else(|| {
1045 Hdf5Error::InvalidState("dataset has no chunk info".into())
1046 })?
1047 .to_vec();
1048 let dims = writer.dataset_dims(*index).to_vec();
1049 let mut grid = vec![0u64; dims.len()];
1050 for d in 0..dims.len() {
1051 grid[d] = if chunk_dims[d] > 0 {
1052 dims[d].div_ceil(chunk_dims[d])
1053 } else {
1054 1
1055 };
1056 }
1057 if grid.contains(&0) {
1060 return Err(Hdf5Error::InvalidState(
1061 "dataset has a zero-extent dimension and no chunks".into(),
1062 ));
1063 }
1064 let mut rem = chunk_idx as u64;
1065 let mut coords = vec![0u64; dims.len()];
1066 for d in (0..dims.len()).rev() {
1067 coords[d] = rem % grid[d];
1068 rem /= grid[d];
1069 }
1070 if rem != 0 {
1072 return Err(Hdf5Error::InvalidState(format!(
1073 "chunk index {chunk_idx} is out of range for this dataset"
1074 )));
1075 }
1076 writer.write_chunk_fixed_array(*index, &coords, data)?;
1077 } else {
1078 writer.write_chunk(*index, chunk_idx as u64, data)?;
1079 }
1080 Ok(())
1081 }
1082 _ => Err(Hdf5Error::InvalidState(
1083 "file is no longer in write mode".into(),
1084 )),
1085 }
1086 }
1087 DatasetInfo::Reader { .. } => {
1088 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
1089 }
1090 }
1091 }
1092
1093 pub fn write_chunk_raw(&self, chunk_idx: usize, data: &[u8], filter_mask: u32) -> Result<()> {
1119 match &self.info {
1120 DatasetInfo::Writer {
1121 index,
1122 chunked,
1123 btree2,
1124 fixed_array,
1125 ..
1126 } => {
1127 if !*chunked {
1128 return Err(Hdf5Error::InvalidState(
1129 "write_chunk_raw is only for chunked datasets".into(),
1130 ));
1131 }
1132 if *btree2 {
1133 return Err(Hdf5Error::InvalidState(
1134 "direct chunk writes are not supported for v2-B-tree-indexed \
1135 datasets (two or more unlimited dimensions)"
1136 .into(),
1137 ));
1138 }
1139
1140 let inner = borrow_inner(&self.file_inner);
1141 match &*inner {
1142 H5FileInner::Writer(writer) => {
1143 if *fixed_array {
1144 let chunk_dims = writer
1147 .dataset_chunk_dims(*index)
1148 .ok_or_else(|| {
1149 Hdf5Error::InvalidState("dataset has no chunk info".into())
1150 })?
1151 .to_vec();
1152 let dims = writer.dataset_dims(*index).to_vec();
1153 let mut grid = vec![0u64; dims.len()];
1154 for d in 0..dims.len() {
1155 grid[d] = if chunk_dims[d] > 0 {
1156 dims[d].div_ceil(chunk_dims[d])
1157 } else {
1158 1
1159 };
1160 }
1161 if grid.contains(&0) {
1164 return Err(Hdf5Error::InvalidState(
1165 "dataset has a zero-extent dimension and no chunks".into(),
1166 ));
1167 }
1168 let mut rem = chunk_idx as u64;
1169 let mut coords = vec![0u64; dims.len()];
1170 for d in (0..dims.len()).rev() {
1171 coords[d] = rem % grid[d];
1172 rem /= grid[d];
1173 }
1174 if rem != 0 {
1176 return Err(Hdf5Error::InvalidState(format!(
1177 "chunk index {chunk_idx} is out of range for this dataset"
1178 )));
1179 }
1180 writer.write_compressed_chunk_fixed_array(
1181 *index,
1182 &coords,
1183 data,
1184 filter_mask,
1185 )?;
1186 } else {
1187 writer.write_compressed_chunk(
1188 *index,
1189 chunk_idx as u64,
1190 data,
1191 filter_mask,
1192 )?;
1193 }
1194 Ok(())
1195 }
1196 _ => Err(Hdf5Error::InvalidState(
1197 "file is no longer in write mode".into(),
1198 )),
1199 }
1200 }
1201 DatasetInfo::Reader { .. } => {
1202 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
1203 }
1204 }
1205 }
1206
1207 pub fn write_chunk_at(&self, chunk_coords: &[usize], data: &[u8]) -> Result<()> {
1228 match &self.info {
1229 DatasetInfo::Writer {
1230 index,
1231 chunked,
1232 btree2,
1233 fixed_array,
1234 ..
1235 } => {
1236 if !*chunked {
1237 return Err(Hdf5Error::InvalidState(
1238 "write_chunk_at is only for chunked datasets".into(),
1239 ));
1240 }
1241 let coords: Vec<u64> = chunk_coords.iter().map(|&c| c as u64).collect();
1242 let btree2 = *btree2;
1243 let fixed_array = *fixed_array;
1244 let inner = borrow_inner(&self.file_inner);
1245 let writer = match &*inner {
1246 H5FileInner::Writer(w) => w,
1247 _ => {
1248 return Err(Hdf5Error::InvalidState(
1249 "file is no longer in write mode".into(),
1250 ))
1251 }
1252 };
1253 let chunk_dims = writer
1254 .dataset_chunk_dims(*index)
1255 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
1256 .to_vec();
1257 let dims = writer.dataset_dims(*index).to_vec();
1258 if coords.len() != dims.len() {
1259 return Err(Hdf5Error::InvalidState(format!(
1260 "chunk_coords has {} entries but the dataset has {} dimensions",
1261 coords.len(),
1262 dims.len()
1263 )));
1264 }
1265 if chunk_dims.len() != dims.len() {
1266 return Err(Hdf5Error::InvalidState(format!(
1267 "dataset chunk shape has {} dimensions but the dataspace has {}",
1268 chunk_dims.len(),
1269 dims.len()
1270 )));
1271 }
1272
1273 let mut new_dims = dims.clone();
1277 for d in 0..dims.len() {
1278 let needed = coords[d]
1279 .checked_add(1)
1280 .and_then(|c| c.checked_mul(chunk_dims[d]))
1281 .ok_or_else(|| {
1282 Hdf5Error::InvalidState(format!(
1283 "chunk coordinate {} in dimension {} is too large",
1284 coords[d], d
1285 ))
1286 })?;
1287 if needed > new_dims[d] {
1288 new_dims[d] = needed;
1289 }
1290 }
1291
1292 if fixed_array {
1293 writer.write_chunk_fixed_array(*index, &coords, data)?;
1295 return Ok(());
1296 }
1297
1298 if btree2 {
1299 writer.write_chunk_btree_v2(*index, &coords, data)?;
1300 } else {
1301 let mut linear = 0u64;
1304 for d in 0..dims.len() {
1305 let grid = if chunk_dims[d] > 0 {
1306 dims[d].div_ceil(chunk_dims[d])
1307 } else {
1308 1
1309 };
1310 linear = linear
1311 .checked_mul(grid)
1312 .and_then(|l| l.checked_add(coords[d]))
1313 .ok_or_else(|| {
1314 Hdf5Error::InvalidState(
1315 "chunk coordinates overflow the array index".into(),
1316 )
1317 })?;
1318 }
1319 writer.write_chunk(*index, linear, data)?;
1320 }
1321
1322 if new_dims != dims {
1323 writer.extend_dataset(*index, &new_dims)?;
1324 }
1325 Ok(())
1326 }
1327 DatasetInfo::Reader { .. } => {
1328 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
1329 }
1330 }
1331 }
1332
1333 pub fn write_chunks_batch(&self, chunks: &[(usize, &[u8])]) -> Result<()> {
1339 match &self.info {
1340 DatasetInfo::Writer { index, chunked, .. } => {
1341 if !*chunked {
1342 return Err(Hdf5Error::InvalidState(
1343 "write_chunks_batch is only for chunked datasets".into(),
1344 ));
1345 }
1346 let pairs: Vec<(u64, &[u8])> = chunks
1347 .iter()
1348 .map(|(idx, data)| (*idx as u64, *data))
1349 .collect();
1350 let inner = borrow_inner(&self.file_inner);
1351 match &*inner {
1352 H5FileInner::Writer(writer) => {
1353 writer.write_chunks_batch(*index, &pairs)?;
1354 Ok(())
1355 }
1356 _ => Err(Hdf5Error::InvalidState(
1357 "file is no longer in write mode".into(),
1358 )),
1359 }
1360 }
1361 DatasetInfo::Reader { .. } => {
1362 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
1363 }
1364 }
1365 }
1366
1367 pub fn append<T: H5Type>(&self, data: &[T]) -> Result<()> {
1390 match &self.info {
1391 DatasetInfo::Writer {
1392 index,
1393 element_size,
1394 chunked,
1395 ..
1396 } => {
1397 if !*chunked {
1398 return Err(Hdf5Error::InvalidState(
1399 "append is only for chunked datasets".into(),
1400 ));
1401 }
1402 if T::element_size() != *element_size {
1403 return Err(Hdf5Error::TypeMismatch(format!(
1404 "append type has element size {} but dataset expects {}",
1405 T::element_size(),
1406 element_size,
1407 )));
1408 }
1409
1410 let ds_index = *index;
1411 let es = *element_size;
1412
1413 let inner = borrow_inner(&self.file_inner);
1414 let writer = match &*inner {
1415 H5FileInner::Writer(w) => w,
1416 _ => {
1417 return Err(Hdf5Error::InvalidState(
1418 "file is no longer in write mode".into(),
1419 ))
1420 }
1421 };
1422
1423 let chunk_dims = writer
1424 .dataset_chunk_dims(ds_index)
1425 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
1426 .to_vec();
1427 let dims = writer.dataset_dims(ds_index).to_vec();
1428
1429 let frame_elems: usize = if dims.len() > 1 {
1431 dims[1..].iter().map(|&d| d as usize).product()
1432 } else {
1433 1
1434 };
1435
1436 if frame_elems == 0 {
1437 return Err(Hdf5Error::InvalidState(
1438 "cannot append to dataset with zero-size trailing dimensions".into(),
1439 ));
1440 }
1441
1442 if !data.len().is_multiple_of(frame_elems) {
1443 return Err(Hdf5Error::InvalidState(format!(
1444 "data length {} is not a multiple of frame size {}",
1445 data.len(),
1446 frame_elems,
1447 )));
1448 }
1449
1450 let n_new_frames = data.len() / frame_elems;
1451 let current_dim0 = dims[0] as usize;
1452
1453 let chunk_dim0 = chunk_dims[0] as usize;
1455 let frame_bytes = frame_elems * es;
1456
1457 let raw = unsafe {
1458 std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * es)
1459 };
1460
1461 let (buffered_frames, mut combined) = {
1464 let ds = writer.ds(ds_index);
1465 let mut m = ds.lock();
1466 let buffered_frames = m.append_buffered_frames as usize;
1467 let combined = std::mem::take(&mut m.append_buffer);
1468 m.append_buffered_frames = 0;
1469 (buffered_frames, combined)
1470 };
1471 combined.extend_from_slice(raw);
1472
1473 let total_frames = buffered_frames + n_new_frames;
1474 let total_bytes = combined.len();
1475
1476 let base_dim0 = current_dim0 - buffered_frames;
1478 let mut byte_pos = 0usize;
1479 let mut frame_pos = 0usize;
1480
1481 while frame_pos < total_frames {
1482 let abs_frame = base_dim0 + frame_pos;
1483 let chunk_idx = abs_frame / chunk_dim0;
1484 let remaining_frames = total_frames - frame_pos;
1485 let frames_to_fill = chunk_dim0 - (abs_frame % chunk_dim0);
1486
1487 if remaining_frames >= frames_to_fill {
1488 let end = byte_pos + frames_to_fill * frame_bytes;
1490 if frames_to_fill == chunk_dim0 {
1491 writer.write_chunk(
1492 ds_index,
1493 chunk_idx as u64,
1494 &combined[byte_pos..end],
1495 )?;
1496 } else {
1497 let offset_in_chunk = (abs_frame % chunk_dim0) * frame_bytes;
1503 let mut chunk_buf =
1504 match writer.read_chunk_if_present(ds_index, chunk_idx as u64)? {
1505 Some(existing) => existing,
1506 None => {
1507 return Err(Hdf5Error::InvalidState(format!(
1508 "cannot append into partially-written chunk {}: \
1509 its existing content was not found in the chunk \
1510 index (the file may be inconsistent)",
1511 chunk_idx
1512 )));
1513 }
1514 };
1515 chunk_buf
1516 [offset_in_chunk..offset_in_chunk + frames_to_fill * frame_bytes]
1517 .copy_from_slice(&combined[byte_pos..end]);
1518 writer.write_chunk(ds_index, chunk_idx as u64, &chunk_buf)?;
1519 }
1520 byte_pos = end;
1521 frame_pos += frames_to_fill;
1522 } else {
1523 let ds = writer.ds(ds_index);
1525 let mut m = ds.lock();
1526 m.append_buffer = combined[byte_pos..total_bytes].to_vec();
1527 m.append_buffered_frames = remaining_frames as u64;
1528 frame_pos = total_frames;
1529 }
1530 }
1531
1532 let logical_dim0 = base_dim0 + total_frames;
1534 let mut new_dims: Vec<u64> = dims;
1535 new_dims[0] = logical_dim0 as u64;
1536 writer.extend_dataset(ds_index, &new_dims)?;
1537
1538 Ok(())
1539 }
1540 DatasetInfo::Reader { .. } => {
1541 Err(Hdf5Error::InvalidState("cannot append in read mode".into()))
1542 }
1543 }
1544 }
1545
1546 pub fn extend(&self, new_dims: &[usize]) -> Result<()> {
1548 match &self.info {
1549 DatasetInfo::Writer { index, chunked, .. } => {
1550 if !*chunked {
1551 return Err(Hdf5Error::InvalidState(
1552 "extend is only for chunked datasets".into(),
1553 ));
1554 }
1555
1556 let dims_u64: Vec<u64> = new_dims.iter().map(|&d| d as u64).collect();
1557 let inner = borrow_inner(&self.file_inner);
1558 match &*inner {
1559 H5FileInner::Writer(writer) => {
1560 writer.extend_dataset(*index, &dims_u64)?;
1561 Ok(())
1562 }
1563 _ => Err(Hdf5Error::InvalidState(
1564 "file is no longer in write mode".into(),
1565 )),
1566 }
1567 }
1568 DatasetInfo::Reader { .. } => {
1569 Err(Hdf5Error::InvalidState("cannot extend in read mode".into()))
1570 }
1571 }
1572 }
1573
1574 pub fn set_extent(&self, new_dims: &[usize]) -> Result<()> {
1585 match &self.info {
1586 DatasetInfo::Writer { index, .. } => {
1587 let dims_u64: Vec<u64> = new_dims.iter().map(|&d| d as u64).collect();
1588 let inner = borrow_inner(&self.file_inner);
1589 match &*inner {
1590 H5FileInner::Writer(writer) => {
1591 writer.set_dataset_extent(*index, &dims_u64)?;
1592 Ok(())
1593 }
1594 _ => Err(Hdf5Error::InvalidState(
1595 "file is no longer in write mode".into(),
1596 )),
1597 }
1598 }
1599 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
1600 "cannot set extent in read mode".into(),
1601 )),
1602 }
1603 }
1604
1605 pub fn flush(&self) -> Result<()> {
1607 match &self.info {
1608 DatasetInfo::Writer { index, .. } => {
1609 let inner = borrow_inner(&self.file_inner);
1610 match &*inner {
1611 H5FileInner::Writer(writer) => {
1612 writer.flush_dataset(*index)?;
1613 Ok(())
1614 }
1615 _ => Ok(()),
1616 }
1617 }
1618 DatasetInfo::Reader { .. } => Ok(()),
1619 }
1620 }
1621
1622 pub fn read_slice<T: H5Type>(&self, starts: &[usize], counts: &[usize]) -> Result<Vec<T>> {
1627 match &self.info {
1628 DatasetInfo::Reader {
1629 name, element_size, ..
1630 } => {
1631 if T::element_size() != *element_size {
1632 return Err(Hdf5Error::TypeMismatch(format!(
1633 "read type has element size {} but dataset has element size {}",
1634 T::element_size(),
1635 element_size,
1636 )));
1637 }
1638 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
1639 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
1640
1641 let raw = {
1642 let mut inner = borrow_inner_mut(&self.file_inner);
1643 match &mut *inner {
1644 H5FileInner::Reader(reader) => {
1645 reader.read_slice(name, &starts_u64, &counts_u64)?
1646 }
1647 _ => {
1648 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
1649 }
1650 }
1651 };
1652
1653 if raw.len() % T::element_size() != 0 {
1654 return Err(Hdf5Error::TypeMismatch(format!(
1655 "raw data size {} is not a multiple of element size {}",
1656 raw.len(),
1657 T::element_size(),
1658 )));
1659 }
1660
1661 let count = raw.len() / T::element_size();
1662 let mut result = Vec::<T>::with_capacity(count);
1663 unsafe {
1664 std::ptr::copy_nonoverlapping(
1665 raw.as_ptr(),
1666 result.as_mut_ptr() as *mut u8,
1667 raw.len(),
1668 );
1669 result.set_len(count);
1670 }
1671 Ok(result)
1672 }
1673 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
1674 "cannot read_slice from a dataset in write mode".into(),
1675 )),
1676 }
1677 }
1678
1679 pub fn write_slice<T: H5Type>(
1683 &self,
1684 starts: &[usize],
1685 counts: &[usize],
1686 data: &[T],
1687 ) -> Result<()> {
1688 match &self.info {
1689 DatasetInfo::Writer {
1690 index,
1691 element_size,
1692 chunked,
1693 ..
1694 } => {
1695 if *chunked {
1696 return Err(Hdf5Error::InvalidState(
1697 "write_slice is only for contiguous datasets".into(),
1698 ));
1699 }
1700 if T::element_size() != *element_size {
1701 return Err(Hdf5Error::TypeMismatch(format!(
1702 "write type has element size {} but dataset expects {}",
1703 T::element_size(),
1704 element_size,
1705 )));
1706 }
1707
1708 let expected: usize = counts.iter().product();
1709 if data.len() != expected {
1710 return Err(Hdf5Error::InvalidState(format!(
1711 "data length {} does not match slice size {}",
1712 data.len(),
1713 expected,
1714 )));
1715 }
1716
1717 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
1718 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
1719
1720 let byte_len = data.len() * T::element_size();
1721 let raw =
1722 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) };
1723
1724 let inner = borrow_inner(&self.file_inner);
1725 match &*inner {
1726 H5FileInner::Writer(writer) => {
1727 writer.write_slice(*index, &starts_u64, &counts_u64, raw)?;
1728 Ok(())
1729 }
1730 _ => Err(Hdf5Error::InvalidState(
1731 "file is no longer in write mode".into(),
1732 )),
1733 }
1734 }
1735 DatasetInfo::Reader { .. } => {
1736 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
1737 }
1738 }
1739 }
1740
1741 pub fn read_vlen_strings(&self) -> Result<Vec<String>> {
1746 match &self.info {
1747 DatasetInfo::Reader { name, .. } => {
1748 let mut inner = borrow_inner_mut(&self.file_inner);
1749 match &mut *inner {
1750 H5FileInner::Reader(reader) => Ok(reader.read_vlen_strings(name)?),
1751 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
1752 }
1753 }
1754 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
1755 "cannot read vlen strings from a dataset in write mode".into(),
1756 )),
1757 }
1758 }
1759
1760 pub fn read_vlen_bytes(&self) -> Result<Vec<Vec<u8>>> {
1767 match &self.info {
1768 DatasetInfo::Reader { name, .. } => {
1769 let mut inner = borrow_inner_mut(&self.file_inner);
1770 match &mut *inner {
1771 H5FileInner::Reader(reader) => Ok(reader.read_vlen_bytes(name)?),
1772 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
1773 }
1774 }
1775 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
1776 "cannot read vlen bytes from a dataset in write mode".into(),
1777 )),
1778 }
1779 }
1780
1781 pub fn read_raw<T: H5Type>(&self) -> Result<Vec<T>> {
1793 match &self.info {
1794 DatasetInfo::Reader {
1795 name, element_size, ..
1796 } => {
1797 if T::element_size() != *element_size {
1798 return Err(Hdf5Error::TypeMismatch(format!(
1799 "read type has element size {} but dataset has element size {}",
1800 T::element_size(),
1801 element_size,
1802 )));
1803 }
1804
1805 let raw = {
1806 let mut inner = borrow_inner_mut(&self.file_inner);
1807 match &mut *inner {
1808 H5FileInner::Reader(reader) => reader.read_dataset_raw(name)?,
1809 _ => {
1810 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
1811 }
1812 }
1813 };
1814
1815 if raw.len() % T::element_size() != 0 {
1816 return Err(Hdf5Error::TypeMismatch(format!(
1817 "raw data size {} is not a multiple of element size {}",
1818 raw.len(),
1819 T::element_size(),
1820 )));
1821 }
1822
1823 let count = raw.len() / T::element_size();
1824 let mut result = Vec::<T>::with_capacity(count);
1825
1826 unsafe {
1833 std::ptr::copy_nonoverlapping(
1834 raw.as_ptr(),
1835 result.as_mut_ptr() as *mut u8,
1836 raw.len(),
1837 );
1838 result.set_len(count);
1839 }
1840
1841 Ok(result)
1842 }
1843 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
1844 "cannot read from a dataset in write mode".into(),
1845 )),
1846 }
1847 }
1848
1849 pub fn read_raw_bytes(&self) -> Result<Vec<u8>> {
1856 match &self.info {
1857 DatasetInfo::Reader { name, .. } => {
1858 let mut inner = borrow_inner_mut(&self.file_inner);
1859 match &mut *inner {
1860 H5FileInner::Reader(reader) => Ok(reader.read_dataset_raw(name)?),
1861 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
1862 }
1863 }
1864 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
1865 "cannot read from a dataset in write mode".into(),
1866 )),
1867 }
1868 }
1869
1870 pub fn read_raw_into<T: H5Type>(&self, out: &mut [T]) -> Result<()> {
1892 match &self.info {
1893 DatasetInfo::Reader {
1894 name, element_size, ..
1895 } => {
1896 if T::element_size() != *element_size {
1897 return Err(Hdf5Error::TypeMismatch(format!(
1898 "read type has element size {} but dataset has element size {}",
1899 T::element_size(),
1900 element_size,
1901 )));
1902 }
1903 let byte_len = out.len() * T::element_size();
1909 let bytes = unsafe {
1910 std::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, byte_len)
1911 };
1912 let mut inner = borrow_inner_mut(&self.file_inner);
1913 match &mut *inner {
1914 H5FileInner::Reader(reader) => Ok(reader.read_dataset_raw_into(name, bytes)?),
1915 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
1916 }
1917 }
1918 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
1919 "cannot read from a dataset in write mode".into(),
1920 )),
1921 }
1922 }
1923
1924 pub fn read_slice_into<T: H5Type>(
1943 &self,
1944 out: &mut [T],
1945 starts: &[usize],
1946 counts: &[usize],
1947 ) -> Result<()> {
1948 match &self.info {
1949 DatasetInfo::Reader {
1950 name, element_size, ..
1951 } => {
1952 if T::element_size() != *element_size {
1953 return Err(Hdf5Error::TypeMismatch(format!(
1954 "read type has element size {} but dataset has element size {}",
1955 T::element_size(),
1956 element_size,
1957 )));
1958 }
1959 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
1960 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
1961 let byte_len = out.len() * T::element_size();
1964 let bytes = unsafe {
1965 std::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, byte_len)
1966 };
1967 let mut inner = borrow_inner_mut(&self.file_inner);
1968 match &mut *inner {
1969 H5FileInner::Reader(reader) => {
1970 Ok(reader.read_slice_into(name, &starts_u64, &counts_u64, bytes)?)
1971 }
1972 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
1973 }
1974 }
1975 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
1976 "cannot read from a dataset in write mode".into(),
1977 )),
1978 }
1979 }
1980}
1981
1982#[cfg(test)]
1983mod tests {
1984 use crate::H5File;
1985 use std::path::PathBuf;
1986
1987 fn temp_path(name: &str) -> PathBuf {
1988 use std::sync::atomic::{AtomicU64, Ordering};
1992 static COUNTER: AtomicU64 = AtomicU64::new(0);
1993 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
1994 std::env::temp_dir().join(format!(
1995 "hdf5_dataset_test_{}_{}_{}.h5",
1996 name,
1997 std::process::id(),
1998 n
1999 ))
2000 }
2001
2002 #[test]
2003 fn runtime_compound_via_datatype_override_and_raw_bytes() {
2004 use crate::format::messages::datatype::DatatypeMessage;
2005 use crate::types::{CompoundType, H5Type};
2006
2007 let path = temp_path("compound_raw");
2008 let ct = CompoundType {
2012 members: vec![
2013 ("id".to_string(), i32::hdf5_type(), 0),
2014 ("val".to_string(), f64::hdf5_type(), 4),
2015 ],
2016 total_size: 12,
2017 };
2018 let recs: [(i32, f64); 3] = [(1, 2.5), (2, 3.5), (3, -4.0)];
2019 let mut bytes = Vec::new();
2020 for (id, val) in recs {
2021 bytes.extend_from_slice(&id.to_le_bytes());
2022 bytes.extend_from_slice(&val.to_le_bytes());
2023 }
2024
2025 {
2026 let file = H5File::create(&path).unwrap();
2027 let ds = file
2028 .new_dataset::<u8>()
2029 .datatype(ct.to_datatype())
2030 .shape([recs.len()])
2031 .create("records")
2032 .unwrap();
2033 ds.write_raw_bytes(&bytes).unwrap();
2034 file.close().unwrap();
2035 }
2036 {
2037 let file = H5File::open(&path).unwrap();
2038 let ds = file.dataset("records").unwrap();
2039 match ds.datatype().unwrap() {
2042 DatatypeMessage::Compound { size, members } => {
2043 assert_eq!(size, 12);
2044 assert_eq!(members.len(), 2);
2045 assert_eq!(members[0].name, "id");
2046 assert_eq!(members[0].offset, 0);
2047 assert_eq!(members[1].name, "val");
2048 assert_eq!(members[1].offset, 4);
2049 }
2050 other => panic!("expected compound datatype, got {other:?}"),
2051 }
2052 assert_eq!(ds.read_raw_bytes().unwrap(), bytes);
2053 }
2054 std::fs::remove_file(&path).ok();
2055 }
2056
2057 #[test]
2058 fn builder_requires_shape() {
2059 let path = temp_path("no_shape");
2060 let file = H5File::create(&path).unwrap();
2061 let result = file.new_dataset::<u8>().create("data");
2062 assert!(result.is_err());
2063 std::fs::remove_file(&path).ok();
2064 }
2065
2066 #[test]
2067 fn write_raw_size_mismatch() {
2068 let path = temp_path("size_mismatch");
2069 let file = H5File::create(&path).unwrap();
2070 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
2071 let result = ds.write_raw(&[1u8, 2, 3]);
2073 assert!(result.is_err());
2074 std::fs::remove_file(&path).ok();
2075 }
2076
2077 #[cfg(feature = "deflate")]
2081 #[test]
2082 fn filter_without_chunk_autochunks_and_roundtrips() {
2083 let path = temp_path("autochunk_filter");
2084 let data: Vec<i32> = (0..8).collect();
2085 {
2086 let file = H5File::create(&path).unwrap();
2087 let ds = file
2088 .new_dataset::<i32>()
2089 .deflate(6)
2090 .shape([8])
2091 .create("seq")
2092 .unwrap();
2093 ds.write_raw(&data).unwrap();
2094 file.close().unwrap();
2095 }
2096 {
2097 let file = H5File::open(&path).unwrap();
2098 let ds = file.dataset("seq").unwrap();
2099 assert!(
2101 ds.is_chunked(),
2102 "auto-chunk did not produce chunked storage"
2103 );
2104 assert_eq!(ds.chunk_dims(), Some(vec![8]));
2105 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
2106 }
2107 std::fs::remove_file(&path).ok();
2108 }
2109
2110 #[cfg(feature = "deflate")]
2114 #[test]
2115 fn write_raw_multichunk_edge_roundtrips() {
2116 let path = temp_path("multichunk_edge");
2117 let data: Vec<i32> = (0..35).collect(); {
2119 let file = H5File::create(&path).unwrap();
2120 let ds = file
2121 .new_dataset::<i32>()
2122 .shape([7, 5])
2123 .chunk(&[3, 2])
2124 .deflate(4)
2125 .create("grid")
2126 .unwrap();
2127 ds.write_raw(&data).unwrap();
2128 file.close().unwrap();
2129 }
2130 {
2131 let file = H5File::open(&path).unwrap();
2132 let ds = file.dataset("grid").unwrap();
2133 assert_eq!(ds.shape(), vec![7, 5]);
2134 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
2135 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
2136 }
2137 std::fs::remove_file(&path).ok();
2138 }
2139
2140 #[test]
2143 fn write_raw_unfiltered_chunked_roundtrips() {
2144 let path = temp_path("chunked_unfiltered");
2145 let data: Vec<f64> = (0..12).map(|i| i as f64 * 1.5).collect(); {
2147 let file = H5File::create(&path).unwrap();
2148 let ds = file
2149 .new_dataset::<f64>()
2150 .shape([4, 3])
2151 .chunk(&[2, 2])
2152 .create("m")
2153 .unwrap();
2154 ds.write_raw(&data).unwrap();
2155 file.close().unwrap();
2156 }
2157 {
2158 let file = H5File::open(&path).unwrap();
2159 let ds = file.dataset("m").unwrap();
2160 assert_eq!(ds.chunk_dims(), Some(vec![2, 2]));
2161 assert_eq!(ds.read_raw::<f64>().unwrap(), data);
2162 }
2163 std::fs::remove_file(&path).ok();
2164 }
2165
2166 #[test]
2167 fn roundtrip_u8_1d() {
2168 let path = temp_path("rt_u8_1d");
2169 let data: Vec<u8> = (0..10).collect();
2170
2171 {
2172 let file = H5File::create(&path).unwrap();
2173 let ds = file.new_dataset::<u8>().shape([10]).create("seq").unwrap();
2174 ds.write_raw(&data).unwrap();
2175 file.close().unwrap();
2176 }
2177
2178 {
2179 let file = H5File::open(&path).unwrap();
2180 let ds = file.dataset("seq").unwrap();
2181 assert_eq!(ds.shape(), vec![10]);
2182 let readback = ds.read_raw::<u8>().unwrap();
2183 assert_eq!(readback, data);
2184 }
2185
2186 std::fs::remove_file(&path).ok();
2187 }
2188
2189 #[test]
2190 fn roundtrip_i32_2d() {
2191 let path = temp_path("rt_i32_2d");
2192 let data: Vec<i32> = vec![-1, 0, 1, 2, 3, 4];
2193
2194 {
2195 let file = H5File::create(&path).unwrap();
2196 let ds = file
2197 .new_dataset::<i32>()
2198 .shape([2, 3])
2199 .create("matrix")
2200 .unwrap();
2201 ds.write_raw(&data).unwrap();
2202 file.close().unwrap();
2203 }
2204
2205 {
2206 let file = H5File::open(&path).unwrap();
2207 let ds = file.dataset("matrix").unwrap();
2208 assert_eq!(ds.shape(), vec![2, 3]);
2209 let readback = ds.read_raw::<i32>().unwrap();
2210 assert_eq!(readback, data);
2211 }
2212
2213 std::fs::remove_file(&path).ok();
2214 }
2215
2216 #[test]
2217 fn roundtrip_f64_3d() {
2218 let path = temp_path("rt_f64_3d");
2219 let data: Vec<f64> = (0..24).map(|i| i as f64 * 0.5).collect();
2220
2221 {
2222 let file = H5File::create(&path).unwrap();
2223 let ds = file
2224 .new_dataset::<f64>()
2225 .shape([2, 3, 4])
2226 .create("cube")
2227 .unwrap();
2228 ds.write_raw(&data).unwrap();
2229 file.close().unwrap();
2230 }
2231
2232 {
2233 let file = H5File::open(&path).unwrap();
2234 let ds = file.dataset("cube").unwrap();
2235 assert_eq!(ds.shape(), vec![2, 3, 4]);
2236 let readback = ds.read_raw::<f64>().unwrap();
2237 assert_eq!(readback, data);
2238 }
2239
2240 std::fs::remove_file(&path).ok();
2241 }
2242
2243 #[test]
2244 fn cannot_read_in_write_mode() {
2245 let path = temp_path("no_read_write");
2246 let file = H5File::create(&path).unwrap();
2247 let ds = file.new_dataset::<u8>().shape([4]).create("x").unwrap();
2248 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
2249 let result = ds.read_raw::<u8>();
2250 assert!(result.is_err());
2251 std::fs::remove_file(&path).ok();
2252 }
2253
2254 #[test]
2255 fn cannot_write_in_read_mode() {
2256 let path = temp_path("no_write_read");
2257
2258 {
2259 let file = H5File::create(&path).unwrap();
2260 let ds = file.new_dataset::<u8>().shape([4]).create("x").unwrap();
2261 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
2262 file.close().unwrap();
2263 }
2264
2265 {
2266 let file = H5File::open(&path).unwrap();
2267 let ds = file.dataset("x").unwrap();
2268 let result = ds.write_raw(&[5u8, 6, 7, 8]);
2269 assert!(result.is_err());
2270 }
2271
2272 std::fs::remove_file(&path).ok();
2273 }
2274
2275 #[test]
2276 fn numeric_attr_roundtrip() {
2277 let path = temp_path("num_attr");
2278 {
2279 let file = H5File::create(&path).unwrap();
2280 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
2281 ds.write_raw(&[1.0f32; 4]).unwrap();
2282
2283 let a1 = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
2284 a1.write_numeric(&1.2345f64).unwrap();
2285
2286 let a2 = ds.new_attr::<i32>().shape(()).create("count").unwrap();
2287 a2.write_numeric(&42i32).unwrap();
2288
2289 file.close().unwrap();
2290 }
2291 {
2292 let file = H5File::open(&path).unwrap();
2293 let ds = file.dataset("data").unwrap();
2294
2295 let scale = ds.attr("scale").unwrap();
2296 let val: f64 = scale.read_numeric().unwrap();
2297 assert!((val - 1.2345).abs() < 1e-10);
2298
2299 let count = ds.attr("count").unwrap();
2300 let val: i32 = count.read_numeric().unwrap();
2301 assert_eq!(val, 42);
2302 }
2303 std::fs::remove_file(&path).ok();
2304 }
2305
2306 #[test]
2307 fn array_attr_roundtrip() {
2308 let path = temp_path("array_attr");
2309 let offsets = [10i32, -20, 30];
2310 {
2311 let file = H5File::create(&path).unwrap();
2312 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
2313 ds.write_raw(&[1.0f32; 4]).unwrap();
2314
2315 let a = ds
2317 .new_attr::<i32>()
2318 .shape([3])
2319 .create("dim_offset")
2320 .unwrap();
2321 a.write_array(&offsets).unwrap();
2322
2323 let bad = ds.new_attr::<i32>().shape([3]).create("bad").unwrap();
2325 assert!(bad.write_array(&[1i32, 2]).is_err());
2326
2327 file.close().unwrap();
2328 }
2329 {
2330 let file = H5File::open(&path).unwrap();
2331 let ds = file.dataset("data").unwrap();
2332 let a = ds.attr("dim_offset").unwrap();
2333 let raw = a.read_raw().unwrap();
2334 assert_eq!(raw.len(), 3 * 4);
2335 let got: Vec<i32> = raw
2336 .chunks_exact(4)
2337 .map(|b| i32::from_le_bytes([b[0], b[1], b[2], b[3]]))
2338 .collect();
2339 assert_eq!(got, offsets);
2340 }
2341 std::fs::remove_file(&path).ok();
2342 }
2343
2344 #[test]
2345 fn attr_datatype_exposes_class_and_sign() {
2346 use crate::format::messages::datatype::DatatypeMessage;
2350
2351 let path = temp_path("attr_datatype");
2352 {
2353 let file = H5File::create(&path).unwrap();
2354 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
2355 ds.new_attr::<f64>()
2356 .shape(())
2357 .create("scale")
2358 .unwrap()
2359 .write_numeric(&1.5f64)
2360 .unwrap();
2361 ds.new_attr::<i32>()
2362 .shape(())
2363 .create("count")
2364 .unwrap()
2365 .write_numeric(&7i32)
2366 .unwrap();
2367 file.close().unwrap();
2368 }
2369 {
2370 let file = H5File::open(&path).unwrap();
2371 let ds = file.dataset("data").unwrap();
2372
2373 match ds.attr("scale").unwrap().datatype().unwrap() {
2374 DatatypeMessage::FloatingPoint { size, .. } => assert_eq!(size, 8),
2375 other => panic!("expected FloatingPoint for f64 attr, got {other:?}"),
2376 }
2377
2378 match ds.attr("count").unwrap().datatype().unwrap() {
2379 DatatypeMessage::FixedPoint { size, signed, .. } => {
2380 assert_eq!(size, 4);
2381 assert!(signed, "i32 attr must be signed");
2382 }
2383 other => panic!("expected FixedPoint for i32 attr, got {other:?}"),
2384 }
2385 }
2386 std::fs::remove_file(&path).ok();
2387 }
2388
2389 #[test]
2390 fn attr_datatype_in_write_mode_errors() {
2391 let path = temp_path("attr_datatype_write_mode");
2392 let file = H5File::create(&path).unwrap();
2393 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
2394 let attr = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
2395 assert!(attr.datatype().is_err());
2396 std::fs::remove_file(&path).ok();
2397 }
2398
2399 #[test]
2400 fn cannot_create_dataset_in_read_mode() {
2401 let path = temp_path("no_create_read");
2402
2403 {
2404 let _file = H5File::create(&path).unwrap();
2405 }
2406
2407 {
2408 let file = H5File::open(&path).unwrap();
2409 let result = file.new_dataset::<u8>().shape([4]).create("x");
2410 assert!(result.is_err());
2411 }
2412
2413 std::fs::remove_file(&path).ok();
2414 }
2415
2416 #[test]
2417 fn shape_accessor() {
2418 let path = temp_path("shape_acc");
2419
2420 let file = H5File::create(&path).unwrap();
2421 let ds = file
2422 .new_dataset::<f32>()
2423 .shape([5, 10, 3])
2424 .create("tensor")
2425 .unwrap();
2426 assert_eq!(ds.shape(), vec![5, 10, 3]);
2427
2428 std::fs::remove_file(&path).ok();
2429 }
2430
2431 #[test]
2432 fn slice_roundtrip_2d() {
2433 let path = temp_path("slice_2d");
2434
2435 let data: Vec<i32> = (0..20).collect();
2437 {
2438 let file = H5File::create(&path).unwrap();
2439 let ds = file
2440 .new_dataset::<i32>()
2441 .shape([4, 5])
2442 .create("mat")
2443 .unwrap();
2444 ds.write_raw(&data).unwrap();
2445 file.close().unwrap();
2446 }
2447 {
2448 let file = H5File::open(&path).unwrap();
2449 let ds = file.dataset("mat").unwrap();
2450 let slice = ds.read_slice::<i32>(&[1, 2], &[2, 2]).unwrap();
2452 assert_eq!(slice, vec![7, 8, 12, 13]);
2455 }
2456
2457 std::fs::remove_file(&path).ok();
2458 }
2459
2460 fn assert_into_matches<T>(ds: &super::H5Dataset, starts: &[usize], counts: &[usize])
2465 where
2466 T: crate::types::H5Type + Copy + std::fmt::Debug + PartialEq + Default,
2467 {
2468 let n: usize = ds.shape().iter().product();
2469 let want_full = ds.read_raw::<T>().unwrap();
2470 let mut got_full = vec![T::default(); n];
2471 ds.read_raw_into::<T>(&mut got_full).unwrap();
2472 assert_eq!(got_full, want_full, "read_raw_into != read_raw");
2473
2474 let want_slice = ds.read_slice::<T>(starts, counts).unwrap();
2475 let sn: usize = counts.iter().product();
2476 let mut got_slice = vec![T::default(); sn];
2477 ds.read_slice_into::<T>(&mut got_slice, starts, counts)
2478 .unwrap();
2479 assert_eq!(got_slice, want_slice, "read_slice_into != read_slice");
2480 }
2481
2482 #[test]
2483 fn read_into_matches_vec_contiguous() {
2484 let path = temp_path("into_contig");
2485 let data: Vec<i32> = (0..20).collect(); {
2487 let file = H5File::create(&path).unwrap();
2488 let ds = file
2489 .new_dataset::<i32>()
2490 .shape([4, 5])
2491 .create("mat")
2492 .unwrap();
2493 ds.write_raw(&data).unwrap();
2494 file.close().unwrap();
2495 }
2496 {
2497 let file = H5File::open(&path).unwrap();
2498 let ds = file.dataset("mat").unwrap();
2499 assert_eq!(ds.chunk_dims(), None);
2500 assert_into_matches::<i32>(&ds, &[1, 2], &[2, 2]);
2501 }
2502 std::fs::remove_file(&path).ok();
2503 }
2504
2505 #[test]
2506 fn read_into_matches_vec_chunked_unfiltered() {
2507 let path = temp_path("into_chunk");
2508 let data: Vec<f64> = (0..35).map(|i| i as f64 * 1.5).collect(); {
2510 let file = H5File::create(&path).unwrap();
2511 let ds = file
2512 .new_dataset::<f64>()
2513 .shape([7, 5])
2514 .chunk(&[3, 2]) .create("grid")
2516 .unwrap();
2517 ds.write_raw(&data).unwrap();
2518 file.close().unwrap();
2519 }
2520 {
2521 let file = H5File::open(&path).unwrap();
2522 let ds = file.dataset("grid").unwrap();
2523 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
2524 assert_into_matches::<f64>(&ds, &[2, 1], &[3, 3]);
2526 }
2527 std::fs::remove_file(&path).ok();
2528 }
2529
2530 #[test]
2531 fn read_into_matches_vec_single_chunk() {
2532 let path = temp_path("into_single_chunk");
2533 let data: Vec<i32> = (0..12).collect(); {
2535 let file = H5File::create(&path).unwrap();
2536 let ds = file
2537 .new_dataset::<i32>()
2538 .shape([3, 4])
2539 .chunk(&[3, 4]) .create("g")
2541 .unwrap();
2542 ds.write_raw(&data).unwrap();
2543 file.close().unwrap();
2544 }
2545 {
2546 let file = H5File::open(&path).unwrap();
2547 let ds = file.dataset("g").unwrap();
2548 assert_eq!(ds.chunk_dims(), Some(vec![3, 4]));
2549 assert_into_matches::<i32>(&ds, &[1, 1], &[2, 2]);
2550 }
2551 std::fs::remove_file(&path).ok();
2552 }
2553
2554 #[cfg(feature = "deflate")]
2555 #[test]
2556 fn read_into_matches_vec_chunked_deflate() {
2557 let path = temp_path("into_chunk_deflate");
2558 let data: Vec<i32> = (0..35).collect(); {
2560 let file = H5File::create(&path).unwrap();
2561 let ds = file
2562 .new_dataset::<i32>()
2563 .shape([7, 5])
2564 .chunk(&[3, 2])
2565 .deflate(4)
2566 .create("grid")
2567 .unwrap();
2568 ds.write_raw(&data).unwrap();
2569 file.close().unwrap();
2570 }
2571 {
2572 let file = H5File::open(&path).unwrap();
2573 let ds = file.dataset("grid").unwrap();
2574 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
2575 assert_into_matches::<i32>(&ds, &[2, 1], &[3, 3]);
2576 }
2577 std::fs::remove_file(&path).ok();
2578 }
2579
2580 #[test]
2581 fn read_into_wrong_buffer_size_rejected() {
2582 let path = temp_path("into_badlen");
2583 let data: Vec<i32> = (0..20).collect(); {
2585 let file = H5File::create(&path).unwrap();
2586 let ds = file
2587 .new_dataset::<i32>()
2588 .shape([4, 5])
2589 .create("mat")
2590 .unwrap();
2591 ds.write_raw(&data).unwrap();
2592 file.close().unwrap();
2593 }
2594 {
2595 let file = H5File::open(&path).unwrap();
2596 let ds = file.dataset("mat").unwrap();
2597
2598 let mut small = vec![0i32; 19];
2600 assert!(ds.read_raw_into::<i32>(&mut small).is_err());
2601 let mut large = vec![0i32; 21];
2602 assert!(ds.read_raw_into::<i32>(&mut large).is_err());
2603
2604 let mut bad_slice = vec![0i32; 3];
2606 assert!(ds
2607 .read_slice_into::<i32>(&mut bad_slice, &[1, 2], &[2, 2])
2608 .is_err());
2609 let mut ok_slice = vec![0i32; 4];
2611 assert!(ds
2612 .read_slice_into::<i32>(&mut ok_slice, &[1, 2], &[2, 2])
2613 .is_ok());
2614 }
2615 std::fs::remove_file(&path).ok();
2616 }
2617
2618 #[test]
2619 fn read_into_wrong_element_size_rejected() {
2620 let path = temp_path("into_badtype");
2621 let data: Vec<i32> = (0..20).collect(); {
2623 let file = H5File::create(&path).unwrap();
2624 let ds = file
2625 .new_dataset::<i32>()
2626 .shape([4, 5])
2627 .create("mat")
2628 .unwrap();
2629 ds.write_raw(&data).unwrap();
2630 file.close().unwrap();
2631 }
2632 {
2633 let file = H5File::open(&path).unwrap();
2634 let ds = file.dataset("mat").unwrap();
2635 let mut as_u8 = vec![0u8; 20];
2638 assert!(matches!(
2639 ds.read_raw_into::<u8>(&mut as_u8),
2640 Err(crate::Hdf5Error::TypeMismatch(_))
2641 ));
2642 let mut as_i64 = vec![0i64; 20];
2643 assert!(matches!(
2644 ds.read_slice_into::<i64>(&mut as_i64, &[0, 0], &[4, 5]),
2645 Err(crate::Hdf5Error::TypeMismatch(_))
2646 ));
2647 }
2648 std::fs::remove_file(&path).ok();
2649 }
2650
2651 #[test]
2652 fn write_slice_2d() {
2653 let path = temp_path("write_slice_2d");
2654
2655 {
2656 let file = H5File::create(&path).unwrap();
2657 let ds = file
2658 .new_dataset::<f32>()
2659 .shape([3, 4])
2660 .create("data")
2661 .unwrap();
2662 ds.write_raw(&[0.0f32; 12]).unwrap();
2663 ds.write_slice(&[1, 1], &[2, 2], &[10.0f32, 20.0, 30.0, 40.0])
2665 .unwrap();
2666 file.close().unwrap();
2667 }
2668 {
2669 let file = H5File::open(&path).unwrap();
2670 let ds = file.dataset("data").unwrap();
2671 let full = ds.read_raw::<f32>().unwrap();
2672 assert_eq!(
2676 full,
2677 vec![0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 20.0, 0.0, 0.0, 30.0, 40.0, 0.0,]
2678 );
2679 }
2680
2681 std::fs::remove_file(&path).ok();
2682 }
2683
2684 #[test]
2685 fn write_slice_out_of_bounds_rejected() {
2686 let path = temp_path("write_slice_oob");
2687 let file = H5File::create(&path).unwrap();
2688 let ds = file.new_dataset::<i32>().shape([4]).create("d").unwrap();
2689 ds.write_raw(&[0i32; 4]).unwrap();
2690 assert!(ds.write_slice(&[2], &[6], &[9i32; 6]).is_err());
2692 assert!(ds.write_slice(&[1], &[2], &[7i32, 8]).is_ok());
2694 std::fs::remove_file(&path).ok();
2695 }
2696
2697 #[test]
2698 fn duplicate_dataset_name_rejected() {
2699 let path = temp_path("dup_name");
2700 let file = H5File::create(&path).unwrap();
2701 let _ = file.new_dataset::<i32>().shape([2]).create("d").unwrap();
2702 assert!(file.new_dataset::<i32>().shape([2]).create("d").is_err());
2703 std::fs::remove_file(&path).ok();
2704 }
2705
2706 #[test]
2707 fn extend_cannot_shrink() {
2708 let path = temp_path("extend_shrink");
2709 let file = H5File::create(&path).unwrap();
2710 let ds = file
2711 .new_dataset::<i32>()
2712 .shape([0])
2713 .chunk(&[2])
2714 .max_shape(&[None])
2715 .create("d")
2716 .unwrap();
2717 ds.append(&[1i32, 2, 3, 4]).unwrap();
2718 assert!(ds.extend(&[2]).is_err());
2720 assert!(ds.extend(&[6]).is_ok());
2722 std::fs::remove_file(&path).ok();
2723 }
2724
2725 #[test]
2726 fn attr_read_roundtrip() {
2727 use crate::types::VarLenUnicode;
2728 let path = temp_path("attr_read");
2729
2730 {
2731 let file = H5File::create(&path).unwrap();
2732 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
2733 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
2734 let a1 = ds
2735 .new_attr::<VarLenUnicode>()
2736 .shape(())
2737 .create("units")
2738 .unwrap();
2739 a1.write_string("meters").unwrap();
2740 let a2 = ds
2741 .new_attr::<VarLenUnicode>()
2742 .shape(())
2743 .create("desc")
2744 .unwrap();
2745 a2.write_string("test data").unwrap();
2746 file.close().unwrap();
2747 }
2748 {
2749 let file = H5File::open(&path).unwrap();
2750 let ds = file.dataset("data").unwrap();
2751
2752 let names = ds.attr_names().unwrap();
2753 assert!(names.contains(&"units".to_string()));
2754 assert!(names.contains(&"desc".to_string()));
2755
2756 let units = ds.attr("units").unwrap();
2757 assert_eq!(units.read_string().unwrap(), "meters");
2758
2759 let desc = ds.attr("desc").unwrap();
2760 assert_eq!(desc.read_string().unwrap(), "test data");
2761 }
2762
2763 std::fs::remove_file(&path).ok();
2764 }
2765
2766 #[test]
2767 fn type_mismatch_element_size() {
2768 let path = temp_path("type_mismatch");
2769
2770 {
2771 let file = H5File::create(&path).unwrap();
2772 let ds = file.new_dataset::<f64>().shape([4]).create("data").unwrap();
2773 ds.write_raw(&[1.0f64, 2.0, 3.0, 4.0]).unwrap();
2774 file.close().unwrap();
2775 }
2776
2777 {
2778 let file = H5File::open(&path).unwrap();
2779 let ds = file.dataset("data").unwrap();
2780 let result = ds.read_raw::<u8>();
2782 assert!(result.is_err());
2783 }
2784
2785 std::fs::remove_file(&path).ok();
2786 }
2787
2788 #[test]
2789 fn dataset_survives_file_move() {
2790 let path = temp_path("ds_survives");
2791
2792 let ds = {
2793 let file = H5File::create(&path).unwrap();
2794 file.new_dataset::<u8>().shape([4]).create("x").unwrap()
2795 };
2796 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
2798 std::fs::remove_file(&path).ok();
2801 }
2802
2803 #[test]
2804 fn new_attr_scalar_string() {
2805 use crate::types::VarLenUnicode;
2806
2807 let path = temp_path("attr_scalar_string");
2808 {
2809 let file = H5File::create(&path).unwrap();
2810 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
2811 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
2812
2813 let attr = ds
2814 .new_attr::<VarLenUnicode>()
2815 .shape(())
2816 .create("name")
2817 .unwrap();
2818 attr.write_scalar(&VarLenUnicode("test_value".to_string()))
2819 .unwrap();
2820
2821 file.close().unwrap();
2822 }
2823
2824 {
2826 use crate::format::messages::datatype::DatatypeMessage;
2827 let file = H5File::open(&path).unwrap();
2828 let ds = file.dataset("data").unwrap();
2829 assert_eq!(ds.shape(), vec![4]);
2830 let readback = ds.read_raw::<u8>().unwrap();
2831 assert_eq!(readback, vec![1u8, 2, 3, 4]);
2832
2833 let attr = ds.attr("name").unwrap();
2836 assert!(
2837 matches!(
2838 attr.datatype().unwrap(),
2839 DatatypeMessage::VarLenString { .. }
2840 ),
2841 "string attribute should have a variable-length string datatype"
2842 );
2843 assert_eq!(attr.read_string().unwrap(), "test_value");
2844 }
2845
2846 std::fs::remove_file(&path).ok();
2847 }
2848
2849 #[test]
2850 fn all_numeric_types_roundtrip() {
2851 let path = temp_path("all_types");
2852
2853 {
2854 let file = H5File::create(&path).unwrap();
2855
2856 let ds = file.new_dataset::<u8>().shape([2]).create("u8").unwrap();
2857 ds.write_raw(&[1u8, 2]).unwrap();
2858
2859 let ds = file.new_dataset::<i8>().shape([2]).create("i8").unwrap();
2860 ds.write_raw(&[-1i8, 1]).unwrap();
2861
2862 let ds = file.new_dataset::<u16>().shape([2]).create("u16").unwrap();
2863 ds.write_raw(&[100u16, 200]).unwrap();
2864
2865 let ds = file.new_dataset::<i16>().shape([2]).create("i16").unwrap();
2866 ds.write_raw(&[-100i16, 100]).unwrap();
2867
2868 let ds = file.new_dataset::<u32>().shape([2]).create("u32").unwrap();
2869 ds.write_raw(&[1000u32, 2000]).unwrap();
2870
2871 let ds = file.new_dataset::<i32>().shape([2]).create("i32").unwrap();
2872 ds.write_raw(&[-1000i32, 1000]).unwrap();
2873
2874 let ds = file.new_dataset::<u64>().shape([2]).create("u64").unwrap();
2875 ds.write_raw(&[10000u64, 20000]).unwrap();
2876
2877 let ds = file.new_dataset::<i64>().shape([2]).create("i64").unwrap();
2878 ds.write_raw(&[-10000i64, 10000]).unwrap();
2879
2880 let ds = file.new_dataset::<f32>().shape([2]).create("f32").unwrap();
2881 ds.write_raw(&[1.5f32, 2.5]).unwrap();
2882
2883 let ds = file.new_dataset::<f64>().shape([2]).create("f64").unwrap();
2884 ds.write_raw(&[1.23456f64, 7.89012]).unwrap();
2885
2886 file.close().unwrap();
2887 }
2888
2889 {
2890 let file = H5File::open(&path).unwrap();
2891
2892 assert_eq!(
2893 file.dataset("u8").unwrap().read_raw::<u8>().unwrap(),
2894 vec![1u8, 2]
2895 );
2896 assert_eq!(
2897 file.dataset("i8").unwrap().read_raw::<i8>().unwrap(),
2898 vec![-1i8, 1]
2899 );
2900 assert_eq!(
2901 file.dataset("u16").unwrap().read_raw::<u16>().unwrap(),
2902 vec![100u16, 200]
2903 );
2904 assert_eq!(
2905 file.dataset("i16").unwrap().read_raw::<i16>().unwrap(),
2906 vec![-100i16, 100]
2907 );
2908 assert_eq!(
2909 file.dataset("u32").unwrap().read_raw::<u32>().unwrap(),
2910 vec![1000u32, 2000]
2911 );
2912 assert_eq!(
2913 file.dataset("i32").unwrap().read_raw::<i32>().unwrap(),
2914 vec![-1000i32, 1000]
2915 );
2916 assert_eq!(
2917 file.dataset("u64").unwrap().read_raw::<u64>().unwrap(),
2918 vec![10000u64, 20000]
2919 );
2920 assert_eq!(
2921 file.dataset("i64").unwrap().read_raw::<i64>().unwrap(),
2922 vec![-10000i64, 10000]
2923 );
2924 assert_eq!(
2925 file.dataset("f32").unwrap().read_raw::<f32>().unwrap(),
2926 vec![1.5f32, 2.5]
2927 );
2928 assert_eq!(
2929 file.dataset("f64").unwrap().read_raw::<f64>().unwrap(),
2930 vec![1.23456f64, 7.89012]
2931 );
2932 }
2933
2934 std::fs::remove_file(&path).ok();
2935 }
2936
2937 #[test]
2938 fn append_chunked_roundtrip() {
2939 let path = temp_path("append_chunked");
2940
2941 {
2942 let file = H5File::create(&path).unwrap();
2943 let ds = file
2944 .new_dataset::<f64>()
2945 .shape([0, 3])
2946 .chunk(&[1, 3])
2947 .max_shape(&[None, Some(3)])
2948 .create("data")
2949 .unwrap();
2950
2951 ds.append(&[1.0f64, 2.0, 3.0]).unwrap();
2953 ds.append(&[4.0f64, 5.0, 6.0, 7.0, 8.0, 9.0]).unwrap();
2955
2956 file.close().unwrap();
2957 }
2958
2959 {
2960 let file = H5File::open(&path).unwrap();
2961 let ds = file.dataset("data").unwrap();
2962 assert_eq!(ds.shape(), vec![3, 3]);
2963 let all = ds.read_raw::<f64>().unwrap();
2964 assert_eq!(all, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
2965 }
2966
2967 std::fs::remove_file(&path).ok();
2968 }
2969
2970 #[test]
2971 fn append_1d_chunked() {
2972 let path = temp_path("append_1d");
2973
2974 {
2975 let file = H5File::create(&path).unwrap();
2976 let ds = file
2977 .new_dataset::<i32>()
2978 .shape([0])
2979 .chunk(&[4])
2980 .max_shape(&[None])
2981 .create("values")
2982 .unwrap();
2983
2984 ds.append(&[10i32, 20, 30]).unwrap(); ds.append(&[40i32]).unwrap(); ds.append(&[50i32, 60, 70, 80]).unwrap(); file.close().unwrap();
2989 }
2990
2991 {
2992 let file = H5File::open(&path).unwrap();
2993 let ds = file.dataset("values").unwrap();
2994 assert_eq!(ds.shape(), vec![8]);
2995 let all = ds.read_raw::<i32>().unwrap();
2996 assert_eq!(all, vec![10, 20, 30, 40, 50, 60, 70, 80]);
2997 }
2998
2999 std::fs::remove_file(&path).ok();
3000 }
3001
3002 #[test]
3003 fn append_partial_chunk_flushed_on_close() {
3004 let path = temp_path("append_partial_close");
3005
3006 {
3007 let file = H5File::create(&path).unwrap();
3008 let ds = file
3009 .new_dataset::<f64>()
3010 .shape([0])
3011 .chunk(&[4])
3012 .max_shape(&[None])
3013 .create("vals")
3014 .unwrap();
3015
3016 ds.append(&[1.0f64, 2.0, 3.0, 4.0, 5.0]).unwrap();
3018 file.close().unwrap();
3019 }
3020
3021 {
3022 let file = H5File::open(&path).unwrap();
3023 let ds = file.dataset("vals").unwrap();
3024 assert_eq!(ds.shape(), vec![5]);
3025 let all = ds.read_raw::<f64>().unwrap();
3026 assert_eq!(all.len(), 5);
3029 assert_eq!(all, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
3030 }
3031
3032 std::fs::remove_file(&path).ok();
3033 }
3034
3035 #[cfg(feature = "deflate")]
3036 #[test]
3037 fn vlen_append_after_reopen_filtered() {
3038 let path = temp_path("vlen_reopen_filtered");
3042 {
3043 let file = H5File::create(&path).unwrap();
3044 file.create_appendable_vlen_dataset(
3045 "strs",
3046 4,
3047 Some(crate::format::messages::filter::FilterPipeline::deflate(6)),
3048 )
3049 .unwrap();
3050 file.append_vlen_strings("strs", &["alpha", "beta", "gamma"])
3051 .unwrap();
3052 file.close().unwrap();
3053 }
3054 {
3055 let file = H5File::open_rw(&path).unwrap();
3056 file.append_vlen_strings("strs", &["delta"]).unwrap();
3057 file.close().unwrap();
3058 }
3059 {
3060 let file = H5File::open(&path).unwrap();
3061 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
3062 assert_eq!(
3063 got.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
3064 vec!["alpha", "beta", "gamma", "delta"]
3065 );
3066 }
3067 std::fs::remove_file(&path).ok();
3068 }
3069
3070 #[test]
3071 fn vlen_append_after_reopen_data_block() {
3072 let path = temp_path("vlen_reopen_datablk");
3076 let labels: Vec<String> = (0..9).map(|i| format!("s{i}")).collect();
3077 {
3078 let file = H5File::create(&path).unwrap();
3079 file.create_appendable_vlen_dataset("strs", 2, None)
3080 .unwrap();
3081 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
3082 file.append_vlen_strings("strs", &refs).unwrap();
3083 file.close().unwrap();
3084 }
3085 {
3086 let file = H5File::open_rw(&path).unwrap();
3087 file.append_vlen_strings("strs", &["s9"]).unwrap();
3088 file.close().unwrap();
3089 }
3090 {
3091 let file = H5File::open(&path).unwrap();
3092 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
3093 let want: Vec<String> = (0..10).map(|i| format!("s{i}")).collect();
3094 assert_eq!(got, want);
3095 }
3096 std::fs::remove_file(&path).ok();
3097 }
3098
3099 #[test]
3100 fn vlen_append_after_reopen_super_block() {
3101 let path = temp_path("vlen_reopen_super");
3109 let labels: Vec<String> = (0..489).map(|i| format!("v{i}")).collect();
3112 {
3113 let file = H5File::create(&path).unwrap();
3114 file.create_appendable_vlen_dataset("strs", 2, None)
3115 .unwrap();
3116 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
3117 file.append_vlen_strings("strs", &refs).unwrap();
3118 file.close().unwrap();
3119 }
3120 {
3121 let file = H5File::open_rw(&path).unwrap();
3122 file.append_vlen_strings("strs", &["v489"]).unwrap();
3123 file.close().unwrap();
3124 }
3125 {
3126 let file = H5File::open(&path).unwrap();
3127 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
3128 let want: Vec<String> = (0..490).map(|i| format!("v{i}")).collect();
3129 assert_eq!(got, want);
3130 }
3131 std::fs::remove_file(&path).ok();
3132 }
3133
3134 #[cfg(feature = "deflate")]
3135 #[test]
3136 fn vlen_append_after_reopen_filtered_data_block() {
3137 let path = temp_path("vlen_reopen_filt_datablk");
3140 let labels: Vec<String> = (0..9).map(|i| format!("item{i:02}")).collect();
3141 {
3142 let file = H5File::create(&path).unwrap();
3143 file.create_appendable_vlen_dataset(
3144 "strs",
3145 2,
3146 Some(crate::format::messages::filter::FilterPipeline::deflate(6)),
3147 )
3148 .unwrap();
3149 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
3150 file.append_vlen_strings("strs", &refs).unwrap();
3151 file.close().unwrap();
3152 }
3153 {
3154 let file = H5File::open_rw(&path).unwrap();
3155 file.append_vlen_strings("strs", &["item09"]).unwrap();
3156 file.close().unwrap();
3157 }
3158 {
3159 let file = H5File::open(&path).unwrap();
3160 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
3161 let want: Vec<String> = (0..10).map(|i| format!("item{i:02}")).collect();
3162 assert_eq!(got, want);
3163 }
3164 std::fs::remove_file(&path).ok();
3165 }
3166
3167 #[test]
3168 fn group_nx_class_attribute_roundtrip() {
3169 let path = temp_path("group_nx_class");
3172 {
3173 let file = H5File::create(&path).unwrap();
3174 let entry = file.create_group("entry").unwrap();
3175 entry.set_attr_string("NX_class", "NXentry").unwrap();
3176 let det = entry.create_group("detector").unwrap();
3177 det.set_attr_string("NX_class", "NXdetector").unwrap();
3178 det.set_attr_numeric("frame_count", &7i32).unwrap();
3179 det.new_dataset::<f32>()
3180 .shape([4])
3181 .create("data")
3182 .unwrap()
3183 .write_raw(&[1.0f32; 4])
3184 .unwrap();
3185 file.close().unwrap();
3186 }
3187 {
3188 let file = H5File::open(&path).unwrap();
3189 let entry = file.root_group().group("entry").unwrap();
3190 assert_eq!(entry.attr_string("NX_class").unwrap(), "NXentry");
3191 let det = entry.group("detector").unwrap();
3192 assert_eq!(det.attr_string("NX_class").unwrap(), "NXdetector");
3193 let names = det.attr_names().unwrap();
3194 assert!(names.contains(&"NX_class".to_string()));
3195 assert!(names.contains(&"frame_count".to_string()));
3196 }
3197 std::fs::remove_file(&path).ok();
3198 }
3199
3200 #[test]
3201 fn ea_super_block_roundtrip() {
3202 let path = temp_path("ea_super_rt");
3205 {
3206 let file = H5File::create(&path).unwrap();
3207 let ds = file
3208 .new_dataset::<i32>()
3209 .shape([0])
3210 .chunk(&[1])
3211 .max_shape(&[None])
3212 .create("v")
3213 .unwrap();
3214 ds.append(&(0..2000).collect::<Vec<i32>>()).unwrap();
3215 file.close().unwrap();
3216 }
3217 {
3218 let file = H5File::open(&path).unwrap();
3219 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
3220 assert_eq!(v.len(), 2000);
3221 assert!(v.iter().enumerate().all(|(i, &x)| x == i as i32));
3222 }
3223 std::fs::remove_file(&path).ok();
3224 }
3225
3226 #[cfg(feature = "deflate")]
3227 #[test]
3228 fn ea_filtered_super_block_roundtrip() {
3229 let path = temp_path("ea_filt_super");
3231 {
3232 let file = H5File::create(&path).unwrap();
3233 let ds = file
3234 .new_dataset::<i32>()
3235 .shape([0])
3236 .chunk(&[1])
3237 .max_shape(&[None])
3238 .deflate(4)
3239 .create("v")
3240 .unwrap();
3241 ds.append(&(0..600).collect::<Vec<i32>>()).unwrap();
3242 file.close().unwrap();
3243 }
3244 {
3245 let file = H5File::open(&path).unwrap();
3246 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
3247 assert_eq!(v, (0..600).collect::<Vec<i32>>());
3248 }
3249 std::fs::remove_file(&path).ok();
3250 }
3251
3252 #[test]
3253 fn ea_super_block_open_append() {
3254 let path = temp_path("ea_super_append");
3256 {
3257 let file = H5File::create(&path).unwrap();
3258 let ds = file
3259 .new_dataset::<i32>()
3260 .shape([0])
3261 .chunk(&[1])
3262 .max_shape(&[None])
3263 .create("v")
3264 .unwrap();
3265 ds.append(&(0..300).collect::<Vec<i32>>()).unwrap();
3266 file.close().unwrap();
3267 }
3268 {
3269 let w = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
3270 let idx = w.dataset_index("v").unwrap();
3271 for c in 300..900u64 {
3272 w.write_chunk(idx, c, &(c as i32).to_le_bytes()).unwrap();
3273 }
3274 w.extend_dataset(idx, &[900]).unwrap();
3275 w.close().unwrap();
3276 }
3277 {
3278 let file = H5File::open(&path).unwrap();
3279 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
3280 assert_eq!(v.len(), 900);
3281 assert!(v.iter().enumerate().all(|(i, &x)| x == i as i32));
3282 }
3283 std::fs::remove_file(&path).ok();
3284 }
3285
3286 #[test]
3287 fn btree_v2_multi_unlimited_roundtrip() {
3288 let path = temp_path("bt2_multi");
3291 {
3292 let file = H5File::create(&path).unwrap();
3293 let ds = file
3294 .new_dataset::<i32>()
3295 .shape([0, 0])
3296 .chunk(&[2, 2])
3297 .max_shape(&[None, None])
3298 .create("grid")
3299 .unwrap();
3300 assert!(ds.is_chunked());
3301 for cr in 0..2usize {
3303 for cc in 0..2usize {
3304 let mut bytes = Vec::new();
3305 for i in 0..2usize {
3306 for j in 0..2usize {
3307 let v = ((cr * 2 + i) * 4 + (cc * 2 + j)) as i32;
3308 bytes.extend_from_slice(&v.to_le_bytes());
3309 }
3310 }
3311 ds.write_chunk_at(&[cr, cc], &bytes).unwrap();
3312 }
3313 }
3314 file.close().unwrap();
3315 }
3316 {
3317 let file = H5File::open(&path).unwrap();
3318 let ds = file.dataset("grid").unwrap();
3319 assert_eq!(ds.shape(), vec![4, 4]);
3320 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..16).collect::<Vec<i32>>());
3321 }
3322 std::fs::remove_file(&path).ok();
3323 }
3324
3325 #[test]
3326 fn subframe_chunking_roundtrip() {
3327 let path = temp_path("subframe");
3331 {
3332 let file = H5File::create(&path).unwrap();
3333 let ds = file
3334 .new_dataset::<i32>()
3335 .shape([0, 8, 8])
3336 .chunk(&[1, 4, 4])
3337 .max_shape(&[None, Some(8), Some(8)])
3338 .create("v")
3339 .unwrap();
3340 for f in 0..3usize {
3341 for cr in 0..2usize {
3342 for cc in 0..2usize {
3343 let mut bytes = Vec::new();
3344 for i in 0..4usize {
3345 for j in 0..4usize {
3346 let v = (f * 64 + (cr * 4 + i) * 8 + (cc * 4 + j)) as i32;
3347 bytes.extend_from_slice(&v.to_le_bytes());
3348 }
3349 }
3350 ds.write_chunk_at(&[f, cr, cc], &bytes).unwrap();
3351 }
3352 }
3353 }
3354 file.close().unwrap();
3355 }
3356 {
3357 let file = H5File::open(&path).unwrap();
3358 let ds = file.dataset("v").unwrap();
3359 assert_eq!(ds.shape(), vec![3, 8, 8]);
3360 assert_eq!(
3361 ds.read_raw::<i32>().unwrap(),
3362 (0..192).collect::<Vec<i32>>()
3363 );
3364 }
3365 std::fs::remove_file(&path).ok();
3366 }
3367
3368 #[test]
3369 fn fill_value_contiguous_roundtrip() {
3370 let path = temp_path("fill_value_contig");
3371 {
3372 let file = H5File::create(&path).unwrap();
3373 let ds = file
3374 .new_dataset::<f32>()
3375 .shape([4])
3376 .fill_value(2.5f32)
3377 .create("data")
3378 .unwrap();
3379 ds.write_raw(&[1.0f32, 2.0, 3.0, 4.0]).unwrap();
3380 file.close().unwrap();
3381 }
3382 {
3384 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
3385 let idx = writer.dataset_index("data").unwrap();
3386 assert_eq!(
3387 writer.ds(idx).lock().fill_value,
3388 Some(2.5f32.to_le_bytes().to_vec())
3389 );
3390 }
3391 {
3393 let file = H5File::open(&path).unwrap();
3394 let ds = file.dataset("data").unwrap();
3395 assert_eq!(ds.read_raw::<f32>().unwrap(), vec![1.0, 2.0, 3.0, 4.0]);
3396 }
3397 std::fs::remove_file(&path).ok();
3398 }
3399
3400 #[test]
3401 fn fill_value_chunked_roundtrip() {
3402 let path = temp_path("fill_value_chunked");
3403 {
3404 let file = H5File::create(&path).unwrap();
3405 let ds = file
3406 .new_dataset::<i32>()
3407 .shape([0])
3408 .chunk(&[4])
3409 .max_shape(&[None])
3410 .fill_value(-7i32)
3411 .create("vals")
3412 .unwrap();
3413 ds.append(&[1i32, 2, 3, 4]).unwrap();
3414 file.close().unwrap();
3415 }
3416 {
3417 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
3418 let idx = writer.dataset_index("vals").unwrap();
3419 assert_eq!(
3420 writer.ds(idx).lock().fill_value,
3421 Some((-7i32).to_le_bytes().to_vec())
3422 );
3423 }
3424 std::fs::remove_file(&path).ok();
3425 }
3426
3427 #[test]
3428 fn fill_value_read_missing_chunks() {
3429 fn i32_bytes(vals: &[i32]) -> Vec<u8> {
3432 vals.iter().flat_map(|v| v.to_le_bytes()).collect()
3433 }
3434 let path = temp_path("fill_value_read_missing");
3435 {
3436 let file = H5File::create(&path).unwrap();
3437 let ds = file
3438 .new_dataset::<i32>()
3439 .shape([0])
3440 .chunk(&[2])
3441 .max_shape(&[None])
3442 .fill_value(-1i32)
3443 .create("vals")
3444 .unwrap();
3445 ds.write_chunk(0, &i32_bytes(&[10, 20])).unwrap();
3447 ds.write_chunk(2, &i32_bytes(&[50, 60])).unwrap();
3448 ds.extend(&[6]).unwrap();
3449 file.close().unwrap();
3450 }
3451 {
3452 let file = H5File::open(&path).unwrap();
3453 let ds = file.dataset("vals").unwrap();
3454 let all = ds.read_raw::<i32>().unwrap();
3455 assert_eq!(all, vec![10, 20, -1, -1, 50, 60]);
3456 }
3457 std::fs::remove_file(&path).ok();
3458 }
3459
3460 #[test]
3461 fn fill_value_partial_chunk_padded_with_fill() {
3462 let path = temp_path("fill_value_partial_pad");
3466 {
3467 let file = H5File::create(&path).unwrap();
3468 let ds = file
3469 .new_dataset::<i32>()
3470 .shape([0])
3471 .chunk(&[4])
3472 .max_shape(&[None])
3473 .fill_value(-9i32)
3474 .create("vals")
3475 .unwrap();
3476 ds.append(&[1i32, 2, 3]).unwrap();
3478 file.close().unwrap();
3479 }
3480 let bytes = std::fs::read(&path).unwrap();
3481 let needle: Vec<u8> = [1i32, 2, 3].iter().flat_map(|v| v.to_le_bytes()).collect();
3483 let pos = bytes
3484 .windows(needle.len())
3485 .position(|w| w == needle)
3486 .expect("chunk data [1,2,3] not found in file");
3487 let pad = &bytes[pos + needle.len()..pos + needle.len() + 4];
3488 assert_eq!(
3489 pad,
3490 &(-9i32).to_le_bytes(),
3491 "partial chunk tail must be padded with fill value -9, got {:?}",
3492 pad
3493 );
3494 std::fs::remove_file(&path).ok();
3495 }
3496
3497 #[test]
3498 fn vlen_append_after_reopen_preserves_existing() {
3499 let path = temp_path("vlen_append_reopen");
3502 {
3503 let file = H5File::create(&path).unwrap();
3504 file.create_appendable_vlen_dataset("strs", 4, None)
3505 .unwrap();
3506 file.append_vlen_strings("strs", &["a", "b", "c"]).unwrap();
3508 file.close().unwrap();
3509 }
3510 {
3511 let file = H5File::open_rw(&path).unwrap();
3513 file.append_vlen_strings("strs", &["d"]).unwrap();
3514 file.close().unwrap();
3515 }
3516 {
3517 let file = H5File::open(&path).unwrap();
3518 let ds = file.dataset("strs").unwrap();
3519 let got = ds.read_vlen_strings().unwrap();
3520 assert_eq!(
3521 got.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
3522 vec!["a", "b", "c", "d"]
3523 );
3524 }
3525 std::fs::remove_file(&path).ok();
3526 }
3527
3528 #[test]
3529 fn fill_value_size_mismatch_errors() {
3530 let path = temp_path("fill_value_mismatch");
3531 let writer = crate::io::writer::Hdf5Writer::create(&path).unwrap();
3532 let dt = <f64 as crate::types::H5Type>::hdf5_type();
3533 let idx = writer.create_dataset("d", dt, &[4u64]).unwrap();
3534 assert!(writer.set_dataset_fill_value(idx, vec![0u8; 4]).is_err());
3536 writer.set_dataset_fill_value(idx, vec![0u8; 8]).unwrap();
3538 writer.close().unwrap();
3539 std::fs::remove_file(&path).ok();
3540 }
3541
3542 #[test]
3543 fn datatype_exposes_class_sign_and_byteorder() {
3544 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
3548
3549 let path = temp_path("datatype_accessor");
3550 {
3551 let file = H5File::create(&path).unwrap();
3552 file.new_dataset::<u8>().shape([3]).create("u8d").unwrap();
3553 file.new_dataset::<i8>().shape([3]).create("i8d").unwrap();
3554 file.new_dataset::<i32>().shape([3]).create("i32d").unwrap();
3555 file.new_dataset::<f32>().shape([3]).create("f32d").unwrap();
3556 file.close().unwrap();
3557 }
3558
3559 let file = H5File::open(&path).unwrap();
3560
3561 match file.dataset("u8d").unwrap().datatype().unwrap() {
3562 DatatypeMessage::FixedPoint {
3563 size,
3564 signed,
3565 byte_order,
3566 ..
3567 } => {
3568 assert_eq!(size, 1);
3569 assert!(!signed, "u8 must be unsigned");
3570 assert_eq!(byte_order, ByteOrder::LittleEndian);
3571 }
3572 other => panic!("expected FixedPoint for u8, got {other:?}"),
3573 }
3574
3575 match file.dataset("i8d").unwrap().datatype().unwrap() {
3576 DatatypeMessage::FixedPoint { size, signed, .. } => {
3577 assert_eq!(size, 1);
3578 assert!(signed, "i8 must be signed");
3579 }
3580 other => panic!("expected FixedPoint for i8, got {other:?}"),
3581 }
3582
3583 match file.dataset("i32d").unwrap().datatype().unwrap() {
3584 DatatypeMessage::FixedPoint { size, signed, .. } => {
3585 assert_eq!(size, 4);
3586 assert!(signed, "i32 must be signed");
3587 }
3588 other => panic!("expected FixedPoint for i32, got {other:?}"),
3589 }
3590
3591 match file.dataset("f32d").unwrap().datatype().unwrap() {
3592 DatatypeMessage::FloatingPoint { size, .. } => assert_eq!(size, 4),
3593 other => panic!("expected FloatingPoint for f32, got {other:?}"),
3594 }
3595
3596 std::fs::remove_file(&path).ok();
3597 }
3598
3599 #[test]
3600 fn datatype_in_write_mode_errors() {
3601 let path = temp_path("datatype_write_mode");
3602 let file = H5File::create(&path).unwrap();
3603 let ds = file.new_dataset::<f32>().shape([4]).create("d").unwrap();
3604 assert!(ds.datatype().is_err());
3605 std::fs::remove_file(&path).ok();
3606 }
3607
3608 #[cfg(feature = "deflate")]
3614 #[test]
3615 fn write_chunk_raw_ea_roundtrip_mask0() {
3616 use crate::format::messages::filter::{apply_filters, FilterPipeline};
3617 let path = temp_path("wcr_ea_mask0");
3618 let original: Vec<i32> = (0..12).collect();
3619 {
3620 let file = H5File::create(&path).unwrap();
3621 let ds = file
3622 .new_dataset::<i32>()
3623 .shape([0])
3624 .chunk(&[4])
3625 .max_shape(&[None])
3626 .deflate(4)
3627 .create("v")
3628 .unwrap();
3629 assert!(ds.is_chunked());
3630 let pipeline = FilterPipeline::deflate(4);
3631 for c in 0..3usize {
3632 let raw: Vec<u8> = original[c * 4..c * 4 + 4]
3633 .iter()
3634 .flat_map(|v| v.to_le_bytes())
3635 .collect();
3636 let compressed = apply_filters(&pipeline, &raw).unwrap();
3637 ds.write_chunk_raw(c, &compressed, 0).unwrap();
3638 }
3639 ds.set_extent(&[12]).unwrap();
3640 file.close().unwrap();
3641 }
3642 {
3643 let file = H5File::open(&path).unwrap();
3644 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
3645 assert_eq!(v, original);
3646 }
3647 std::fs::remove_file(&path).ok();
3648 }
3649
3650 #[cfg(feature = "deflate")]
3653 #[test]
3654 fn write_chunk_raw_fixed_array_roundtrip_mask0() {
3655 use crate::format::messages::filter::{apply_filters, FilterPipeline};
3656 let path = temp_path("wcr_fa_mask0");
3657 let original: Vec<i32> = (0..12).collect();
3658 {
3659 let file = H5File::create(&path).unwrap();
3660 let ds = file
3661 .new_dataset::<i32>()
3662 .shape([12])
3663 .chunk(&[4])
3664 .deflate(4)
3665 .create("v")
3666 .unwrap();
3667 assert!(ds.is_chunked());
3668 let pipeline = FilterPipeline::deflate(4);
3669 for c in 0..3usize {
3670 let raw: Vec<u8> = original[c * 4..c * 4 + 4]
3671 .iter()
3672 .flat_map(|v| v.to_le_bytes())
3673 .collect();
3674 let compressed = apply_filters(&pipeline, &raw).unwrap();
3675 ds.write_chunk_raw(c, &compressed, 0).unwrap();
3676 }
3677 file.close().unwrap();
3678 }
3679 {
3680 let file = H5File::open(&path).unwrap();
3681 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
3682 assert_eq!(v, original);
3683 }
3684 std::fs::remove_file(&path).ok();
3685 }
3686
3687 #[cfg(feature = "deflate")]
3693 #[test]
3694 fn write_chunk_raw_records_filter_mask() {
3695 let path = temp_path("wcr_records_mask");
3696 let raw: Vec<u8> = [10i32, 20, 30, 40]
3697 .iter()
3698 .flat_map(|v| v.to_le_bytes())
3699 .collect();
3700 assert_eq!(raw.len(), 16);
3701 {
3702 let file = H5File::create(&path).unwrap();
3703 let ds = file
3704 .new_dataset::<i32>()
3705 .shape([0])
3706 .chunk(&[4])
3707 .max_shape(&[None])
3708 .deflate(4)
3709 .create("v")
3710 .unwrap();
3711 ds.write_chunk_raw(0, &raw, 1).unwrap();
3714 ds.set_extent(&[4]).unwrap();
3715 file.close().unwrap();
3716 }
3717 {
3720 let w = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
3721 let idx = w.dataset_index("v").unwrap();
3722 let ds = w.ds(idx);
3723 let m = ds.lock();
3724 let entry = &m
3725 .chunked
3726 .as_ref()
3727 .unwrap()
3728 .filt_iblk
3729 .as_ref()
3730 .unwrap()
3731 .elements[0];
3732 assert_eq!(entry.filter_mask, 1, "filter_mask must round-trip to disk");
3733 assert_eq!(entry.nbytes, 16, "uncompressed chunk stored verbatim");
3734 }
3735 std::fs::remove_file(&path).ok();
3736 }
3737
3738 #[cfg(feature = "deflate")]
3743 #[test]
3744 fn write_chunk_raw_ea_per_chunk_mask_roundtrip() {
3745 use crate::format::messages::filter::{apply_filters, FilterPipeline};
3746 let path = temp_path("wcr_ea_per_chunk_mask");
3747 let original: Vec<i32> = (0..8).collect();
3748 let pipeline = FilterPipeline::deflate(4);
3749 {
3750 let file = H5File::create(&path).unwrap();
3751 let ds = file
3752 .new_dataset::<i32>()
3753 .shape([0])
3754 .chunk(&[4])
3755 .max_shape(&[None])
3756 .deflate(4)
3757 .create("v")
3758 .unwrap();
3759 let raw0: Vec<u8> = original[0..4]
3760 .iter()
3761 .flat_map(|v| v.to_le_bytes())
3762 .collect();
3763 ds.write_chunk_raw(0, &apply_filters(&pipeline, &raw0).unwrap(), 0)
3765 .unwrap();
3766 let raw1: Vec<u8> = original[4..8]
3767 .iter()
3768 .flat_map(|v| v.to_le_bytes())
3769 .collect();
3770 ds.write_chunk_raw(1, &raw1, 1).unwrap();
3772 ds.set_extent(&[8]).unwrap();
3773 file.close().unwrap();
3774 }
3775 {
3776 let file = H5File::open(&path).unwrap();
3777 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
3778 assert_eq!(v, original);
3779 }
3780 std::fs::remove_file(&path).ok();
3781 }
3782
3783 #[cfg(feature = "deflate")]
3786 #[test]
3787 fn write_chunk_raw_fixed_array_per_chunk_mask_roundtrip() {
3788 use crate::format::messages::filter::{apply_filters, FilterPipeline};
3789 let path = temp_path("wcr_fa_per_chunk_mask");
3790 let original: Vec<i32> = (0..8).collect();
3791 let pipeline = FilterPipeline::deflate(4);
3792 {
3793 let file = H5File::create(&path).unwrap();
3794 let ds = file
3795 .new_dataset::<i32>()
3796 .shape([8])
3797 .chunk(&[4])
3798 .deflate(4)
3799 .create("v")
3800 .unwrap();
3801 let raw0: Vec<u8> = original[0..4]
3802 .iter()
3803 .flat_map(|v| v.to_le_bytes())
3804 .collect();
3805 ds.write_chunk_raw(0, &apply_filters(&pipeline, &raw0).unwrap(), 0)
3806 .unwrap();
3807 let raw1: Vec<u8> = original[4..8]
3808 .iter()
3809 .flat_map(|v| v.to_le_bytes())
3810 .collect();
3811 ds.write_chunk_raw(1, &raw1, 1).unwrap();
3812 file.close().unwrap();
3813 }
3814 {
3815 let file = H5File::open(&path).unwrap();
3816 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
3817 assert_eq!(v, original);
3818 }
3819 std::fs::remove_file(&path).ok();
3820 }
3821
3822 #[test]
3825 fn write_chunk_raw_rejects_unfiltered() {
3826 let path = temp_path("wcr_unfiltered");
3827 let file = H5File::create(&path).unwrap();
3828 let ds = file
3829 .new_dataset::<i32>()
3830 .shape([0])
3831 .chunk(&[4])
3832 .max_shape(&[None])
3833 .create("v")
3834 .unwrap();
3835 let err = ds.write_chunk_raw(0, &[0u8; 16], 0).unwrap_err();
3836 assert!(
3837 err.to_string().contains("filtered dataset"),
3838 "expected a filtered-dataset error, got: {err}"
3839 );
3840 std::fs::remove_file(&path).ok();
3841 }
3842
3843 #[test]
3846 fn write_chunk_raw_rejects_btree_v2() {
3847 let path = temp_path("wcr_btree2");
3848 let file = H5File::create(&path).unwrap();
3849 let ds = file
3850 .new_dataset::<i32>()
3851 .shape([0, 0])
3852 .chunk(&[2, 2])
3853 .max_shape(&[None, None])
3854 .create("grid")
3855 .unwrap();
3856 let err = ds.write_chunk_raw(0, &[0u8; 16], 0).unwrap_err();
3857 assert!(
3858 err.to_string().contains("v2-B-tree"),
3859 "expected a v2-B-tree rejection, got: {err}"
3860 );
3861 std::fs::remove_file(&path).ok();
3862 }
3863
3864 #[cfg(feature = "deflate")]
3869 #[test]
3870 fn write_chunk_raw_rejects_oversized_chunk() {
3871 let path = temp_path("wcr_oversized");
3872 let file = H5File::create(&path).unwrap();
3873 let ds = file
3874 .new_dataset::<i32>()
3875 .shape([0])
3876 .chunk(&[1])
3877 .max_shape(&[None])
3878 .deflate(4)
3879 .create("v")
3880 .unwrap();
3881 let err = ds.write_chunk_raw(0, &vec![0u8; 70000], 0).unwrap_err();
3882 assert!(
3883 err.to_string().contains("does not fit"),
3884 "expected a chunk-size-field overflow error, got: {err}"
3885 );
3886 std::fs::remove_file(&path).ok();
3887 }
3888}