1use std::path::Path;
20
21use crate::format::messages::superblock_ext::FileSpaceStrategy;
22use crate::io::locking::FileLocking;
23use crate::io::reader::SuperblockExtension;
24use crate::io::writer::{FileSpaceConfig, SharedMessageConfig};
25use crate::io::{Hdf5Reader, Hdf5Writer};
26
27use crate::dataset::{DatasetAccess, DatasetBuilder, H5Dataset};
28use crate::error::{Hdf5Error, Result};
29use crate::format::messages::datatype::{DatatypeMessage, DatatypeNodeVersion};
30use crate::format::messages::filter::FilterPipeline;
31use crate::format::messages::shared::MessageStorage;
32use crate::format::LibverBound;
33use crate::group::H5Group;
34use crate::types::H5Type;
35
36#[cfg(not(feature = "threadsafe"))]
42pub(crate) type SharedInner = std::rc::Rc<std::cell::RefCell<H5FileInner>>;
43
44#[cfg(feature = "threadsafe")]
45pub(crate) type SharedInner = std::sync::Arc<std::sync::RwLock<H5FileInner>>;
46
47#[cfg(not(feature = "threadsafe"))]
49pub(crate) fn borrow_inner(inner: &SharedInner) -> std::cell::Ref<'_, H5FileInner> {
50 inner.borrow()
51}
52
53#[cfg(not(feature = "threadsafe"))]
55pub(crate) fn borrow_inner_mut(inner: &SharedInner) -> std::cell::RefMut<'_, H5FileInner> {
56 inner.borrow_mut()
57}
58
59#[cfg(not(feature = "threadsafe"))]
66pub(crate) fn try_borrow_inner_mut(
67 inner: &SharedInner,
68) -> Option<std::cell::RefMut<'_, H5FileInner>> {
69 inner.try_borrow_mut().ok()
70}
71
72#[cfg(not(feature = "threadsafe"))]
74pub(crate) fn clone_inner(inner: &SharedInner) -> SharedInner {
75 std::rc::Rc::clone(inner)
76}
77
78#[cfg(not(feature = "threadsafe"))]
80pub(crate) fn new_shared(inner: H5FileInner) -> SharedInner {
81 std::rc::Rc::new(std::cell::RefCell::new(inner))
82}
83
84#[cfg(not(feature = "threadsafe"))]
87pub(crate) fn same_inner(a: &SharedInner, b: &SharedInner) -> bool {
88 std::rc::Rc::ptr_eq(a, b)
89}
90
91#[cfg(feature = "threadsafe")]
96pub(crate) fn borrow_inner(inner: &SharedInner) -> std::sync::RwLockReadGuard<'_, H5FileInner> {
97 inner.read().unwrap()
98}
99
100#[cfg(feature = "threadsafe")]
104pub(crate) fn borrow_inner_mut(
105 inner: &SharedInner,
106) -> std::sync::RwLockWriteGuard<'_, H5FileInner> {
107 inner.write().unwrap()
108}
109
110#[cfg(feature = "threadsafe")]
113pub(crate) fn try_borrow_inner_mut(
114 inner: &SharedInner,
115) -> Option<std::sync::RwLockWriteGuard<'_, H5FileInner>> {
116 inner.write().ok()
117}
118
119#[cfg(feature = "threadsafe")]
122pub(crate) fn same_inner(a: &SharedInner, b: &SharedInner) -> bool {
123 std::sync::Arc::ptr_eq(a, b)
124}
125
126#[cfg(feature = "threadsafe")]
127pub(crate) fn clone_inner(inner: &SharedInner) -> SharedInner {
128 std::sync::Arc::clone(inner)
129}
130
131#[cfg(feature = "threadsafe")]
132pub(crate) fn new_shared(inner: H5FileInner) -> SharedInner {
133 std::sync::Arc::new(std::sync::RwLock::new(inner))
134}
135
136pub(crate) enum H5FileInner {
142 Writer(Box<Hdf5Writer>),
147 Reader(Box<Hdf5Reader>),
148 Closed,
150}
151
152pub struct H5File {
158 pub(crate) inner: SharedInner,
159}
160
161impl H5File {
162 pub fn create<P: AsRef<Path>>(path: P) -> Result<Self> {
164 let writer = Hdf5Writer::create(path.as_ref())?;
165 Ok(Self {
166 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
167 })
168 }
169
170 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
172 let reader = Hdf5Reader::open(path.as_ref())?;
173 Ok(Self {
174 inner: new_shared(H5FileInner::Reader(Box::new(reader))),
175 })
176 }
177
178 pub fn open_rw<P: AsRef<Path>>(path: P) -> Result<Self> {
192 let writer = Hdf5Writer::open_append(path.as_ref())?;
193 Ok(Self {
194 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
195 })
196 }
197
198 pub fn options() -> H5FileOptions {
212 H5FileOptions::default()
213 }
214
215 pub fn set_libver_latest(&self, latest: bool) -> Result<()> {
242 let mut inner = borrow_inner_mut(&self.inner);
243 match &mut *inner {
244 H5FileInner::Writer(writer) => {
245 writer.set_libver_latest(latest)?;
246 Ok(())
247 }
248 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
249 }
250 }
251
252 pub fn set_libver_bound(&self, libver: LibverBound) -> Result<()> {
276 let mut inner = borrow_inner_mut(&self.inner);
277 match &mut *inner {
278 H5FileInner::Writer(writer) => {
279 writer.set_libver_bound(libver)?;
280 Ok(())
281 }
282 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
283 }
284 }
285
286 pub fn set_track_order(&self, track: bool) -> Result<()> {
301 let mut inner = borrow_inner_mut(&self.inner);
302 match &mut *inner {
303 H5FileInner::Writer(writer) => {
304 writer.set_track_order(track);
305 Ok(())
306 }
307 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
308 }
309 }
310
311 pub fn set_track_times(&self, track: bool) -> Result<()> {
324 let mut inner = borrow_inner_mut(&self.inner);
325 match &mut *inner {
326 H5FileInner::Writer(writer) => {
327 writer.set_track_times(track);
328 Ok(())
329 }
330 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
331 }
332 }
333
334 pub fn root_group(&self) -> H5Group {
338 H5Group::new(clone_inner(&self.inner), "/".to_string())
339 }
340
341 pub fn create_group(&self, name: &str) -> Result<H5Group> {
349 self.root_group().create_group(name)
350 }
351
352 pub fn create_soft_link(&self, link_name: &str, target_path: &str) -> Result<()> {
363 self.root_group().create_soft_link(link_name, target_path)
364 }
365
366 pub fn create_external_link(
376 &self,
377 link_name: &str,
378 target_file: &str,
379 target_path: &str,
380 ) -> Result<()> {
381 self.root_group()
382 .create_external_link(link_name, target_file, target_path)
383 }
384
385 pub fn commit_datatype(
396 &self,
397 name: &str,
398 datatype: crate::format::messages::datatype::DatatypeMessage,
399 ) -> Result<()> {
400 self.root_group().commit_datatype(name, datatype)
401 }
402
403 pub fn new_dataset<T: H5Type>(&self) -> DatasetBuilder<T> {
414 DatasetBuilder::new(clone_inner(&self.inner))
415 }
416
417 pub fn set_attr_string(&self, name: &str, value: &str) -> Result<()> {
422 let inner = borrow_inner(&self.inner);
423 match &*inner {
424 H5FileInner::Writer(writer) => {
425 writer.set_vlen_string_attribute(
426 crate::io::writer::AttrTarget::Root,
427 name,
428 value,
429 )?;
430 Ok(())
431 }
432 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
433 }
434 }
435
436 pub fn set_attr_numeric<T: crate::types::H5Type>(&self, name: &str, value: &T) -> Result<()> {
438 use crate::format::messages::attribute::AttributeMessage;
439 let es = T::element_size();
440 let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
441 let attr = AttributeMessage::scalar_numeric(name, T::hdf5_type(), raw.to_vec());
442 let inner = borrow_inner(&self.inner);
443 match &*inner {
444 H5FileInner::Writer(writer) => {
445 writer.add_root_attribute(attr)?;
446 Ok(())
447 }
448 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
449 }
450 }
451
452 pub fn set_attr_typed(
478 &self,
479 name: &str,
480 datatype: DatatypeMessage,
481 value: Vec<u8>,
482 ) -> Result<()> {
483 use crate::format::messages::attribute::AttributeMessage;
484 if value.len() as u64 != u64::from(datatype.element_size()) {
485 return Err(Hdf5Error::InvalidState(format!(
486 "attribute '{name}' was given {} bytes for a datatype whose element is {}",
487 value.len(),
488 datatype.element_size()
489 )));
490 }
491 let attr = AttributeMessage::scalar_numeric(name, datatype, value);
492 let inner = borrow_inner(&self.inner);
493 match &*inner {
494 H5FileInner::Writer(writer) => {
495 writer.add_root_attribute(attr)?;
496 Ok(())
497 }
498 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
499 }
500 }
501
502 pub fn set_attr_array_numeric<T: crate::types::H5Type>(
510 &self,
511 name: &str,
512 values: &[T],
513 ) -> Result<()> {
514 self.set_attr_array_numeric_nd(name, values, &[values.len()])
515 }
516
517 pub fn set_attr_array_numeric_nd<T: crate::types::H5Type>(
526 &self,
527 name: &str,
528 values: &[T],
529 shape: &[usize],
530 ) -> Result<()> {
531 use crate::format::messages::attribute::AttributeMessage;
532 let n: usize = shape.iter().product();
533 if values.len() != n {
534 return Err(Hdf5Error::InvalidState(format!(
535 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
536 values.len()
537 )));
538 }
539 let es = T::element_size();
540 let raw =
542 unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * es) };
543 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
544 let attr = AttributeMessage::array_numeric(name, T::hdf5_type(), &dims, raw.to_vec());
545 let mut inner = borrow_inner_mut(&self.inner);
546 match &mut *inner {
547 H5FileInner::Writer(writer) => {
548 writer.add_root_attribute(attr)?;
549 Ok(())
550 }
551 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
552 }
553 }
554
555 pub fn set_attr_string_array(&self, name: &str, values: &[&str]) -> Result<()> {
560 self.set_attr_string_array_nd(name, values, &[values.len()])
561 }
562
563 pub fn set_attr_string_array_nd(
573 &self,
574 name: &str,
575 values: &[&str],
576 shape: &[usize],
577 ) -> Result<()> {
578 let n: usize = shape.iter().product();
579 if values.len() != n {
580 return Err(Hdf5Error::InvalidState(format!(
581 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
582 values.len()
583 )));
584 }
585 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
586 let mut inner = borrow_inner_mut(&self.inner);
587 match &mut *inner {
588 H5FileInner::Writer(writer) => {
589 writer.set_vlen_string_array_attribute(
590 crate::io::writer::AttrTarget::Root,
591 name,
592 values,
593 &dims,
594 )?;
595 Ok(())
596 }
597 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
598 }
599 }
600
601 pub fn set_attr_object_reference(&self, name: &str, path: &str) -> Result<()> {
610 self.set_root_reference_attr(name, &[path], &[])
611 }
612
613 pub fn set_attr_object_references(&self, name: &str, paths: &[&str]) -> Result<()> {
617 self.set_root_reference_attr(name, paths, &[paths.len() as u64])
618 }
619
620 fn set_root_reference_attr(&self, name: &str, paths: &[&str], dims: &[u64]) -> Result<()> {
621 let inner = borrow_inner(&self.inner);
622 match &*inner {
623 H5FileInner::Writer(writer) => {
624 writer.set_object_reference_attribute(
625 crate::io::writer::AttrTarget::Root,
626 name,
627 paths,
628 dims,
629 )?;
630 Ok(())
631 }
632 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
633 }
634 }
635
636 pub fn attr_names(&self) -> Result<Vec<String>> {
638 let inner = borrow_inner(&self.inner);
639 match &*inner {
640 H5FileInner::Reader(reader) => Ok(reader.root_attr_names()?),
641 _ => Ok(vec![]),
642 }
643 }
644
645 pub fn attr_unreadable_reason(&self, name: &str) -> Result<Option<String>> {
648 let inner = borrow_inner(&self.inner);
649 match &*inner {
650 H5FileInner::Reader(reader) => {
651 Ok(reader.root_attr_unreadable_reason(name).map(str::to_string))
652 }
653 _ => Err(Hdf5Error::InvalidState("not in read mode".into())),
654 }
655 }
656
657 pub fn attrs_unreadable_reason(&self) -> Result<Option<String>> {
661 let inner = borrow_inner(&self.inner);
662 match &*inner {
663 H5FileInner::Reader(reader) => {
664 Ok(reader.root_attrs_unreadable_reason().map(str::to_string))
665 }
666 _ => Err(Hdf5Error::InvalidState("not in read mode".into())),
667 }
668 }
669
670 pub fn attr_string(&self, name: &str) -> Result<String> {
672 let mut inner = borrow_inner_mut(&self.inner);
673 match &mut *inner {
674 H5FileInner::Reader(reader) => {
675 let attr = reader.root_attr(name)?.clone();
676 Ok(reader.attr_string_value(&attr)?)
677 }
678 _ => Err(Hdf5Error::InvalidState("not in read mode".into())),
679 }
680 }
681
682 pub fn superblock_extension(&self) -> SuperblockExtension {
689 let inner = borrow_inner(&self.inner);
690 match &*inner {
691 H5FileInner::Reader(reader) => reader.superblock_extension().clone(),
692 _ => SuperblockExtension::default(),
693 }
694 }
695
696 pub fn object_message_storage(&self, path: &str) -> Result<Vec<(u8, MessageStorage)>> {
704 let mut inner = borrow_inner_mut(&self.inner);
705 match &mut *inner {
706 H5FileInner::Reader(reader) => Ok(reader.object_message_storage(path)?),
707 _ => Err(Hdf5Error::InvalidState(
708 "object_message_storage is only available in read mode".into(),
709 )),
710 }
711 }
712
713 pub fn object_message_flags(&self, path: &str) -> Result<Vec<(u8, u8)>> {
722 let mut inner = borrow_inner_mut(&self.inner);
723 match &mut *inner {
724 H5FileInner::Reader(reader) => Ok(reader.object_message_flags(path)?),
725 _ => Err(Hdf5Error::InvalidState(
726 "object_message_flags is only available in read mode".into(),
727 )),
728 }
729 }
730
731 pub fn object_datatype_versions(&self, path: &str) -> Result<Vec<DatatypeNodeVersion>> {
742 let mut inner = borrow_inner_mut(&self.inner);
743 match &mut *inner {
744 H5FileInner::Reader(reader) => Ok(reader.object_datatype_versions(path)?),
745 _ => Err(Hdf5Error::InvalidState(
746 "object_datatype_versions is only available in read mode".into(),
747 )),
748 }
749 }
750
751 pub fn object_records_times(&self, path: &str) -> Result<bool> {
761 let mut inner = borrow_inner_mut(&self.inner);
762 match &mut *inner {
763 H5FileInner::Reader(reader) => Ok(reader.object_records_times(path)?),
764 _ => Err(Hdf5Error::InvalidState(
765 "object_records_times is only available in read mode".into(),
766 )),
767 }
768 }
769
770 pub fn tracked_free_space(&self) -> Result<u64> {
779 let mut inner = borrow_inner_mut(&self.inner);
780 match &mut *inner {
781 H5FileInner::Reader(reader) => Ok(reader.tracked_free_space()?),
782 _ => Err(Hdf5Error::InvalidState(
783 "tracked_free_space is only available in read mode".into(),
784 )),
785 }
786 }
787
788 pub fn userblock_size(&self) -> u64 {
792 let inner = borrow_inner(&self.inner);
793 match &*inner {
794 H5FileInner::Reader(reader) => reader.userblock_size(),
795 H5FileInner::Writer(writer) => writer.userblock_size(),
796 H5FileInner::Closed => 0,
797 }
798 }
799
800 pub fn superblock_version(&self) -> Result<u8> {
804 let inner = borrow_inner(&self.inner);
805 match &*inner {
806 H5FileInner::Reader(reader) => Ok(reader.superblock_version()),
807 _ => Err(Hdf5Error::InvalidState(
808 "superblock_version is only available in read mode".into(),
809 )),
810 }
811 }
812
813 pub fn libver_bound(&self) -> Result<LibverBound> {
820 self.superblock_version()
821 .map(LibverBound::from_superblock_version)
822 }
823
824 pub fn is_writable(&self) -> bool {
826 let inner = borrow_inner(&self.inner);
827 matches!(&*inner, H5FileInner::Writer(_))
828 }
829
830 pub fn write_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<H5Dataset> {
840 self.write_vlen_strings_charset(name, strings, 1)
841 }
842
843 pub fn write_vlen_strings_ascii(&self, name: &str, strings: &[&str]) -> Result<H5Dataset> {
855 self.write_vlen_strings_charset(name, strings, 0)
856 }
857
858 fn write_vlen_strings_charset(
861 &self,
862 name: &str,
863 strings: &[&str],
864 charset: u8,
865 ) -> Result<H5Dataset> {
866 let inner = borrow_inner(&self.inner);
867 match &*inner {
868 H5FileInner::Writer(writer) => {
869 let idx = writer.create_vlen_string_dataset(name, strings, charset)?;
870 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
871 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
872 }
873 H5FileInner::Reader(_) => {
874 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
875 }
876 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
877 }
878 }
879
880 pub fn write_vlen_bytes(&self, name: &str, items: &[&[u8]]) -> Result<H5Dataset> {
889 self.write_vlen_numeric(name, items)
890 }
891
892 pub fn write_vlen_numeric<T: H5Type>(&self, name: &str, items: &[&[T]]) -> Result<H5Dataset> {
909 let images = crate::dataset::vlen_sequence_images(items)?;
910 let images: Vec<&[u8]> = images.iter().map(|c| c.as_ref()).collect();
911 let inner = borrow_inner(&self.inner);
912 match &*inner {
913 H5FileInner::Writer(writer) => {
914 let idx = writer.create_vlen_sequence_dataset(name, T::hdf5_type(), &images)?;
915 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
916 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
917 }
918 H5FileInner::Reader(_) => {
919 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
920 }
921 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
922 }
923 }
924
925 pub fn write_vlen_strings_compressed(
932 &self,
933 name: &str,
934 strings: &[&str],
935 chunk_size: usize,
936 pipeline: FilterPipeline,
937 ) -> Result<H5Dataset> {
938 let inner = borrow_inner(&self.inner);
939 match &*inner {
940 H5FileInner::Writer(writer) => {
941 let idx = writer
942 .create_vlen_string_dataset_compressed(name, strings, chunk_size, pipeline)?;
943 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
944 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
945 }
946 H5FileInner::Reader(_) => {
947 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
948 }
949 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
950 }
951 }
952
953 pub fn create_appendable_vlen_dataset(
958 &self,
959 name: &str,
960 chunk_size: usize,
961 pipeline: Option<FilterPipeline>,
962 ) -> Result<H5Dataset> {
963 let inner = borrow_inner(&self.inner);
964 match &*inner {
965 H5FileInner::Writer(writer) => {
966 let idx =
967 writer.create_appendable_vlen_string_dataset(name, chunk_size, pipeline)?;
968 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
969 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
970 }
971 H5FileInner::Reader(_) => {
972 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
973 }
974 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
975 }
976 }
977
978 pub fn append_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
980 let inner = borrow_inner(&self.inner);
981 match &*inner {
982 H5FileInner::Writer(writer) => {
983 let ds_index = writer.open_dataset_index(name)?;
984 writer.append_vlen_strings(ds_index, strings)?;
985 Ok(())
986 }
987 H5FileInner::Reader(_) => {
988 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
989 }
990 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
991 }
992 }
993
994 pub fn delete_dataset(&self, name: &str) -> Result<()> {
1003 let inner = borrow_inner(&self.inner);
1004 match &*inner {
1005 H5FileInner::Writer(writer) => {
1006 writer.delete_dataset(name)?;
1007 Ok(())
1008 }
1009 _ => Err(Hdf5Error::InvalidState("cannot delete in read mode".into())),
1010 }
1011 }
1012
1013 pub fn delete_group(&self, name: &str) -> Result<()> {
1022 let inner = borrow_inner(&self.inner);
1023 match &*inner {
1024 H5FileInner::Writer(writer) => {
1025 writer.delete_group(name)?;
1026 Ok(())
1027 }
1028 _ => Err(Hdf5Error::InvalidState("cannot delete in read mode".into())),
1029 }
1030 }
1031
1032 pub fn dataset(&self, name: &str) -> Result<H5Dataset> {
1037 self.dataset_with(name, DatasetAccess::default())
1038 }
1039
1040 pub fn dataset_with(&self, name: &str, access: DatasetAccess) -> Result<H5Dataset> {
1074 access.validate()?;
1075 let mut inner = borrow_inner_mut(&self.inner);
1078 match &mut *inner {
1079 H5FileInner::Reader(reader) => {
1080 let (open, info) = reader.open_dataset_with(name, &access)?;
1084 let shape: Vec<usize> = info.dataspace.dims.iter().map(|&d| d as usize).collect();
1085 let element_size = info.datatype.element_size() as usize;
1086 Ok(H5Dataset::new_reader(
1087 clone_inner(&self.inner),
1088 name.to_string(),
1089 shape,
1090 element_size,
1091 open,
1092 ))
1093 }
1094 H5FileInner::Writer(_) => Err(Hdf5Error::InvalidState(
1095 "cannot open a dataset by name in write mode; use new_dataset() instead"
1096 .to_string(),
1097 )),
1098 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".to_string())),
1099 }
1100 }
1101
1102 pub fn dataset_writer(&self, name: &str) -> Result<H5Dataset> {
1116 self.dataset_writer_with(name, DatasetAccess::default())
1117 }
1118
1119 pub fn dataset_writer_with(&self, name: &str, access: DatasetAccess) -> Result<H5Dataset> {
1146 access.validate()?;
1147 let inner = borrow_inner(&self.inner);
1148 match &*inner {
1149 H5FileInner::Writer(writer) => {
1150 let index = writer.open_dataset_index(name)?;
1151 let parts = writer.dataset_handle_parts(index, &access)?;
1152 Ok(H5Dataset::new_writer(
1153 clone_inner(&self.inner),
1154 index,
1155 parts,
1156 ))
1157 }
1158 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
1159 "cannot open a dataset_writer in read mode; use dataset() instead".to_string(),
1160 )),
1161 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".to_string())),
1162 }
1163 }
1164
1165 pub fn dataset_names(&self) -> Vec<String> {
1171 let inner = borrow_inner(&self.inner);
1172 match &*inner {
1173 H5FileInner::Reader(reader) => reader
1174 .dataset_names()
1175 .iter()
1176 .map(|s| s.to_string())
1177 .collect(),
1178 H5FileInner::Writer(writer) => writer
1179 .dataset_names()
1180 .iter()
1181 .map(|s| s.to_string())
1182 .collect(),
1183 H5FileInner::Closed => Vec::new(),
1184 }
1185 }
1186
1187 pub fn named_datatype_names(&self) -> Vec<String> {
1194 let inner = borrow_inner(&self.inner);
1195 match &*inner {
1196 H5FileInner::Reader(reader) => reader
1197 .named_datatype_names()
1198 .iter()
1199 .map(|s| s.to_string())
1200 .collect(),
1201 H5FileInner::Writer(writer) => writer.committed_datatype_names(),
1202 H5FileInner::Closed => Vec::new(),
1203 }
1204 }
1205
1206 pub fn named_datatype(&self, path: &str) -> Result<crate::H5NamedDatatype> {
1217 let mut inner = borrow_inner_mut(&self.inner);
1220 match &mut *inner {
1221 H5FileInner::Reader(reader) => {
1222 reader.named_datatype_info(path)?;
1223 drop(inner);
1224 Ok(crate::H5NamedDatatype::new_reader(
1225 clone_inner(&self.inner),
1226 path.to_string(),
1227 ))
1228 }
1229 H5FileInner::Writer(_) => Err(Hdf5Error::InvalidState(
1230 "committed datatypes are readable only in read mode".to_string(),
1231 )),
1232 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".to_string())),
1233 }
1234 }
1235
1236 pub fn close(self) -> Result<()> {
1242 let old = {
1243 let mut inner = borrow_inner_mut(&self.inner);
1244 std::mem::replace(&mut *inner, H5FileInner::Closed)
1245 };
1246 match old {
1247 H5FileInner::Writer(writer) => {
1248 writer.close()?;
1249 Ok(())
1250 }
1251 H5FileInner::Reader(_) => Ok(()),
1252 H5FileInner::Closed => Ok(()),
1253 }
1254 }
1255
1256 pub fn close_no_sync(self) -> Result<()> {
1272 let old = {
1273 let mut inner = borrow_inner_mut(&self.inner);
1274 std::mem::replace(&mut *inner, H5FileInner::Closed)
1275 };
1276 match old {
1277 H5FileInner::Writer(writer) => {
1278 writer.close_no_sync()?;
1279 Ok(())
1280 }
1281 H5FileInner::Reader(_) => Ok(()),
1282 H5FileInner::Closed => Ok(()),
1283 }
1284 }
1285
1286 pub fn flush(&self) -> Result<()> {
1294 let mut inner = borrow_inner_mut(&self.inner);
1295 match &mut *inner {
1296 H5FileInner::Writer(writer) => Ok(writer.handle().flush()?),
1297 H5FileInner::Reader(_) => Ok(()),
1298 H5FileInner::Closed => Ok(()),
1299 }
1300 }
1301}
1302
1303#[derive(Debug, Default, Clone)]
1310pub struct H5FileOptions {
1311 locking: Option<FileLocking>,
1312 track_order: bool,
1313 track_times: bool,
1314 libver: Option<LibverBound>,
1315 userblock: u64,
1316 shared_messages: SharedMessageConfig,
1317 file_space: Option<FileSpaceConfig>,
1318 file_space_page_size: Option<u64>,
1319 elink_prefix: Option<String>,
1320}
1321
1322impl H5FileOptions {
1323 pub fn new() -> Self {
1325 Self::default()
1326 }
1327
1328 pub fn locking(mut self, policy: FileLocking) -> Self {
1331 self.locking = Some(policy);
1332 self
1333 }
1334
1335 pub fn no_locking(self) -> Self {
1340 self.locking(FileLocking::Disabled)
1341 }
1342
1343 pub fn best_effort_locking(self) -> Self {
1346 self.locking(FileLocking::BestEffort)
1347 }
1348
1349 pub fn elink_prefix(mut self, prefix: impl Into<String>) -> Self {
1380 self.elink_prefix = Some(prefix.into());
1381 self
1382 }
1383
1384 pub fn track_order(mut self, track: bool) -> Self {
1392 self.track_order = track;
1393 self
1394 }
1395
1396 pub fn track_times(mut self, track: bool) -> Self {
1416 self.track_times = track;
1417 self
1418 }
1419
1420 pub fn libver(mut self, libver: LibverBound) -> Self {
1463 self.libver = Some(libver);
1464 self
1465 }
1466
1467 pub fn userblock(mut self, size: u64) -> Self {
1490 self.userblock = size;
1491 self
1492 }
1493
1494 pub fn shared_messages(
1525 mut self,
1526 indexes: &[(u16, u32)],
1527 list_max: u16,
1528 btree_min: u16,
1529 ) -> Self {
1530 self.shared_messages = SharedMessageConfig::new(indexes, list_max, btree_min);
1531 self
1532 }
1533
1534 pub fn file_space(
1568 mut self,
1569 strategy: FileSpaceStrategy,
1570 persist: bool,
1571 threshold: u64,
1572 ) -> Self {
1573 self.file_space = Some(FileSpaceConfig::new(strategy, persist, threshold));
1574 self
1575 }
1576
1577 pub fn file_space_page_size(mut self, size: u64) -> Self {
1605 self.file_space_page_size = Some(size);
1606 self
1607 }
1608
1609 fn resolved_file_space(&self) -> FileSpaceConfig {
1618 let config = self.file_space.unwrap_or_default();
1619 match self.file_space_page_size {
1620 Some(size) => config.with_page_size(size),
1621 None => config,
1622 }
1623 }
1624
1625 fn resolved_locking(&self) -> FileLocking {
1626 match self.locking {
1627 Some(p) => p,
1628 None => FileLocking::from_env_or(FileLocking::default()),
1629 }
1630 }
1631
1632 fn refuse_create_only_options(&self) -> Result<()> {
1650 let mut offending = Vec::new();
1651 if self.track_order {
1652 offending.push("track_order");
1653 }
1654 if self.track_times {
1655 offending.push("track_times");
1656 }
1657 if self.libver.is_some() {
1658 offending.push("libver");
1659 }
1660 if self.userblock != 0 {
1661 offending.push("userblock");
1662 }
1663 if self.shared_messages != SharedMessageConfig::default() {
1664 offending.push("shared_messages");
1665 }
1666 if self.file_space.is_some() {
1667 offending.push("file_space");
1668 }
1669 if self.file_space_page_size.is_some() {
1670 offending.push("file_space_page_size");
1671 }
1672 if offending.is_empty() {
1673 Ok(())
1674 } else {
1675 Err(Hdf5Error::InvalidState(format!(
1676 "these options only take effect when creating a file, not when \
1677 opening an existing one: {}",
1678 offending.join(", ")
1679 )))
1680 }
1681 }
1682
1683 fn refuse_read_only_options(&self) -> Result<()> {
1691 if self.elink_prefix.is_none() {
1692 return Ok(());
1693 }
1694 Err(Hdf5Error::InvalidState(
1695 "these options only take effect when opening a file for reading: \
1696 elink_prefix"
1697 .to_string(),
1698 ))
1699 }
1700
1701 pub fn create<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1703 self.refuse_read_only_options()?;
1704 let writer = Hdf5Writer::create_with_options(
1705 path.as_ref(),
1706 crate::io::writer::FileCreateOptions {
1707 locking: self.resolved_locking(),
1708 track_order: self.track_order,
1709 track_times: self.track_times,
1710 libver: self.libver,
1711 userblock: self.userblock,
1712 shared_messages: self.shared_messages,
1713 file_space: self.resolved_file_space(),
1714 },
1715 )?;
1716 Ok(H5File {
1717 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
1718 })
1719 }
1720
1721 pub fn open<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1723 self.refuse_create_only_options()?;
1724 let mut reader = Hdf5Reader::open_with_locking(path.as_ref(), self.resolved_locking())?;
1725 reader.set_elink_prefix(self.elink_prefix);
1726 Ok(H5File {
1727 inner: new_shared(H5FileInner::Reader(Box::new(reader))),
1728 })
1729 }
1730
1731 pub fn open_rw<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1733 self.refuse_create_only_options()?;
1734 self.refuse_read_only_options()?;
1735 let writer = Hdf5Writer::open_append_with_locking(path.as_ref(), self.resolved_locking())?;
1736 Ok(H5File {
1737 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
1738 })
1739 }
1740}
1741
1742#[cfg(test)]
1743fn unique_test_path(name: &str) -> std::path::PathBuf {
1744 use std::sync::atomic::{AtomicU64, Ordering};
1749 static COUNTER: AtomicU64 = AtomicU64::new(0);
1750 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
1751 std::env::temp_dir().join(format!(
1752 "rust_hdf5_test_{}_{}_{}.h5",
1753 name,
1754 std::process::id(),
1755 n
1756 ))
1757}
1758
1759#[cfg(test)]
1760mod tests {
1761 use super::*;
1762 use std::path::PathBuf;
1763
1764 fn temp_path(name: &str) -> PathBuf {
1765 super::unique_test_path(name)
1766 }
1767
1768 #[test]
1769 fn create_and_close_empty() {
1770 let path = temp_path("create_empty");
1771 let file = H5File::create(&path).unwrap();
1772 file.close().unwrap();
1773
1774 let file = H5File::open(&path).unwrap();
1776 file.close().unwrap();
1777
1778 std::fs::remove_file(&path).ok();
1779 }
1780
1781 #[test]
1782 fn create_and_drop_empty() {
1783 let path = temp_path("drop_empty");
1784 {
1785 let _file = H5File::create(&path).unwrap();
1786 }
1788 let file = H5File::open(&path).unwrap();
1790 file.close().unwrap();
1791
1792 std::fs::remove_file(&path).ok();
1793 }
1794
1795 #[test]
1796 fn dataset_not_found() {
1797 let path = temp_path("ds_not_found");
1798 {
1799 let _file = H5File::create(&path).unwrap();
1800 }
1801 let file = H5File::open(&path).unwrap();
1802 let result = file.dataset("nonexistent");
1803 assert!(result.is_err());
1804
1805 std::fs::remove_file(&path).ok();
1806 }
1807
1808 #[test]
1809 fn write_and_read_roundtrip() {
1810 let path = temp_path("write_read_rt");
1811
1812 {
1814 let file = H5File::create(&path).unwrap();
1815 let ds = file
1816 .new_dataset::<u8>()
1817 .shape([4, 4])
1818 .create("data")
1819 .unwrap();
1820 ds.write_raw(&[0u8; 16]).unwrap();
1821 file.close().unwrap();
1822 }
1823
1824 {
1826 let file = H5File::open(&path).unwrap();
1827 let ds = file.dataset("data").unwrap();
1828 assert_eq!(ds.shape(), vec![4, 4]);
1829 let data = ds.read_raw::<u8>().unwrap();
1830 assert_eq!(data.len(), 16);
1831 assert!(data.iter().all(|&b| b == 0));
1832 file.close().unwrap();
1833 }
1834
1835 std::fs::remove_file(&path).ok();
1836 }
1837
1838 #[test]
1839 fn close_no_sync_produces_valid_readable_file() {
1840 let path = temp_path("close_no_sync_rt");
1841 let payload: Vec<u8> = (0u8..16).collect();
1842
1843 {
1845 let file = H5File::create(&path).unwrap();
1846 let ds = file
1847 .new_dataset::<u8>()
1848 .shape([4, 4])
1849 .create("data")
1850 .unwrap();
1851 ds.write_raw(&payload).unwrap();
1852 file.close_no_sync().unwrap();
1855 }
1856
1857 {
1860 let file = H5File::open(&path).unwrap();
1861 let ds = file.dataset("data").unwrap();
1862 assert_eq!(ds.shape(), vec![4, 4]);
1863 let data = ds.read_raw::<u8>().unwrap();
1864 assert_eq!(data, payload);
1865 file.close().unwrap();
1866 }
1867
1868 std::fs::remove_file(&path).ok();
1869 }
1870
1871 #[test]
1872 fn create_over_existing_file_truncates() {
1873 let path = temp_path("create_truncates");
1879
1880 {
1881 let file = H5File::create(&path).unwrap();
1882 let ds = file
1883 .new_dataset::<u8>()
1884 .shape([4, 4])
1885 .create("old_data")
1886 .unwrap();
1887 ds.write_raw(&[7u8; 16]).unwrap();
1888 file.close().unwrap();
1889 }
1890 assert!(std::fs::metadata(&path).unwrap().len() > 0);
1891
1892 {
1894 let file = H5File::create(&path).unwrap();
1895 file.close().unwrap();
1896 }
1897
1898 let file = H5File::open(&path).unwrap();
1900 assert!(file.dataset("old_data").is_err());
1901 file.close().unwrap();
1902
1903 std::fs::remove_file(&path).ok();
1904 }
1905
1906 #[test]
1907 fn close_no_sync_chunked_dataset_valid() {
1908 let path = temp_path("close_no_sync_chunked");
1913
1914 {
1915 let file = H5File::create(&path).unwrap();
1916 let ds = file
1917 .new_dataset::<i32>()
1918 .shape([0usize, 3])
1919 .chunk(&[1, 3])
1920 .max_shape(&[None, Some(3)])
1921 .create("data")
1922 .unwrap();
1923 for frame in 0..10u64 {
1925 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
1926 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
1927 ds.write_chunk(frame as usize, &raw).unwrap();
1928 }
1929 ds.extend(&[10, 3]).unwrap();
1930 file.close_no_sync().unwrap();
1931 }
1932
1933 {
1934 let file = H5File::open(&path).unwrap();
1935 let ds = file.dataset("data").unwrap();
1936 assert_eq!(ds.shape(), vec![10, 3]);
1937 let data = ds.read_raw::<i32>().unwrap();
1938 let expected: Vec<i32> = (0..30).collect();
1939 assert_eq!(data, expected);
1940 file.close().unwrap();
1941 }
1942
1943 std::fs::remove_file(&path).ok();
1944 }
1945
1946 #[test]
1947 fn write_and_read_f64() {
1948 let path = temp_path("write_read_f64");
1949
1950 let values: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1951
1952 {
1954 let file = H5File::create(&path).unwrap();
1955 let ds = file
1956 .new_dataset::<f64>()
1957 .shape([2, 3])
1958 .create("matrix")
1959 .unwrap();
1960 ds.write_raw(&values).unwrap();
1961 file.close().unwrap();
1962 }
1963
1964 {
1966 let file = H5File::open(&path).unwrap();
1967 let ds = file.dataset("matrix").unwrap();
1968 assert_eq!(ds.shape(), vec![2, 3]);
1969 let readback = ds.read_raw::<f64>().unwrap();
1970 assert_eq!(readback, values);
1971 }
1972
1973 std::fs::remove_file(&path).ok();
1974 }
1975
1976 #[test]
1977 fn multiple_datasets() {
1978 let path = temp_path("multi_ds");
1979
1980 {
1981 let file = H5File::create(&path).unwrap();
1982 let ds1 = file.new_dataset::<i32>().shape([3]).create("ints").unwrap();
1983 ds1.write_raw(&[10i32, 20, 30]).unwrap();
1984
1985 let ds2 = file
1986 .new_dataset::<f32>()
1987 .shape([2, 2])
1988 .create("floats")
1989 .unwrap();
1990 ds2.write_raw(&[1.0f32, 2.0, 3.0, 4.0]).unwrap();
1991
1992 file.close().unwrap();
1993 }
1994
1995 {
1996 let file = H5File::open(&path).unwrap();
1997
1998 let ds_ints = file.dataset("ints").unwrap();
1999 assert_eq!(ds_ints.shape(), vec![3]);
2000 let ints = ds_ints.read_raw::<i32>().unwrap();
2001 assert_eq!(ints, vec![10, 20, 30]);
2002
2003 let ds_floats = file.dataset("floats").unwrap();
2004 assert_eq!(ds_floats.shape(), vec![2, 2]);
2005 let floats = ds_floats.read_raw::<f32>().unwrap();
2006 assert_eq!(floats, vec![1.0f32, 2.0, 3.0, 4.0]);
2007 }
2008
2009 std::fs::remove_file(&path).ok();
2010 }
2011
2012 #[test]
2013 fn close_is_idempotent() {
2014 let path = temp_path("close_idemp");
2015 let file = H5File::create(&path).unwrap();
2016 file.close().unwrap();
2017 std::fs::remove_file(&path).ok();
2019 }
2020}
2021
2022#[cfg(test)]
2023mod integration_tests {
2024 use super::*;
2025
2026 fn temp_path(name: &str) -> std::path::PathBuf {
2027 super::unique_test_path(name)
2028 }
2029
2030 #[test]
2031 fn write_file_for_h5dump() {
2032 let path = temp_path("integration");
2033 let file = H5File::create(&path).unwrap();
2034
2035 let ds = file
2036 .new_dataset::<u8>()
2037 .shape([4usize, 4])
2038 .create("data_u8")
2039 .unwrap();
2040 let data: Vec<u8> = (0..16).collect();
2041 ds.write_raw(&data).unwrap();
2042
2043 let ds2 = file
2044 .new_dataset::<f64>()
2045 .shape([3usize, 2])
2046 .create("data_f64")
2047 .unwrap();
2048 let fdata: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
2049 ds2.write_raw(&fdata).unwrap();
2050
2051 let ds3 = file
2052 .new_dataset::<i32>()
2053 .shape([5usize])
2054 .create("values")
2055 .unwrap();
2056 let idata: Vec<i32> = vec![-10, -5, 0, 5, 10];
2057 ds3.write_raw(&idata).unwrap();
2058
2059 file.close().unwrap();
2060
2061 assert!(path.exists());
2063 }
2064
2065 #[test]
2066 fn write_chunked_file_for_h5dump() {
2067 let path = temp_path("chunked");
2068 let file = H5File::create(&path).unwrap();
2069
2070 let ds = file
2072 .new_dataset::<f64>()
2073 .shape([0usize, 4])
2074 .chunk(&[1, 4])
2075 .max_shape(&[None, Some(4)])
2076 .create("streaming_data")
2077 .unwrap();
2078
2079 for frame in 0..5u64 {
2081 let values: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
2082 let raw: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
2083 ds.write_chunk(frame as usize, &raw).unwrap();
2084 }
2085
2086 ds.extend(&[5, 4]).unwrap();
2088 ds.flush().unwrap();
2089
2090 file.close().unwrap();
2091
2092 assert!(path.exists());
2093 }
2094
2095 #[test]
2096 fn write_chunked_many_frames_for_h5dump() {
2097 let path = temp_path("chunked_many");
2098 let file = H5File::create(&path).unwrap();
2099
2100 let ds = file
2101 .new_dataset::<i32>()
2102 .shape([0usize, 3])
2103 .chunk(&[1, 3])
2104 .max_shape(&[None, Some(3)])
2105 .create("data")
2106 .unwrap();
2107
2108 for frame in 0..10u64 {
2110 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2111 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2112 ds.write_chunk(frame as usize, &raw).unwrap();
2113 }
2114 ds.extend(&[10, 3]).unwrap();
2115 file.close().unwrap();
2116
2117 assert!(path.exists());
2118 }
2119
2120 #[test]
2121 fn write_dataset_with_attributes() {
2122 use crate::types::VarLenUnicode;
2123
2124 let path = temp_path("attributes");
2125 let file = H5File::create(&path).unwrap();
2126
2127 let ds = file
2128 .new_dataset::<f32>()
2129 .shape([10usize])
2130 .create("temperature")
2131 .unwrap();
2132 let data: Vec<f32> = (0..10).map(|i| i as f32 * 1.5).collect();
2133 ds.write_raw(&data).unwrap();
2134
2135 let attr = ds
2137 .new_attr::<VarLenUnicode>()
2138 .shape(())
2139 .create("units")
2140 .unwrap();
2141 attr.write_scalar(&VarLenUnicode("kelvin".to_string()))
2142 .unwrap();
2143
2144 let attr2 = ds
2145 .new_attr::<VarLenUnicode>()
2146 .shape(())
2147 .create("description")
2148 .unwrap();
2149 attr2
2150 .write_scalar(&VarLenUnicode("Temperature measurements".to_string()))
2151 .unwrap();
2152
2153 let attr3 = ds
2155 .new_attr::<VarLenUnicode>()
2156 .shape(())
2157 .create("source")
2158 .unwrap();
2159 attr3.write_string("sensor_01").unwrap();
2160
2161 let attr4 = ds
2163 .new_attr::<VarLenUnicode>()
2164 .shape(())
2165 .create("label")
2166 .unwrap();
2167 let s: VarLenUnicode = "test_label".parse().unwrap_or_default();
2168 attr4.write_scalar(&s).unwrap();
2169
2170 file.close().unwrap();
2171
2172 assert!(path.exists());
2173 }
2174
2175 #[test]
2176 fn dataset_writer_reopens_for_attributes() {
2177 let path = temp_path("dataset_writer");
2180 {
2181 let file = H5File::create(&path).unwrap();
2182 {
2183 let ds = file
2184 .new_dataset::<u16>()
2185 .shape([8])
2186 .create("image")
2187 .unwrap();
2188 ds.write_raw(&[0u16; 8]).unwrap();
2189 }
2191
2192 assert!(file.dataset("image").is_err());
2194 let ds = file.dataset_writer("image").unwrap();
2195 assert_eq!(ds.shape(), vec![8]);
2196 ds.new_attr::<i32>()
2197 .shape([3])
2198 .create("NDArrayDimOffset")
2199 .unwrap()
2200 .write_array(&[0i32, 4, 8])
2201 .unwrap();
2202 ds.new_attr::<i32>()
2203 .shape(())
2204 .create("NDUniqueId")
2205 .unwrap()
2206 .write_numeric(&42i32)
2207 .unwrap();
2208
2209 assert!(matches!(
2211 file.dataset_writer("nope"),
2212 Err(crate::error::Hdf5Error::NotFound(_))
2213 ));
2214
2215 file.close().unwrap();
2216 }
2217 {
2218 let file = H5File::open(&path).unwrap();
2219 let ds = file.dataset("image").unwrap();
2220 let off = ds.attr("NDArrayDimOffset").unwrap().read_raw().unwrap();
2221 let got: Vec<i32> = off
2222 .as_chunks::<4>()
2223 .0
2224 .iter()
2225 .map(|b| i32::from_le_bytes(*b))
2226 .collect();
2227 assert_eq!(got, vec![0, 4, 8]);
2228 let uid: i32 = ds.attr("NDUniqueId").unwrap().read_numeric().unwrap();
2229 assert_eq!(uid, 42);
2230 }
2231 std::fs::remove_file(&path).ok();
2232 }
2233
2234 #[test]
2235 fn chunked_write_read_roundtrip() {
2236 let path = temp_path("chunked_roundtrip");
2237
2238 {
2240 let file = H5File::create(&path).unwrap();
2241 let ds = file
2242 .new_dataset::<i32>()
2243 .shape([0usize, 3])
2244 .chunk(&[1, 3])
2245 .max_shape(&[None, Some(3)])
2246 .create("table")
2247 .unwrap();
2248
2249 for frame in 0..8u64 {
2250 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2251 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2252 ds.write_chunk(frame as usize, &raw).unwrap();
2253 }
2254 ds.extend(&[8, 3]).unwrap();
2255 file.close().unwrap();
2256 }
2257
2258 {
2260 let file = H5File::open(&path).unwrap();
2261 let ds = file.dataset("table").unwrap();
2262 assert_eq!(ds.shape(), vec![8, 3]);
2263 let data = ds.read_raw::<i32>().unwrap();
2264 assert_eq!(data.len(), 24);
2265 for (i, val) in data.iter().enumerate() {
2266 assert_eq!(*val, i as i32);
2267 }
2268 }
2269
2270 std::fs::remove_file(&path).ok();
2271 }
2272
2273 #[test]
2274 #[cfg(feature = "deflate")]
2275 fn compressed_chunked_roundtrip() {
2276 let path = temp_path("compressed_roundtrip");
2277
2278 {
2280 let file = H5File::create(&path).unwrap();
2281 let ds = file
2282 .new_dataset::<f64>()
2283 .shape([0usize, 4])
2284 .chunk(&[1, 4])
2285 .max_shape(&[None, Some(4)])
2286 .deflate(6)
2287 .create("compressed")
2288 .unwrap();
2289
2290 for frame in 0..10u64 {
2291 let vals: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
2292 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2293 ds.write_chunk(frame as usize, &raw).unwrap();
2294 }
2295 ds.extend(&[10, 4]).unwrap();
2296 file.close().unwrap();
2297 }
2298
2299 {
2301 let file = H5File::open(&path).unwrap();
2302 let ds = file.dataset("compressed").unwrap();
2303 assert_eq!(ds.shape(), vec![10, 4]);
2304 let data = ds.read_raw::<f64>().unwrap();
2305 assert_eq!(data.len(), 40);
2306 for (i, val) in data.iter().enumerate() {
2307 assert!(
2308 (val - i as f64).abs() < 1e-10,
2309 "mismatch at {}: {} != {}",
2310 i,
2311 val,
2312 i
2313 );
2314 }
2315 }
2316
2317 std::fs::remove_file(&path).ok();
2318 }
2319
2320 #[test]
2321 #[cfg(feature = "deflate")]
2322 fn compressed_chunked_many_frames() {
2323 let path = temp_path("compressed_many");
2324
2325 {
2326 let file = H5File::create(&path).unwrap();
2327 let ds = file
2328 .new_dataset::<i32>()
2329 .shape([0usize, 3])
2330 .chunk(&[1, 3])
2331 .max_shape(&[None, Some(3)])
2332 .deflate(6)
2333 .create("stream")
2334 .unwrap();
2335
2336 for frame in 0..100u64 {
2337 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2338 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2339 ds.write_chunk(frame as usize, &raw).unwrap();
2340 }
2341 ds.extend(&[100, 3]).unwrap();
2342 file.close().unwrap();
2343 }
2344
2345 {
2346 let file = H5File::open(&path).unwrap();
2347 let ds = file.dataset("stream").unwrap();
2348 assert_eq!(ds.shape(), vec![100, 3]);
2349 let data = ds.read_raw::<i32>().unwrap();
2350 assert_eq!(data.len(), 300);
2351 for (i, val) in data.iter().enumerate() {
2352 assert_eq!(*val, i as i32, "mismatch at {}", i);
2353 }
2354 }
2355
2356 std::fs::remove_file(&path).ok();
2357 }
2358 #[test]
2359 fn append_mode() {
2360 let path = temp_path("append");
2361
2362 {
2364 let file = H5File::create(&path).unwrap();
2365 let ds = file
2366 .new_dataset::<i32>()
2367 .shape([3usize])
2368 .create("first")
2369 .unwrap();
2370 ds.write_raw(&[1i32, 2, 3]).unwrap();
2371 file.close().unwrap();
2372 }
2373
2374 {
2376 let file = H5File::open_rw(&path).unwrap();
2377 let ds = file
2378 .new_dataset::<f64>()
2379 .shape([2usize])
2380 .create("second")
2381 .unwrap();
2382 ds.write_raw(&[4.0f64, 5.0]).unwrap();
2383 file.close().unwrap();
2384 }
2385
2386 {
2388 let file = H5File::open(&path).unwrap();
2389 let names = file.dataset_names();
2390 assert!(names.contains(&"first".to_string()));
2391 assert!(names.contains(&"second".to_string()));
2392
2393 let ds1 = file.dataset("first").unwrap();
2394 assert_eq!(ds1.read_raw::<i32>().unwrap(), vec![1, 2, 3]);
2395
2396 let ds2 = file.dataset("second").unwrap();
2397 assert_eq!(ds2.read_raw::<f64>().unwrap(), vec![4.0, 5.0]);
2398 }
2399
2400 std::fs::remove_file(&path).ok();
2401 }
2402
2403 #[test]
2404 fn open_rw_set_attr_preserves_file() {
2405 let path = temp_path("open_rw_attr");
2406 {
2408 let file = H5File::create(&path).unwrap();
2409 let ds = file
2410 .new_dataset::<i32>()
2411 .shape([3usize])
2412 .create("data")
2413 .unwrap();
2414 ds.write_raw(&[10i32, 20, 30]).unwrap();
2415 file.set_attr_string("version", "1.0").unwrap();
2416 file.close().unwrap();
2417 }
2418 {
2420 let file = H5File::open_rw(&path).unwrap();
2421 file.set_attr_string("version", "2.0").unwrap();
2422 file.close().unwrap();
2423 }
2424 {
2426 let file = H5File::open(&path).unwrap();
2427 let ds = file.dataset("data").unwrap();
2428 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![10, 20, 30]);
2429 let ver = file.attr_string("version").unwrap();
2430 assert_eq!(ver, "2.0");
2431 }
2432 std::fs::remove_file(&path).ok();
2433 }
2434
2435 #[test]
2436 #[cfg(feature = "deflate")]
2437 fn open_rw_attr_with_compressed_dataset() {
2438 use crate::format::messages::filter::FilterPipeline;
2439 let path = temp_path("open_rw_compressed");
2440 let input: Vec<&str> = (0..50).map(|_| "test string data").collect();
2441 {
2443 let file = H5File::create(&path).unwrap();
2444 file.write_vlen_strings_compressed("texts", &input, 16, FilterPipeline::deflate(6))
2445 .unwrap();
2446 file.set_attr_string("version", "1.0").unwrap();
2447 file.close().unwrap();
2448 }
2449 {
2451 let file = H5File::open_rw(&path).unwrap();
2452 file.set_attr_string("version", "2.0").unwrap();
2453 file.close().unwrap();
2454 }
2455 {
2457 let file = H5File::open(&path).unwrap();
2458 let ds = file.dataset("texts").unwrap();
2459 let strings = ds.read_vlen_strings().unwrap();
2460 assert_eq!(strings.len(), 50);
2461 assert_eq!(strings[0], "test string data");
2462 let ver = file.attr_string("version").unwrap();
2463 assert_eq!(ver, "2.0");
2464 }
2465 std::fs::remove_file(&path).ok();
2466 }
2467
2468 #[test]
2469 #[cfg(feature = "lz4")]
2470 fn append_vlen_strings_basic() {
2471 use crate::format::messages::filter::FilterPipeline;
2472 let path = temp_path("append_vlen");
2473 {
2474 let file = H5File::create(&path).unwrap();
2475 file.create_appendable_vlen_dataset("names", 4, Some(FilterPipeline::lz4()))
2476 .unwrap();
2477 file.append_vlen_strings("names", &["alice", "bob", "charlie"])
2478 .unwrap();
2479 file.append_vlen_strings("names", &["dave", "eve"]).unwrap();
2480 file.close().unwrap();
2481 }
2482 {
2483 let file = H5File::open(&path).unwrap();
2484 let ds = file.dataset("names").unwrap();
2485 let strings = ds.read_vlen_strings().unwrap();
2486 assert_eq!(strings, vec!["alice", "bob", "charlie", "dave", "eve"]);
2487 }
2488 std::fs::remove_file(&path).ok();
2489 }
2490
2491 #[test]
2492 #[cfg(feature = "lz4")]
2493 fn append_vlen_strings_large() {
2494 use crate::format::messages::filter::FilterPipeline;
2495 let path = temp_path("append_vlen_large");
2496 let batch1: Vec<String> = (0..5000).map(|i| format!("node-{:06}", i)).collect();
2497 let batch2: Vec<String> = (5000..7189).map(|i| format!("node-{:06}", i)).collect();
2498 {
2499 let file = H5File::create(&path).unwrap();
2500 file.create_appendable_vlen_dataset("data", 512, Some(FilterPipeline::lz4()))
2501 .unwrap();
2502 let r1: Vec<&str> = batch1.iter().map(|s| s.as_str()).collect();
2503 file.append_vlen_strings("data", &r1).unwrap();
2504 let r2: Vec<&str> = batch2.iter().map(|s| s.as_str()).collect();
2505 file.append_vlen_strings("data", &r2).unwrap();
2506 file.close().unwrap();
2507 }
2508 {
2509 let file = H5File::open(&path).unwrap();
2510 let ds = file.dataset("data").unwrap();
2511 let strings = ds.read_vlen_strings().unwrap();
2512 assert_eq!(strings.len(), 7189);
2513 assert_eq!(strings[0], "node-000000");
2514 assert_eq!(strings[7188], "node-007188");
2515 }
2516 std::fs::remove_file(&path).ok();
2517 }
2518
2519 #[test]
2520 fn append_vlen_strings_uncompressed() {
2521 let path = temp_path("append_vlen_unc");
2522 {
2523 let file = H5File::create(&path).unwrap();
2524 file.create_appendable_vlen_dataset("texts", 8, None)
2525 .unwrap();
2526 file.append_vlen_strings("texts", &["hello", "world"])
2527 .unwrap();
2528 file.append_vlen_strings("texts", &["foo", "bar", "baz"])
2529 .unwrap();
2530 file.close().unwrap();
2531 }
2532 {
2533 let file = H5File::open(&path).unwrap();
2534 let ds = file.dataset("texts").unwrap();
2535 let strings = ds.read_vlen_strings().unwrap();
2536 assert_eq!(strings, vec!["hello", "world", "foo", "bar", "baz"]);
2537 }
2538 std::fs::remove_file(&path).ok();
2539 }
2540
2541 #[test]
2542 fn delete_dataset_roundtrip() {
2543 let path = temp_path("delete_ds");
2544 {
2545 let file = H5File::create(&path).unwrap();
2546 file.write_vlen_strings("keep", &["a", "b"]).unwrap();
2547 file.write_vlen_strings("remove", &["x", "y"]).unwrap();
2548 file.delete_dataset("remove").unwrap();
2549 file.close().unwrap();
2550 }
2551 {
2552 let file = H5File::open(&path).unwrap();
2553 let names = file.dataset_names();
2554 assert!(names.contains(&"keep".to_string()));
2555 assert!(!names.contains(&"remove".to_string()));
2556 let ds = file.dataset("keep").unwrap();
2557 assert_eq!(ds.read_vlen_strings().unwrap(), vec!["a", "b"]);
2558 }
2559 std::fs::remove_file(&path).ok();
2560 }
2561
2562 #[test]
2563 fn delete_group_roundtrip() {
2564 let path = temp_path("delete_grp");
2565 {
2566 let file = H5File::create(&path).unwrap();
2567 let g1 = file.create_group("keep").unwrap();
2568 g1.write_vlen_strings("data", &["a"]).unwrap();
2569 let g2 = file.create_group("remove").unwrap();
2570 g2.write_vlen_strings("data", &["x"]).unwrap();
2571 file.delete_group("remove").unwrap();
2572 file.close().unwrap();
2573 }
2574 {
2575 let file = H5File::open(&path).unwrap();
2576 let names = file.dataset_names();
2577 assert!(names.contains(&"keep/data".to_string()));
2578 assert!(!names.contains(&"remove/data".to_string()));
2579 }
2580 std::fs::remove_file(&path).ok();
2581 }
2582
2583 #[test]
2584 fn open_rw_delete_recreate_group() {
2585 let path = temp_path("rw_delete_recreate");
2586 {
2588 let file = H5File::create(&path).unwrap();
2589 let n = file.create_group("nodes").unwrap();
2590 n.write_vlen_strings("id", &["a", "b", "c"]).unwrap();
2591 let e = file.create_group("edges").unwrap();
2592 e.write_vlen_strings("src", &["x", "y"]).unwrap();
2593 file.close().unwrap();
2594 }
2595 {
2597 let file = H5File::open_rw(&path).unwrap();
2598 file.delete_group("nodes").unwrap();
2599 let n = file.create_group("nodes").unwrap();
2600 n.write_vlen_strings("id", &["new1", "new2"]).unwrap();
2601 file.close().unwrap();
2602 }
2603 {
2605 let file = H5File::open(&path).unwrap();
2606 let ds = file.dataset("nodes/id").unwrap();
2607 let s = ds.read_vlen_strings().unwrap();
2608 assert_eq!(s, vec!["new1", "new2"]);
2609 let ds = file.dataset("edges/src").unwrap();
2611 let s = ds.read_vlen_strings().unwrap();
2612 assert_eq!(s, vec!["x", "y"]);
2613 }
2614 std::fs::remove_file(&path).ok();
2615 }
2616
2617 #[test]
2618 fn delete_and_recreate_group() {
2619 let path = temp_path("delete_recreate");
2620 {
2621 let file = H5File::create(&path).unwrap();
2622 let g = file.create_group("nodes").unwrap();
2623 g.write_vlen_strings("id", &["old1", "old2"]).unwrap();
2624 file.delete_group("nodes").unwrap();
2625 let g = file.create_group("nodes").unwrap();
2626 g.write_vlen_strings("id", &["new1", "new2", "new3"])
2627 .unwrap();
2628 file.close().unwrap();
2629 }
2630 {
2631 let file = H5File::open(&path).unwrap();
2632 let ds = file.dataset("nodes/id").unwrap();
2633 let strings = ds.read_vlen_strings().unwrap();
2634 assert_eq!(strings, vec!["new1", "new2", "new3"]);
2635 }
2636 std::fs::remove_file(&path).ok();
2637 }
2638
2639 #[test]
2640 #[cfg(feature = "deflate")]
2641 fn vlen_string_compressed_large_roundtrip() {
2642 use crate::format::messages::filter::FilterPipeline;
2643 let path = temp_path("vlen_large");
2644 let input: Vec<String> = (0..7189)
2646 .map(|i| format!("node-{:08x}-{}", i, "a".repeat(20 + (i % 30))))
2647 .collect();
2648 let input_refs: Vec<&str> = input.iter().map(|s| s.as_str()).collect();
2649 {
2650 let file = H5File::create(&path).unwrap();
2651 file.create_group("nodes").unwrap();
2652 file.write_vlen_strings_compressed(
2653 "nodes/id",
2654 &input_refs,
2655 512,
2656 FilterPipeline::deflate(6),
2657 )
2658 .unwrap();
2659 file.close().unwrap();
2660 }
2661 {
2663 let file = H5File::open(&path).unwrap();
2664 let ds = file.dataset("nodes/id").unwrap();
2665 let strings = ds.read_vlen_strings().unwrap();
2666 assert_eq!(strings.len(), 7189);
2667 assert_eq!(strings[0], input[0]);
2668 assert_eq!(strings[7188], input[7188]);
2669 }
2670 {
2672 let file = H5File::open_rw(&path).unwrap();
2673 file.set_attr_string("version", "1.0").unwrap();
2674 file.close().unwrap();
2675 }
2676 {
2677 let file = H5File::open(&path).unwrap();
2678 let ds = file.dataset("nodes/id").unwrap();
2679 let strings = ds.read_vlen_strings().unwrap();
2680 assert_eq!(strings.len(), 7189);
2681 assert_eq!(strings[0], input[0]);
2682 }
2683 std::fs::remove_file(&path).ok();
2684 }
2685
2686 #[test]
2687 fn vlen_string_write_read() {
2688 let path = temp_path("vlen_wr");
2689 {
2690 let file = H5File::create(&path).unwrap();
2691 file.write_vlen_strings("names", &["alice", "bob", "charlie"])
2692 .unwrap();
2693 file.close().unwrap();
2694 }
2695 {
2696 let file = H5File::open(&path).unwrap();
2697 let ds = file.dataset("names").unwrap();
2698 let strings = ds.read_vlen_strings().unwrap();
2699 assert_eq!(strings, vec!["alice", "bob", "charlie"]);
2700 }
2701 std::fs::remove_file(&path).ok();
2702 }
2703
2704 #[test]
2709 fn vlen_string_writers_declare_their_character_set() {
2710 use crate::format::messages::datatype::DatatypeMessage;
2711
2712 let path = temp_path("vlen_cset");
2713 let file = H5File::create(&path).unwrap();
2714 file.write_vlen_strings_ascii("ascii", &["alpha", "b", ""])
2715 .unwrap();
2716 file.write_vlen_strings("utf8", &["été", "日本"]).unwrap();
2717 let err = file
2718 .write_vlen_strings_ascii("rejected", &["ok", "안녕"])
2719 .err()
2720 .expect("a non-ASCII string was accepted under an ASCII datatype")
2721 .to_string();
2722 assert!(
2723 err.contains("string 1") && err.contains("is not ASCII"),
2724 "got: {err}"
2725 );
2726 file.close().unwrap();
2727
2728 let file = H5File::open(&path).unwrap();
2729 let ascii = file.dataset("ascii").unwrap();
2730 assert_eq!(
2731 ascii.datatype().unwrap(),
2732 DatatypeMessage::VarLenString {
2733 padding: 0,
2734 charset: 0,
2735 }
2736 );
2737 assert_eq!(ascii.read_strings().unwrap(), vec!["alpha", "b", ""]);
2738 let utf8 = file.dataset("utf8").unwrap();
2739 assert_eq!(
2740 utf8.datatype().unwrap(),
2741 DatatypeMessage::VarLenString {
2742 padding: 0,
2743 charset: 1,
2744 }
2745 );
2746 assert_eq!(utf8.read_strings().unwrap(), vec!["été", "日本"]);
2747 assert!(file.dataset("rejected").is_err());
2749 std::fs::remove_file(&path).ok();
2750 }
2751
2752 #[test]
2755 fn group_vlen_string_writer_declares_ascii() {
2756 use crate::format::messages::datatype::DatatypeMessage;
2757
2758 let path = temp_path("vlen_cset_group");
2759 let file = H5File::create(&path).unwrap();
2760 let g = file.create_group("entry").unwrap();
2761 g.write_vlen_strings_ascii("notes", &["alpha", "b"])
2762 .unwrap();
2763 let err = g
2764 .write_vlen_strings_ascii("rejected", &["안녕"])
2765 .err()
2766 .expect("a non-ASCII string was accepted under an ASCII datatype")
2767 .to_string();
2768 assert!(err.contains("is not ASCII"), "got: {err}");
2769 file.close().unwrap();
2770
2771 let file = H5File::open(&path).unwrap();
2772 let ds = file.dataset("entry/notes").unwrap();
2773 assert_eq!(
2774 ds.datatype().unwrap(),
2775 DatatypeMessage::VarLenString {
2776 padding: 0,
2777 charset: 0,
2778 }
2779 );
2780 assert_eq!(ds.read_strings().unwrap(), vec!["alpha", "b"]);
2781 std::fs::remove_file(&path).ok();
2782 }
2783
2784 #[test]
2785 fn vlen_bytes_write_read() {
2786 let path = temp_path("vlen_bytes_wr");
2787 let items: [&[u8]; 4] = [b"abc", b"", &[0u8, 1, 2, 255], b"hi"];
2788 {
2789 let file = H5File::create(&path).unwrap();
2790 file.write_vlen_bytes("blobs", &items).unwrap();
2791 file.close().unwrap();
2792 }
2793 {
2794 let file = H5File::open(&path).unwrap();
2795 let ds = file.dataset("blobs").unwrap();
2796 let got = ds.read_vlen_bytes().unwrap();
2797 let expected: Vec<Vec<u8>> = items.iter().map(|s| s.to_vec()).collect();
2798 assert_eq!(got, expected);
2799 }
2800 std::fs::remove_file(&path).ok();
2801 }
2802
2803 #[test]
2807 fn vlen_numeric_write_read() {
2808 use crate::format::global_heap::decode_vlen_reference;
2809 use crate::format::messages::datatype::DatatypeMessage;
2810
2811 let path = temp_path("vlen_numeric_wr");
2812 let a: &[i32] = &[1, 2, 3];
2813 let b: &[i32] = &[];
2814 let c: &[i32] = &[-7];
2815 {
2816 let file = H5File::create(&path).unwrap();
2817 file.write_vlen_numeric("data", &[a, b, c]).unwrap();
2818 let f64s: &[f64] = &[1.5, -2.5];
2819 file.write_vlen_numeric("wide", &[f64s]).unwrap();
2820 file.close().unwrap();
2821 }
2822 let file = H5File::open(&path).unwrap();
2823 let ds = file.dataset("data").unwrap();
2824 assert_eq!(
2825 ds.datatype().unwrap(),
2826 DatatypeMessage::VarLenSequence {
2827 base: Box::new(DatatypeMessage::i32_type()),
2828 }
2829 );
2830 let decoded: Vec<Vec<i32>> = ds
2831 .read_vlen_bytes()
2832 .unwrap()
2833 .iter()
2834 .map(|item| {
2835 item.as_chunks::<4>()
2836 .0
2837 .iter()
2838 .map(|w| i32::from_le_bytes(*w))
2839 .collect()
2840 })
2841 .collect();
2842 assert_eq!(decoded, vec![a.to_vec(), b.to_vec(), c.to_vec()]);
2843
2844 let ctx = crate::format::FormatContext::default_v3();
2846 let raw = ds.read_raw_bytes().unwrap();
2847 let (seq_len, _, _) = decode_vlen_reference(&raw, &ctx).unwrap();
2848 assert_eq!(seq_len, 3);
2849
2850 let wide = file.dataset("wide").unwrap();
2851 assert_eq!(
2852 wide.datatype().unwrap(),
2853 DatatypeMessage::VarLenSequence {
2854 base: Box::new(DatatypeMessage::f64_type()),
2855 }
2856 );
2857 let (seq_len, _, _) = decode_vlen_reference(&wide.read_raw_bytes().unwrap(), &ctx).unwrap();
2858 assert_eq!(seq_len, 2);
2859 drop(file);
2860 std::fs::remove_file(&path).ok();
2861 }
2862
2863 #[test]
2866 fn vlen_bytes_is_the_u8_case_of_vlen_numeric() {
2867 use crate::format::messages::datatype::DatatypeMessage;
2868
2869 let path = temp_path("vlen_bytes_u8");
2870 let items: [&[u8]; 2] = [b"abc", b""];
2871 {
2872 let file = H5File::create(&path).unwrap();
2873 file.write_vlen_bytes("blobs", &items).unwrap();
2874 file.close().unwrap();
2875 }
2876 let file = H5File::open(&path).unwrap();
2877 let ds = file.dataset("blobs").unwrap();
2878 assert_eq!(ds.datatype().unwrap(), DatatypeMessage::vlen_bytes());
2879 let ctx = crate::format::FormatContext::default_v3();
2880 let (seq_len, _, _) =
2881 crate::format::global_heap::decode_vlen_reference(&ds.read_raw_bytes().unwrap(), &ctx)
2882 .unwrap();
2883 assert_eq!(seq_len, 3);
2884 drop(file);
2885 std::fs::remove_file(&path).ok();
2886 }
2887
2888 #[test]
2889 fn vlen_bytes_in_group_with_attribute() {
2890 use crate::types::VarLenUnicode;
2891 let path = temp_path("vlen_bytes_grp");
2892 let items: [&[u8]; 2] = [&[1u8, 2, 3], &[9u8, 8, 7, 6]];
2893 {
2894 let file = H5File::create(&path).unwrap();
2895 let grp = file.root_group().create_group("payloads").unwrap();
2896 let ds = grp.write_vlen_bytes("frames", &items).unwrap();
2897 ds.new_attr::<VarLenUnicode>()
2898 .shape(())
2899 .create("codec")
2900 .unwrap()
2901 .write_string("raw")
2902 .unwrap();
2903 file.close().unwrap();
2904 }
2905 {
2906 let file = H5File::open(&path).unwrap();
2907 let ds = file.dataset("payloads/frames").unwrap();
2908 let got = ds.read_vlen_bytes().unwrap();
2909 let expected: Vec<Vec<u8>> = items.iter().map(|s| s.to_vec()).collect();
2910 assert_eq!(got, expected);
2911 }
2912 std::fs::remove_file(&path).ok();
2913 }
2914
2915 #[test]
2916 fn vlen_dataset_returns_handle_for_attributes() {
2917 use crate::types::VarLenUnicode;
2918 let path = temp_path("vlen_attr");
2919 {
2920 let file = H5File::create(&path).unwrap();
2921 let grp = file.root_group().create_group("ch").unwrap();
2922 let ds = grp
2925 .write_vlen_strings("labels", &["a", "bb", "ccc"])
2926 .unwrap();
2927 ds.new_attr::<VarLenUnicode>()
2928 .shape(())
2929 .create("unit")
2930 .unwrap()
2931 .write_string("volt")
2932 .unwrap();
2933 let ds2 = grp.dataset_writer("labels").unwrap();
2935 ds2.new_attr::<VarLenUnicode>()
2936 .shape(())
2937 .create("desc")
2938 .unwrap()
2939 .write_string("channel labels")
2940 .unwrap();
2941 file.close().unwrap();
2942 }
2943 {
2944 let file = H5File::open(&path).unwrap();
2945 let ds = file.dataset("ch/labels").unwrap();
2946 assert_eq!(ds.read_vlen_strings().unwrap(), vec!["a", "bb", "ccc"]);
2947 assert_eq!(ds.attr("unit").unwrap().read_string().unwrap(), "volt");
2948 assert_eq!(
2949 ds.attr("desc").unwrap().read_string().unwrap(),
2950 "channel labels"
2951 );
2952 }
2953 std::fs::remove_file(&path).ok();
2954 }
2955
2956 #[test]
2957 #[cfg(feature = "deflate")]
2958 fn vlen_string_deflate_roundtrip() {
2959 use crate::format::messages::filter::FilterPipeline;
2960 let path = temp_path("vlen_deflate");
2961 let input: Vec<&str> = (0..100)
2962 .map(|i| match i % 3 {
2963 0 => "hello world",
2964 1 => "compressed vlen string test",
2965 _ => "rust-hdf5",
2966 })
2967 .collect();
2968 {
2969 let file = H5File::create(&path).unwrap();
2970 file.write_vlen_strings_compressed("texts", &input, 16, FilterPipeline::deflate(6))
2971 .unwrap();
2972 file.close().unwrap();
2973 }
2974 {
2975 let file = H5File::open(&path).unwrap();
2976 let ds = file.dataset("texts").unwrap();
2977 let strings = ds.read_vlen_strings().unwrap();
2978 assert_eq!(strings.len(), 100);
2979 for (i, s) in strings.iter().enumerate() {
2980 assert_eq!(s, input[i]);
2981 }
2982 }
2983 std::fs::remove_file(&path).ok();
2984 }
2985
2986 #[test]
2987 #[cfg(feature = "zstd")]
2988 fn vlen_string_zstd_roundtrip() {
2989 use crate::format::messages::filter::FilterPipeline;
2990 let path = temp_path("vlen_zstd");
2991 let input: Vec<&str> = (0..200)
2992 .map(|i| match i % 4 {
2993 0 => "zstandard compression test",
2994 1 => "variable length string",
2995 2 => "rust-hdf5 chunked storage",
2996 _ => "hello zstd world",
2997 })
2998 .collect();
2999 {
3000 let file = H5File::create(&path).unwrap();
3001 file.write_vlen_strings_compressed("data", &input, 32, FilterPipeline::zstd(3))
3002 .unwrap();
3003 file.close().unwrap();
3004 }
3005 {
3006 let file = H5File::open(&path).unwrap();
3007 let ds = file.dataset("data").unwrap();
3008 let strings = ds.read_vlen_strings().unwrap();
3009 assert_eq!(strings.len(), 200);
3010 for (i, s) in strings.iter().enumerate() {
3011 assert_eq!(s, input[i]);
3012 }
3013 }
3014 std::fs::remove_file(&path).ok();
3015 }
3016
3017 #[test]
3018 #[cfg(feature = "deflate")]
3019 fn shuffle_deflate_roundtrip() {
3020 let path = temp_path("shuf_defl");
3021 {
3022 let file = H5File::create(&path).unwrap();
3023 let ds = file
3024 .new_dataset::<f64>()
3025 .shape([0usize, 4])
3026 .chunk(&[1, 4])
3027 .max_shape(&[None, Some(4)])
3028 .shuffle_deflate(6)
3029 .create("data")
3030 .unwrap();
3031 for frame in 0..20u64 {
3032 let vals: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
3033 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3034 ds.write_chunk(frame as usize, &raw).unwrap();
3035 }
3036 ds.extend(&[20, 4]).unwrap();
3037 file.close().unwrap();
3038 }
3039 {
3040 let file = H5File::open(&path).unwrap();
3041 let ds = file.dataset("data").unwrap();
3042 assert_eq!(ds.shape(), vec![20, 4]);
3043 let data = ds.read_raw::<f64>().unwrap();
3044 assert_eq!(data.len(), 80);
3045 for (i, val) in data.iter().enumerate() {
3046 assert!((val - i as f64).abs() < 1e-10);
3047 }
3048 }
3049 std::fs::remove_file(&path).ok();
3050 }
3051
3052 #[test]
3053 fn file_level_attributes() {
3054 let path = temp_path("file_attr");
3055 {
3056 let file = H5File::create(&path).unwrap();
3057 file.set_attr_string("title", "Test File").unwrap();
3058 file.set_attr_numeric("version", &42i32).unwrap();
3059 let ds = file
3060 .new_dataset::<u8>()
3061 .shape([1usize])
3062 .create("dummy")
3063 .unwrap();
3064 ds.write_raw(&[0u8]).unwrap();
3065 file.close().unwrap();
3066 }
3067 {
3068 let file = H5File::open(&path).unwrap();
3069 assert!(file.dataset_names().contains(&"dummy".to_string()));
3070
3071 let names = file.attr_names().unwrap();
3073 assert!(names.contains(&"title".to_string()));
3074
3075 let title = file.attr_string("title").unwrap();
3076 assert_eq!(title, "Test File");
3077 }
3078 std::fs::remove_file(&path).ok();
3079 }
3080
3081 #[test]
3082 fn scalar_dataset_roundtrip() {
3083 let path = temp_path("scalar");
3084 {
3085 let file = H5File::create(&path).unwrap();
3086 let ds = file.new_dataset::<f64>().scalar().create("pi").unwrap();
3087 ds.write_raw(&[std::f64::consts::PI]).unwrap();
3088 file.close().unwrap();
3089 }
3090 {
3091 let file = H5File::open(&path).unwrap();
3092 let ds = file.dataset("pi").unwrap();
3093 assert_eq!(ds.shape(), Vec::<usize>::new());
3094 assert_eq!(ds.total_elements(), 1);
3095 let data = ds.read_raw::<f64>().unwrap();
3096 assert_eq!(data.len(), 1);
3097 assert!((data[0] - std::f64::consts::PI).abs() < 1e-15);
3098 }
3099 std::fs::remove_file(&path).ok();
3100 }
3101
3102 #[test]
3103 fn append_mode_extend_chunked() {
3104 let path = temp_path("append_extend");
3105
3106 {
3108 let file = H5File::create(&path).unwrap();
3109 let ds = file
3110 .new_dataset::<i32>()
3111 .shape([0usize, 3])
3112 .chunk(&[1, 3])
3113 .max_shape(&[None, Some(3)])
3114 .create("stream")
3115 .unwrap();
3116 for i in 0..5u64 {
3117 let vals: Vec<i32> = (0..3).map(|j| (i * 3 + j) as i32).collect();
3118 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3119 ds.write_chunk(i as usize, &raw).unwrap();
3120 }
3121 ds.extend(&[5, 3]).unwrap();
3122 file.close().unwrap();
3123 }
3124
3125 {
3127 let file = H5File::open_rw(&path).unwrap();
3128 let names = file.dataset_names();
3130 assert!(names.contains(&"stream".to_string()));
3131
3132 let mut inner = crate::file::borrow_inner_mut(&file.inner);
3134 if let crate::file::H5FileInner::Writer(writer) = &mut *inner {
3135 let ds_idx = writer.dataset_index("stream").unwrap();
3136 for i in 5..10u64 {
3137 let vals: Vec<i32> = (0..3).map(|j| (i * 3 + j) as i32).collect();
3138 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3139 writer.write_chunk(ds_idx, i, &raw).unwrap();
3140 }
3141 writer.extend_dataset(ds_idx, &[10, 3]).unwrap();
3142 }
3143 drop(inner);
3144 file.close().unwrap();
3145 }
3146
3147 {
3149 let file = H5File::open(&path).unwrap();
3150 let ds = file.dataset("stream").unwrap();
3151 assert_eq!(ds.shape(), vec![10, 3]);
3152 let data = ds.read_raw::<i32>().unwrap();
3153 assert_eq!(data.len(), 30);
3154 for (i, val) in data.iter().enumerate() {
3155 assert_eq!(*val, i as i32, "mismatch at {}", i);
3156 }
3157 }
3158
3159 std::fs::remove_file(&path).ok();
3160 }
3161
3162 #[test]
3163 fn group_hierarchy_roundtrip() {
3164 let path = temp_path("groups_rt");
3165
3166 {
3167 let file = H5File::create(&path).unwrap();
3168 let root = file.root_group();
3169
3170 let det = root.create_group("detector").unwrap();
3172 let raw = det.create_group("raw").unwrap();
3173
3174 let ds1 = det
3176 .new_dataset::<f32>()
3177 .shape([10usize])
3178 .create("temperature")
3179 .unwrap();
3180 ds1.write_raw(&[1.0f32; 10]).unwrap();
3181
3182 let ds2 = raw
3183 .new_dataset::<u16>()
3184 .shape([4usize, 4])
3185 .create("image")
3186 .unwrap();
3187 ds2.write_raw(&[42u16; 16]).unwrap();
3188
3189 let ds3 = file
3191 .new_dataset::<i32>()
3192 .shape([3usize])
3193 .create("version")
3194 .unwrap();
3195 ds3.write_raw(&[1i32, 0, 0]).unwrap();
3196
3197 file.close().unwrap();
3198 }
3199
3200 {
3201 let file = H5File::open(&path).unwrap();
3202 let names = file.dataset_names();
3203 assert!(names.contains(&"version".to_string()));
3204 assert!(names.contains(&"detector/temperature".to_string()));
3205 assert!(names.contains(&"detector/raw/image".to_string()));
3206
3207 let ds = file.dataset("version").unwrap();
3209 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![1, 0, 0]);
3210
3211 let ds = file.dataset("detector/temperature").unwrap();
3212 assert_eq!(ds.read_raw::<f32>().unwrap(), vec![1.0f32; 10]);
3213
3214 let ds = file.dataset("detector/raw/image").unwrap();
3215 assert_eq!(ds.shape(), vec![4, 4]);
3216 assert_eq!(ds.read_raw::<u16>().unwrap(), vec![42u16; 16]);
3217
3218 let root = file.root_group();
3220 let group_names = root.group_names().unwrap();
3221 assert!(group_names.contains(&"detector".to_string()));
3222 }
3223
3224 std::fs::remove_file(&path).ok();
3225 }
3226
3227 #[test]
3228 fn nested_groups_via_file_create_group() {
3229 let path = temp_path("file_create_group");
3230
3231 {
3232 let file = H5File::create(&path).unwrap();
3233
3234 let grp = file.create_group("sensors").unwrap();
3236 let sub = grp.create_group("accel").unwrap();
3237
3238 let ds = sub
3239 .new_dataset::<f64>()
3240 .shape([3usize])
3241 .create("xyz")
3242 .unwrap();
3243 ds.write_raw(&[1.0f64, 2.0, 3.0]).unwrap();
3244
3245 file.close().unwrap();
3246 }
3247
3248 {
3249 let file = H5File::open(&path).unwrap();
3250 let names = file.dataset_names();
3251 assert!(names.contains(&"sensors/accel/xyz".to_string()));
3252
3253 let ds = file.dataset("sensors/accel/xyz").unwrap();
3254 assert_eq!(ds.read_raw::<f64>().unwrap(), vec![1.0, 2.0, 3.0]);
3255
3256 let root = file.root_group();
3258 let sensors = root.group("sensors").unwrap();
3259 assert_eq!(sensors.name(), "/sensors");
3260
3261 let accel = sensors.group("accel").unwrap();
3262 assert_eq!(accel.name(), "/sensors/accel");
3263
3264 let top_groups = root.group_names().unwrap();
3266 assert!(top_groups.contains(&"sensors".to_string()));
3267
3268 let sub_groups = sensors.group_names().unwrap();
3270 assert!(sub_groups.contains(&"accel".to_string()));
3271 }
3272
3273 std::fs::remove_file(&path).ok();
3274 }
3275}
3276
3277#[cfg(test)]
3278mod h5py_compat_tests {
3279 use super::*;
3280
3281 fn temp_path(name: &str) -> std::path::PathBuf {
3282 super::unique_test_path(name)
3283 }
3284
3285 #[test]
3287 #[cfg(feature = "deflate")]
3288 fn h5dump_validates_our_files() {
3289 let h5dump = std::process::Command::new("h5dump")
3291 .arg("--version")
3292 .output();
3293 if h5dump.is_err() {
3294 eprintln!("skipping: h5dump not found");
3295 return;
3296 }
3297
3298 let path = temp_path("h5dump_validate");
3299
3300 {
3302 let file = H5File::create(&path).unwrap();
3303
3304 let ds = file
3306 .new_dataset::<f64>()
3307 .shape([3usize, 4])
3308 .create("matrix")
3309 .unwrap();
3310 let data: Vec<f64> = (0..12).map(|i| i as f64).collect();
3311 ds.write_raw(&data).unwrap();
3312
3313 let ds2 = file
3315 .new_dataset::<i32>()
3316 .shape([0usize, 2])
3317 .chunk(&[1, 2])
3318 .max_shape(&[None, Some(2)])
3319 .deflate(6)
3320 .create("stream")
3321 .unwrap();
3322 for i in 0..5u64 {
3323 let vals: Vec<i32> = vec![i as i32 * 2, i as i32 * 2 + 1];
3324 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3325 ds2.write_chunk(i as usize, &raw).unwrap();
3326 }
3327 ds2.extend(&[5, 2]).unwrap();
3328
3329 let grp = file.create_group("meta").unwrap();
3331 let ds3 = grp
3332 .new_dataset::<u8>()
3333 .shape([4usize])
3334 .create("flags")
3335 .unwrap();
3336 ds3.write_raw(&[1u8, 0, 1, 0]).unwrap();
3337
3338 use crate::types::VarLenUnicode;
3340 let attr = ds
3341 .new_attr::<VarLenUnicode>()
3342 .shape(())
3343 .create("units")
3344 .unwrap();
3345 attr.write_string("meters").unwrap();
3346
3347 file.close().unwrap();
3348 }
3349
3350 let output = std::process::Command::new("h5dump")
3352 .arg("-H") .arg(path.to_str().unwrap())
3354 .output()
3355 .unwrap();
3356
3357 assert!(
3358 output.status.success(),
3359 "h5dump failed:\nstdout: {}\nstderr: {}",
3360 String::from_utf8_lossy(&output.stdout),
3361 String::from_utf8_lossy(&output.stderr),
3362 );
3363
3364 let output2 = std::process::Command::new("h5dump")
3366 .arg(path.to_str().unwrap())
3367 .output()
3368 .unwrap();
3369
3370 assert!(
3371 output2.status.success(),
3372 "h5dump (full) failed:\nstderr: {}",
3373 String::from_utf8_lossy(&output2.stderr),
3374 );
3375
3376 std::fs::remove_file(&path).ok();
3377 }
3378
3379 #[test]
3380 fn read_h5py_generated_file() {
3381 let path = "/tmp/test_h5py_default.h5";
3382 if !std::path::Path::new(path).exists() {
3383 eprintln!("skipping: h5py test file not found");
3384 return;
3385 }
3386 let file = H5File::open(path).unwrap();
3387
3388 let ds = file.dataset("data").unwrap();
3389 assert_eq!(ds.shape(), vec![4, 5]);
3390 let data = ds.read_raw::<f64>().unwrap();
3391 assert_eq!(data.len(), 20);
3392 assert!((data[0]).abs() < 1e-10);
3393 assert!((data[19] - 19.0).abs() < 1e-10);
3394
3395 let ds2 = file.dataset("images").unwrap();
3396 assert_eq!(ds2.shape(), vec![3, 64, 64]);
3397 let images = ds2.read_raw::<u16>().unwrap();
3398 assert_eq!(images.len(), 3 * 64 * 64);
3399 }
3400
3401 #[test]
3406 fn open_rw_refuses_every_create_only_option() {
3407 let path = temp_path("open_rw_refuses");
3408 H5File::create(&path).unwrap().close().unwrap();
3409
3410 let err = H5File::options()
3411 .track_order(true)
3412 .open_rw(&path)
3413 .err()
3414 .unwrap();
3415 assert!(err.to_string().contains("track_order"), "{err}");
3416
3417 let err = H5File::options()
3418 .libver(LibverBound::V110)
3419 .open_rw(&path)
3420 .err()
3421 .unwrap();
3422 assert!(err.to_string().contains("libver"), "{err}");
3423
3424 for bound in [
3429 LibverBound::Earliest,
3430 LibverBound::V18,
3431 LibverBound::V112,
3432 LibverBound::V114,
3433 LibverBound::V200,
3434 ] {
3435 let err = H5File::options()
3436 .libver(bound)
3437 .open_rw(&path)
3438 .err()
3439 .unwrap();
3440 assert!(err.to_string().contains("libver"), "{bound:?}: {err}");
3441 }
3442
3443 let err = H5File::options()
3444 .userblock(512)
3445 .open_rw(&path)
3446 .err()
3447 .unwrap();
3448 assert!(err.to_string().contains("userblock"), "{err}");
3449
3450 let types = crate::format::sohm::type_flag(crate::format::messages::MSG_DATATYPE).unwrap();
3451 let err = H5File::options()
3452 .shared_messages(&[(types, 0)], 50, 40)
3453 .open_rw(&path)
3454 .err()
3455 .unwrap();
3456 assert!(err.to_string().contains("shared_messages"), "{err}");
3457
3458 H5File::options().open_rw(&path).unwrap().close().unwrap();
3460
3461 std::fs::remove_file(&path).ok();
3462 }
3463
3464 #[test]
3467 fn open_refuses_a_create_only_option() {
3468 let path = temp_path("open_refuses");
3469 H5File::create(&path).unwrap().close().unwrap();
3470
3471 let err = H5File::options()
3472 .track_order(true)
3473 .open(&path)
3474 .err()
3475 .unwrap();
3476 assert!(err.to_string().contains("track_order"), "{err}");
3477
3478 H5File::options().open(&path).unwrap().close().unwrap();
3479
3480 std::fs::remove_file(&path).ok();
3481 }
3482}