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(feature = "threadsafe")]
89pub(crate) fn borrow_inner(inner: &SharedInner) -> std::sync::RwLockReadGuard<'_, H5FileInner> {
90 inner.read().unwrap()
91}
92
93#[cfg(feature = "threadsafe")]
97pub(crate) fn borrow_inner_mut(
98 inner: &SharedInner,
99) -> std::sync::RwLockWriteGuard<'_, H5FileInner> {
100 inner.write().unwrap()
101}
102
103#[cfg(feature = "threadsafe")]
106pub(crate) fn try_borrow_inner_mut(
107 inner: &SharedInner,
108) -> Option<std::sync::RwLockWriteGuard<'_, H5FileInner>> {
109 inner.write().ok()
110}
111
112#[cfg(feature = "threadsafe")]
113pub(crate) fn clone_inner(inner: &SharedInner) -> SharedInner {
114 std::sync::Arc::clone(inner)
115}
116
117#[cfg(feature = "threadsafe")]
118pub(crate) fn new_shared(inner: H5FileInner) -> SharedInner {
119 std::sync::Arc::new(std::sync::RwLock::new(inner))
120}
121
122pub(crate) enum H5FileInner {
128 Writer(Box<Hdf5Writer>),
133 Reader(Box<Hdf5Reader>),
134 Closed,
136}
137
138pub struct H5File {
144 pub(crate) inner: SharedInner,
145}
146
147impl H5File {
148 pub fn create<P: AsRef<Path>>(path: P) -> Result<Self> {
150 let writer = Hdf5Writer::create(path.as_ref())?;
151 Ok(Self {
152 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
153 })
154 }
155
156 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
158 let reader = Hdf5Reader::open(path.as_ref())?;
159 Ok(Self {
160 inner: new_shared(H5FileInner::Reader(Box::new(reader))),
161 })
162 }
163
164 pub fn open_rw<P: AsRef<Path>>(path: P) -> Result<Self> {
178 let writer = Hdf5Writer::open_append(path.as_ref())?;
179 Ok(Self {
180 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
181 })
182 }
183
184 pub fn options() -> H5FileOptions {
198 H5FileOptions::default()
199 }
200
201 pub fn set_libver_latest(&self, latest: bool) -> Result<()> {
228 let mut inner = borrow_inner_mut(&self.inner);
229 match &mut *inner {
230 H5FileInner::Writer(writer) => {
231 writer.set_libver_latest(latest)?;
232 Ok(())
233 }
234 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
235 }
236 }
237
238 pub fn set_libver_bound(&self, libver: LibverBound) -> Result<()> {
262 let mut inner = borrow_inner_mut(&self.inner);
263 match &mut *inner {
264 H5FileInner::Writer(writer) => {
265 writer.set_libver_bound(libver)?;
266 Ok(())
267 }
268 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
269 }
270 }
271
272 pub fn set_track_order(&self, track: bool) -> Result<()> {
287 let mut inner = borrow_inner_mut(&self.inner);
288 match &mut *inner {
289 H5FileInner::Writer(writer) => {
290 writer.set_track_order(track);
291 Ok(())
292 }
293 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
294 }
295 }
296
297 pub fn set_track_times(&self, track: bool) -> Result<()> {
310 let mut inner = borrow_inner_mut(&self.inner);
311 match &mut *inner {
312 H5FileInner::Writer(writer) => {
313 writer.set_track_times(track);
314 Ok(())
315 }
316 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
317 }
318 }
319
320 pub fn root_group(&self) -> H5Group {
324 H5Group::new(clone_inner(&self.inner), "/".to_string())
325 }
326
327 pub fn create_group(&self, name: &str) -> Result<H5Group> {
335 self.root_group().create_group(name)
336 }
337
338 pub fn create_soft_link(&self, link_name: &str, target_path: &str) -> Result<()> {
349 self.root_group().create_soft_link(link_name, target_path)
350 }
351
352 pub fn create_external_link(
362 &self,
363 link_name: &str,
364 target_file: &str,
365 target_path: &str,
366 ) -> Result<()> {
367 self.root_group()
368 .create_external_link(link_name, target_file, target_path)
369 }
370
371 pub fn commit_datatype(
382 &self,
383 name: &str,
384 datatype: crate::format::messages::datatype::DatatypeMessage,
385 ) -> Result<()> {
386 self.root_group().commit_datatype(name, datatype)
387 }
388
389 pub fn new_dataset<T: H5Type>(&self) -> DatasetBuilder<T> {
400 DatasetBuilder::new(clone_inner(&self.inner))
401 }
402
403 pub fn set_attr_string(&self, name: &str, value: &str) -> Result<()> {
408 let inner = borrow_inner(&self.inner);
409 match &*inner {
410 H5FileInner::Writer(writer) => {
411 writer.set_vlen_string_attribute(
412 crate::io::writer::AttrTarget::Root,
413 name,
414 value,
415 )?;
416 Ok(())
417 }
418 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
419 }
420 }
421
422 pub fn set_attr_numeric<T: crate::types::H5Type>(&self, name: &str, value: &T) -> Result<()> {
424 use crate::format::messages::attribute::AttributeMessage;
425 let es = T::element_size();
426 let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
427 let attr = AttributeMessage::scalar_numeric(name, T::hdf5_type(), raw.to_vec());
428 let inner = borrow_inner(&self.inner);
429 match &*inner {
430 H5FileInner::Writer(writer) => {
431 writer.add_root_attribute(attr)?;
432 Ok(())
433 }
434 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
435 }
436 }
437
438 pub fn set_attr_typed(
464 &self,
465 name: &str,
466 datatype: DatatypeMessage,
467 value: Vec<u8>,
468 ) -> Result<()> {
469 use crate::format::messages::attribute::AttributeMessage;
470 if value.len() as u64 != u64::from(datatype.element_size()) {
471 return Err(Hdf5Error::InvalidState(format!(
472 "attribute '{name}' was given {} bytes for a datatype whose element is {}",
473 value.len(),
474 datatype.element_size()
475 )));
476 }
477 let attr = AttributeMessage::scalar_numeric(name, datatype, value);
478 let inner = borrow_inner(&self.inner);
479 match &*inner {
480 H5FileInner::Writer(writer) => {
481 writer.add_root_attribute(attr)?;
482 Ok(())
483 }
484 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
485 }
486 }
487
488 pub fn set_attr_array_numeric<T: crate::types::H5Type>(
496 &self,
497 name: &str,
498 values: &[T],
499 ) -> Result<()> {
500 self.set_attr_array_numeric_nd(name, values, &[values.len()])
501 }
502
503 pub fn set_attr_array_numeric_nd<T: crate::types::H5Type>(
512 &self,
513 name: &str,
514 values: &[T],
515 shape: &[usize],
516 ) -> Result<()> {
517 use crate::format::messages::attribute::AttributeMessage;
518 let n: usize = shape.iter().product();
519 if values.len() != n {
520 return Err(Hdf5Error::InvalidState(format!(
521 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
522 values.len()
523 )));
524 }
525 let es = T::element_size();
526 let raw =
528 unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * es) };
529 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
530 let attr = AttributeMessage::array_numeric(name, T::hdf5_type(), &dims, raw.to_vec());
531 let mut inner = borrow_inner_mut(&self.inner);
532 match &mut *inner {
533 H5FileInner::Writer(writer) => {
534 writer.add_root_attribute(attr)?;
535 Ok(())
536 }
537 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
538 }
539 }
540
541 pub fn set_attr_string_array(&self, name: &str, values: &[&str]) -> Result<()> {
546 self.set_attr_string_array_nd(name, values, &[values.len()])
547 }
548
549 pub fn set_attr_string_array_nd(
559 &self,
560 name: &str,
561 values: &[&str],
562 shape: &[usize],
563 ) -> Result<()> {
564 let n: usize = shape.iter().product();
565 if values.len() != n {
566 return Err(Hdf5Error::InvalidState(format!(
567 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
568 values.len()
569 )));
570 }
571 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
572 let mut inner = borrow_inner_mut(&self.inner);
573 match &mut *inner {
574 H5FileInner::Writer(writer) => {
575 writer.set_vlen_string_array_attribute(
576 crate::io::writer::AttrTarget::Root,
577 name,
578 values,
579 &dims,
580 )?;
581 Ok(())
582 }
583 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
584 }
585 }
586
587 pub fn set_attr_object_reference(&self, name: &str, path: &str) -> Result<()> {
596 self.set_root_reference_attr(name, &[path], &[])
597 }
598
599 pub fn set_attr_object_references(&self, name: &str, paths: &[&str]) -> Result<()> {
603 self.set_root_reference_attr(name, paths, &[paths.len() as u64])
604 }
605
606 fn set_root_reference_attr(&self, name: &str, paths: &[&str], dims: &[u64]) -> Result<()> {
607 let inner = borrow_inner(&self.inner);
608 match &*inner {
609 H5FileInner::Writer(writer) => {
610 writer.set_object_reference_attribute(
611 crate::io::writer::AttrTarget::Root,
612 name,
613 paths,
614 dims,
615 )?;
616 Ok(())
617 }
618 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
619 }
620 }
621
622 pub fn attr_names(&self) -> Result<Vec<String>> {
624 let inner = borrow_inner(&self.inner);
625 match &*inner {
626 H5FileInner::Reader(reader) => Ok(reader.root_attr_names()?),
627 _ => Ok(vec![]),
628 }
629 }
630
631 pub fn attr_unreadable_reason(&self, name: &str) -> Result<Option<String>> {
634 let inner = borrow_inner(&self.inner);
635 match &*inner {
636 H5FileInner::Reader(reader) => {
637 Ok(reader.root_attr_unreadable_reason(name).map(str::to_string))
638 }
639 _ => Err(Hdf5Error::InvalidState("not in read mode".into())),
640 }
641 }
642
643 pub fn attrs_unreadable_reason(&self) -> Result<Option<String>> {
647 let inner = borrow_inner(&self.inner);
648 match &*inner {
649 H5FileInner::Reader(reader) => {
650 Ok(reader.root_attrs_unreadable_reason().map(str::to_string))
651 }
652 _ => Err(Hdf5Error::InvalidState("not in read mode".into())),
653 }
654 }
655
656 pub fn attr_string(&self, name: &str) -> Result<String> {
658 let mut inner = borrow_inner_mut(&self.inner);
659 match &mut *inner {
660 H5FileInner::Reader(reader) => {
661 let attr = reader.root_attr(name)?.clone();
662 Ok(reader.attr_string_value(&attr)?)
663 }
664 _ => Err(Hdf5Error::InvalidState("not in read mode".into())),
665 }
666 }
667
668 pub fn superblock_extension(&self) -> SuperblockExtension {
675 let inner = borrow_inner(&self.inner);
676 match &*inner {
677 H5FileInner::Reader(reader) => reader.superblock_extension().clone(),
678 _ => SuperblockExtension::default(),
679 }
680 }
681
682 pub fn object_message_storage(&self, path: &str) -> Result<Vec<(u8, MessageStorage)>> {
690 let mut inner = borrow_inner_mut(&self.inner);
691 match &mut *inner {
692 H5FileInner::Reader(reader) => Ok(reader.object_message_storage(path)?),
693 _ => Err(Hdf5Error::InvalidState(
694 "object_message_storage is only available in read mode".into(),
695 )),
696 }
697 }
698
699 pub fn object_message_flags(&self, path: &str) -> Result<Vec<(u8, u8)>> {
708 let mut inner = borrow_inner_mut(&self.inner);
709 match &mut *inner {
710 H5FileInner::Reader(reader) => Ok(reader.object_message_flags(path)?),
711 _ => Err(Hdf5Error::InvalidState(
712 "object_message_flags is only available in read mode".into(),
713 )),
714 }
715 }
716
717 pub fn object_datatype_versions(&self, path: &str) -> Result<Vec<DatatypeNodeVersion>> {
728 let mut inner = borrow_inner_mut(&self.inner);
729 match &mut *inner {
730 H5FileInner::Reader(reader) => Ok(reader.object_datatype_versions(path)?),
731 _ => Err(Hdf5Error::InvalidState(
732 "object_datatype_versions is only available in read mode".into(),
733 )),
734 }
735 }
736
737 pub fn object_records_times(&self, path: &str) -> Result<bool> {
747 let mut inner = borrow_inner_mut(&self.inner);
748 match &mut *inner {
749 H5FileInner::Reader(reader) => Ok(reader.object_records_times(path)?),
750 _ => Err(Hdf5Error::InvalidState(
751 "object_records_times is only available in read mode".into(),
752 )),
753 }
754 }
755
756 pub fn tracked_free_space(&self) -> Result<u64> {
765 let mut inner = borrow_inner_mut(&self.inner);
766 match &mut *inner {
767 H5FileInner::Reader(reader) => Ok(reader.tracked_free_space()?),
768 _ => Err(Hdf5Error::InvalidState(
769 "tracked_free_space is only available in read mode".into(),
770 )),
771 }
772 }
773
774 pub fn userblock_size(&self) -> u64 {
778 let inner = borrow_inner(&self.inner);
779 match &*inner {
780 H5FileInner::Reader(reader) => reader.userblock_size(),
781 H5FileInner::Writer(writer) => writer.userblock_size(),
782 H5FileInner::Closed => 0,
783 }
784 }
785
786 pub fn superblock_version(&self) -> Result<u8> {
790 let inner = borrow_inner(&self.inner);
791 match &*inner {
792 H5FileInner::Reader(reader) => Ok(reader.superblock_version()),
793 _ => Err(Hdf5Error::InvalidState(
794 "superblock_version is only available in read mode".into(),
795 )),
796 }
797 }
798
799 pub fn libver_bound(&self) -> Result<LibverBound> {
806 self.superblock_version()
807 .map(LibverBound::from_superblock_version)
808 }
809
810 pub fn is_writable(&self) -> bool {
812 let inner = borrow_inner(&self.inner);
813 matches!(&*inner, H5FileInner::Writer(_))
814 }
815
816 pub fn write_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<H5Dataset> {
826 self.write_vlen_strings_charset(name, strings, 1)
827 }
828
829 pub fn write_vlen_strings_ascii(&self, name: &str, strings: &[&str]) -> Result<H5Dataset> {
841 self.write_vlen_strings_charset(name, strings, 0)
842 }
843
844 fn write_vlen_strings_charset(
847 &self,
848 name: &str,
849 strings: &[&str],
850 charset: u8,
851 ) -> Result<H5Dataset> {
852 let inner = borrow_inner(&self.inner);
853 match &*inner {
854 H5FileInner::Writer(writer) => {
855 let idx = writer.create_vlen_string_dataset(name, strings, charset)?;
856 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
857 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
858 }
859 H5FileInner::Reader(_) => {
860 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
861 }
862 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
863 }
864 }
865
866 pub fn write_vlen_bytes(&self, name: &str, items: &[&[u8]]) -> Result<H5Dataset> {
875 self.write_vlen_numeric(name, items)
876 }
877
878 pub fn write_vlen_numeric<T: H5Type>(&self, name: &str, items: &[&[T]]) -> Result<H5Dataset> {
895 let images = crate::dataset::vlen_sequence_images(items)?;
896 let images: Vec<&[u8]> = images.iter().map(|c| c.as_ref()).collect();
897 let inner = borrow_inner(&self.inner);
898 match &*inner {
899 H5FileInner::Writer(writer) => {
900 let idx = writer.create_vlen_sequence_dataset(name, T::hdf5_type(), &images)?;
901 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
902 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
903 }
904 H5FileInner::Reader(_) => {
905 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
906 }
907 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
908 }
909 }
910
911 pub fn write_vlen_strings_compressed(
918 &self,
919 name: &str,
920 strings: &[&str],
921 chunk_size: usize,
922 pipeline: FilterPipeline,
923 ) -> Result<H5Dataset> {
924 let inner = borrow_inner(&self.inner);
925 match &*inner {
926 H5FileInner::Writer(writer) => {
927 let idx = writer
928 .create_vlen_string_dataset_compressed(name, strings, chunk_size, pipeline)?;
929 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
930 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
931 }
932 H5FileInner::Reader(_) => {
933 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
934 }
935 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
936 }
937 }
938
939 pub fn create_appendable_vlen_dataset(
944 &self,
945 name: &str,
946 chunk_size: usize,
947 pipeline: Option<FilterPipeline>,
948 ) -> Result<H5Dataset> {
949 let inner = borrow_inner(&self.inner);
950 match &*inner {
951 H5FileInner::Writer(writer) => {
952 let idx =
953 writer.create_appendable_vlen_string_dataset(name, chunk_size, pipeline)?;
954 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
955 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
956 }
957 H5FileInner::Reader(_) => {
958 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
959 }
960 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
961 }
962 }
963
964 pub fn append_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
966 let inner = borrow_inner(&self.inner);
967 match &*inner {
968 H5FileInner::Writer(writer) => {
969 let ds_index = writer.open_dataset_index(name)?;
970 writer.append_vlen_strings(ds_index, strings)?;
971 Ok(())
972 }
973 H5FileInner::Reader(_) => {
974 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
975 }
976 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
977 }
978 }
979
980 pub fn delete_dataset(&self, name: &str) -> Result<()> {
989 let inner = borrow_inner(&self.inner);
990 match &*inner {
991 H5FileInner::Writer(writer) => {
992 writer.delete_dataset(name)?;
993 Ok(())
994 }
995 _ => Err(Hdf5Error::InvalidState("cannot delete in read mode".into())),
996 }
997 }
998
999 pub fn delete_group(&self, name: &str) -> Result<()> {
1008 let inner = borrow_inner(&self.inner);
1009 match &*inner {
1010 H5FileInner::Writer(writer) => {
1011 writer.delete_group(name)?;
1012 Ok(())
1013 }
1014 _ => Err(Hdf5Error::InvalidState("cannot delete in read mode".into())),
1015 }
1016 }
1017
1018 pub fn dataset(&self, name: &str) -> Result<H5Dataset> {
1023 self.dataset_with(name, DatasetAccess::default())
1024 }
1025
1026 pub fn dataset_with(&self, name: &str, access: DatasetAccess) -> Result<H5Dataset> {
1060 access.validate()?;
1061 let mut inner = borrow_inner_mut(&self.inner);
1064 match &mut *inner {
1065 H5FileInner::Reader(reader) => {
1066 let (open, info) = reader.open_dataset_with(name, &access)?;
1070 let shape: Vec<usize> = info.dataspace.dims.iter().map(|&d| d as usize).collect();
1071 let element_size = info.datatype.element_size() as usize;
1072 Ok(H5Dataset::new_reader(
1073 clone_inner(&self.inner),
1074 name.to_string(),
1075 shape,
1076 element_size,
1077 open,
1078 ))
1079 }
1080 H5FileInner::Writer(_) => Err(Hdf5Error::InvalidState(
1081 "cannot open a dataset by name in write mode; use new_dataset() instead"
1082 .to_string(),
1083 )),
1084 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".to_string())),
1085 }
1086 }
1087
1088 pub fn dataset_writer(&self, name: &str) -> Result<H5Dataset> {
1102 self.dataset_writer_with(name, DatasetAccess::default())
1103 }
1104
1105 pub fn dataset_writer_with(&self, name: &str, access: DatasetAccess) -> Result<H5Dataset> {
1132 access.validate()?;
1133 let inner = borrow_inner(&self.inner);
1134 match &*inner {
1135 H5FileInner::Writer(writer) => {
1136 let index = writer.open_dataset_index(name)?;
1137 let parts = writer.dataset_handle_parts(index, &access)?;
1138 Ok(H5Dataset::new_writer(
1139 clone_inner(&self.inner),
1140 index,
1141 parts,
1142 ))
1143 }
1144 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
1145 "cannot open a dataset_writer in read mode; use dataset() instead".to_string(),
1146 )),
1147 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".to_string())),
1148 }
1149 }
1150
1151 pub fn dataset_names(&self) -> Vec<String> {
1157 let inner = borrow_inner(&self.inner);
1158 match &*inner {
1159 H5FileInner::Reader(reader) => reader
1160 .dataset_names()
1161 .iter()
1162 .map(|s| s.to_string())
1163 .collect(),
1164 H5FileInner::Writer(writer) => writer
1165 .dataset_names()
1166 .iter()
1167 .map(|s| s.to_string())
1168 .collect(),
1169 H5FileInner::Closed => Vec::new(),
1170 }
1171 }
1172
1173 pub fn named_datatype_names(&self) -> Vec<String> {
1180 let inner = borrow_inner(&self.inner);
1181 match &*inner {
1182 H5FileInner::Reader(reader) => reader
1183 .named_datatype_names()
1184 .iter()
1185 .map(|s| s.to_string())
1186 .collect(),
1187 H5FileInner::Writer(writer) => writer.committed_datatype_names(),
1188 H5FileInner::Closed => Vec::new(),
1189 }
1190 }
1191
1192 pub fn named_datatype(&self, path: &str) -> Result<crate::H5NamedDatatype> {
1203 let mut inner = borrow_inner_mut(&self.inner);
1206 match &mut *inner {
1207 H5FileInner::Reader(reader) => {
1208 reader.named_datatype_info(path)?;
1209 drop(inner);
1210 Ok(crate::H5NamedDatatype::new_reader(
1211 clone_inner(&self.inner),
1212 path.to_string(),
1213 ))
1214 }
1215 H5FileInner::Writer(_) => Err(Hdf5Error::InvalidState(
1216 "committed datatypes are readable only in read mode".to_string(),
1217 )),
1218 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".to_string())),
1219 }
1220 }
1221
1222 pub fn close(self) -> Result<()> {
1228 let old = {
1229 let mut inner = borrow_inner_mut(&self.inner);
1230 std::mem::replace(&mut *inner, H5FileInner::Closed)
1231 };
1232 match old {
1233 H5FileInner::Writer(writer) => {
1234 writer.close()?;
1235 Ok(())
1236 }
1237 H5FileInner::Reader(_) => Ok(()),
1238 H5FileInner::Closed => Ok(()),
1239 }
1240 }
1241
1242 pub fn close_no_sync(self) -> Result<()> {
1258 let old = {
1259 let mut inner = borrow_inner_mut(&self.inner);
1260 std::mem::replace(&mut *inner, H5FileInner::Closed)
1261 };
1262 match old {
1263 H5FileInner::Writer(writer) => {
1264 writer.close_no_sync()?;
1265 Ok(())
1266 }
1267 H5FileInner::Reader(_) => Ok(()),
1268 H5FileInner::Closed => Ok(()),
1269 }
1270 }
1271
1272 pub fn flush(&self) -> Result<()> {
1280 let mut inner = borrow_inner_mut(&self.inner);
1281 match &mut *inner {
1282 H5FileInner::Writer(writer) => Ok(writer.handle().flush()?),
1283 H5FileInner::Reader(_) => Ok(()),
1284 H5FileInner::Closed => Ok(()),
1285 }
1286 }
1287}
1288
1289#[derive(Debug, Default, Clone)]
1296pub struct H5FileOptions {
1297 locking: Option<FileLocking>,
1298 track_order: bool,
1299 track_times: bool,
1300 libver: Option<LibverBound>,
1301 userblock: u64,
1302 shared_messages: SharedMessageConfig,
1303 file_space: Option<FileSpaceConfig>,
1304 file_space_page_size: Option<u64>,
1305 elink_prefix: Option<String>,
1306}
1307
1308impl H5FileOptions {
1309 pub fn new() -> Self {
1311 Self::default()
1312 }
1313
1314 pub fn locking(mut self, policy: FileLocking) -> Self {
1317 self.locking = Some(policy);
1318 self
1319 }
1320
1321 pub fn no_locking(self) -> Self {
1324 self.locking(FileLocking::Disabled)
1325 }
1326
1327 pub fn best_effort_locking(self) -> Self {
1330 self.locking(FileLocking::BestEffort)
1331 }
1332
1333 pub fn elink_prefix(mut self, prefix: impl Into<String>) -> Self {
1364 self.elink_prefix = Some(prefix.into());
1365 self
1366 }
1367
1368 pub fn track_order(mut self, track: bool) -> Self {
1376 self.track_order = track;
1377 self
1378 }
1379
1380 pub fn track_times(mut self, track: bool) -> Self {
1400 self.track_times = track;
1401 self
1402 }
1403
1404 pub fn libver(mut self, libver: LibverBound) -> Self {
1447 self.libver = Some(libver);
1448 self
1449 }
1450
1451 pub fn userblock(mut self, size: u64) -> Self {
1474 self.userblock = size;
1475 self
1476 }
1477
1478 pub fn shared_messages(
1509 mut self,
1510 indexes: &[(u16, u32)],
1511 list_max: u16,
1512 btree_min: u16,
1513 ) -> Self {
1514 self.shared_messages = SharedMessageConfig::new(indexes, list_max, btree_min);
1515 self
1516 }
1517
1518 pub fn file_space(
1552 mut self,
1553 strategy: FileSpaceStrategy,
1554 persist: bool,
1555 threshold: u64,
1556 ) -> Self {
1557 self.file_space = Some(FileSpaceConfig::new(strategy, persist, threshold));
1558 self
1559 }
1560
1561 pub fn file_space_page_size(mut self, size: u64) -> Self {
1589 self.file_space_page_size = Some(size);
1590 self
1591 }
1592
1593 fn resolved_file_space(&self) -> FileSpaceConfig {
1602 let config = self.file_space.unwrap_or_default();
1603 match self.file_space_page_size {
1604 Some(size) => config.with_page_size(size),
1605 None => config,
1606 }
1607 }
1608
1609 fn resolved_locking(&self) -> FileLocking {
1610 match self.locking {
1611 Some(p) => p,
1612 None => FileLocking::from_env_or(FileLocking::default()),
1613 }
1614 }
1615
1616 fn refuse_create_only_options(&self) -> Result<()> {
1634 let mut offending = Vec::new();
1635 if self.track_order {
1636 offending.push("track_order");
1637 }
1638 if self.track_times {
1639 offending.push("track_times");
1640 }
1641 if self.libver.is_some() {
1642 offending.push("libver");
1643 }
1644 if self.userblock != 0 {
1645 offending.push("userblock");
1646 }
1647 if self.shared_messages != SharedMessageConfig::default() {
1648 offending.push("shared_messages");
1649 }
1650 if self.file_space.is_some() {
1651 offending.push("file_space");
1652 }
1653 if self.file_space_page_size.is_some() {
1654 offending.push("file_space_page_size");
1655 }
1656 if offending.is_empty() {
1657 Ok(())
1658 } else {
1659 Err(Hdf5Error::InvalidState(format!(
1660 "these options only take effect when creating a file, not when \
1661 opening an existing one: {}",
1662 offending.join(", ")
1663 )))
1664 }
1665 }
1666
1667 fn refuse_read_only_options(&self) -> Result<()> {
1675 if self.elink_prefix.is_none() {
1676 return Ok(());
1677 }
1678 Err(Hdf5Error::InvalidState(
1679 "these options only take effect when opening a file for reading: \
1680 elink_prefix"
1681 .to_string(),
1682 ))
1683 }
1684
1685 pub fn create<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1687 self.refuse_read_only_options()?;
1688 let writer = Hdf5Writer::create_with_options(
1689 path.as_ref(),
1690 crate::io::writer::FileCreateOptions {
1691 locking: self.resolved_locking(),
1692 track_order: self.track_order,
1693 track_times: self.track_times,
1694 libver: self.libver,
1695 userblock: self.userblock,
1696 shared_messages: self.shared_messages,
1697 file_space: self.resolved_file_space(),
1698 },
1699 )?;
1700 Ok(H5File {
1701 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
1702 })
1703 }
1704
1705 pub fn open<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1707 self.refuse_create_only_options()?;
1708 let mut reader = Hdf5Reader::open_with_locking(path.as_ref(), self.resolved_locking())?;
1709 reader.set_elink_prefix(self.elink_prefix);
1710 Ok(H5File {
1711 inner: new_shared(H5FileInner::Reader(Box::new(reader))),
1712 })
1713 }
1714
1715 pub fn open_rw<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1717 self.refuse_create_only_options()?;
1718 self.refuse_read_only_options()?;
1719 let writer = Hdf5Writer::open_append_with_locking(path.as_ref(), self.resolved_locking())?;
1720 Ok(H5File {
1721 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
1722 })
1723 }
1724}
1725
1726#[cfg(test)]
1727fn unique_test_path(name: &str) -> std::path::PathBuf {
1728 use std::sync::atomic::{AtomicU64, Ordering};
1733 static COUNTER: AtomicU64 = AtomicU64::new(0);
1734 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
1735 std::env::temp_dir().join(format!(
1736 "rust_hdf5_test_{}_{}_{}.h5",
1737 name,
1738 std::process::id(),
1739 n
1740 ))
1741}
1742
1743#[cfg(test)]
1744mod tests {
1745 use super::*;
1746 use std::path::PathBuf;
1747
1748 fn temp_path(name: &str) -> PathBuf {
1749 super::unique_test_path(name)
1750 }
1751
1752 #[test]
1753 fn create_and_close_empty() {
1754 let path = temp_path("create_empty");
1755 let file = H5File::create(&path).unwrap();
1756 file.close().unwrap();
1757
1758 let file = H5File::open(&path).unwrap();
1760 file.close().unwrap();
1761
1762 std::fs::remove_file(&path).ok();
1763 }
1764
1765 #[test]
1766 fn create_and_drop_empty() {
1767 let path = temp_path("drop_empty");
1768 {
1769 let _file = H5File::create(&path).unwrap();
1770 }
1772 let file = H5File::open(&path).unwrap();
1774 file.close().unwrap();
1775
1776 std::fs::remove_file(&path).ok();
1777 }
1778
1779 #[test]
1780 fn dataset_not_found() {
1781 let path = temp_path("ds_not_found");
1782 {
1783 let _file = H5File::create(&path).unwrap();
1784 }
1785 let file = H5File::open(&path).unwrap();
1786 let result = file.dataset("nonexistent");
1787 assert!(result.is_err());
1788
1789 std::fs::remove_file(&path).ok();
1790 }
1791
1792 #[test]
1793 fn write_and_read_roundtrip() {
1794 let path = temp_path("write_read_rt");
1795
1796 {
1798 let file = H5File::create(&path).unwrap();
1799 let ds = file
1800 .new_dataset::<u8>()
1801 .shape([4, 4])
1802 .create("data")
1803 .unwrap();
1804 ds.write_raw(&[0u8; 16]).unwrap();
1805 file.close().unwrap();
1806 }
1807
1808 {
1810 let file = H5File::open(&path).unwrap();
1811 let ds = file.dataset("data").unwrap();
1812 assert_eq!(ds.shape(), vec![4, 4]);
1813 let data = ds.read_raw::<u8>().unwrap();
1814 assert_eq!(data.len(), 16);
1815 assert!(data.iter().all(|&b| b == 0));
1816 file.close().unwrap();
1817 }
1818
1819 std::fs::remove_file(&path).ok();
1820 }
1821
1822 #[test]
1823 fn close_no_sync_produces_valid_readable_file() {
1824 let path = temp_path("close_no_sync_rt");
1825 let payload: Vec<u8> = (0u8..16).collect();
1826
1827 {
1829 let file = H5File::create(&path).unwrap();
1830 let ds = file
1831 .new_dataset::<u8>()
1832 .shape([4, 4])
1833 .create("data")
1834 .unwrap();
1835 ds.write_raw(&payload).unwrap();
1836 file.close_no_sync().unwrap();
1839 }
1840
1841 {
1844 let file = H5File::open(&path).unwrap();
1845 let ds = file.dataset("data").unwrap();
1846 assert_eq!(ds.shape(), vec![4, 4]);
1847 let data = ds.read_raw::<u8>().unwrap();
1848 assert_eq!(data, payload);
1849 file.close().unwrap();
1850 }
1851
1852 std::fs::remove_file(&path).ok();
1853 }
1854
1855 #[test]
1856 fn create_over_existing_file_truncates() {
1857 let path = temp_path("create_truncates");
1863
1864 {
1865 let file = H5File::create(&path).unwrap();
1866 let ds = file
1867 .new_dataset::<u8>()
1868 .shape([4, 4])
1869 .create("old_data")
1870 .unwrap();
1871 ds.write_raw(&[7u8; 16]).unwrap();
1872 file.close().unwrap();
1873 }
1874 assert!(std::fs::metadata(&path).unwrap().len() > 0);
1875
1876 {
1878 let file = H5File::create(&path).unwrap();
1879 file.close().unwrap();
1880 }
1881
1882 let file = H5File::open(&path).unwrap();
1884 assert!(file.dataset("old_data").is_err());
1885 file.close().unwrap();
1886
1887 std::fs::remove_file(&path).ok();
1888 }
1889
1890 #[test]
1891 fn close_no_sync_chunked_dataset_valid() {
1892 let path = temp_path("close_no_sync_chunked");
1897
1898 {
1899 let file = H5File::create(&path).unwrap();
1900 let ds = file
1901 .new_dataset::<i32>()
1902 .shape([0usize, 3])
1903 .chunk(&[1, 3])
1904 .max_shape(&[None, Some(3)])
1905 .create("data")
1906 .unwrap();
1907 for frame in 0..10u64 {
1909 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
1910 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
1911 ds.write_chunk(frame as usize, &raw).unwrap();
1912 }
1913 ds.extend(&[10, 3]).unwrap();
1914 file.close_no_sync().unwrap();
1915 }
1916
1917 {
1918 let file = H5File::open(&path).unwrap();
1919 let ds = file.dataset("data").unwrap();
1920 assert_eq!(ds.shape(), vec![10, 3]);
1921 let data = ds.read_raw::<i32>().unwrap();
1922 let expected: Vec<i32> = (0..30).collect();
1923 assert_eq!(data, expected);
1924 file.close().unwrap();
1925 }
1926
1927 std::fs::remove_file(&path).ok();
1928 }
1929
1930 #[test]
1931 fn write_and_read_f64() {
1932 let path = temp_path("write_read_f64");
1933
1934 let values: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1935
1936 {
1938 let file = H5File::create(&path).unwrap();
1939 let ds = file
1940 .new_dataset::<f64>()
1941 .shape([2, 3])
1942 .create("matrix")
1943 .unwrap();
1944 ds.write_raw(&values).unwrap();
1945 file.close().unwrap();
1946 }
1947
1948 {
1950 let file = H5File::open(&path).unwrap();
1951 let ds = file.dataset("matrix").unwrap();
1952 assert_eq!(ds.shape(), vec![2, 3]);
1953 let readback = ds.read_raw::<f64>().unwrap();
1954 assert_eq!(readback, values);
1955 }
1956
1957 std::fs::remove_file(&path).ok();
1958 }
1959
1960 #[test]
1961 fn multiple_datasets() {
1962 let path = temp_path("multi_ds");
1963
1964 {
1965 let file = H5File::create(&path).unwrap();
1966 let ds1 = file.new_dataset::<i32>().shape([3]).create("ints").unwrap();
1967 ds1.write_raw(&[10i32, 20, 30]).unwrap();
1968
1969 let ds2 = file
1970 .new_dataset::<f32>()
1971 .shape([2, 2])
1972 .create("floats")
1973 .unwrap();
1974 ds2.write_raw(&[1.0f32, 2.0, 3.0, 4.0]).unwrap();
1975
1976 file.close().unwrap();
1977 }
1978
1979 {
1980 let file = H5File::open(&path).unwrap();
1981
1982 let ds_ints = file.dataset("ints").unwrap();
1983 assert_eq!(ds_ints.shape(), vec![3]);
1984 let ints = ds_ints.read_raw::<i32>().unwrap();
1985 assert_eq!(ints, vec![10, 20, 30]);
1986
1987 let ds_floats = file.dataset("floats").unwrap();
1988 assert_eq!(ds_floats.shape(), vec![2, 2]);
1989 let floats = ds_floats.read_raw::<f32>().unwrap();
1990 assert_eq!(floats, vec![1.0f32, 2.0, 3.0, 4.0]);
1991 }
1992
1993 std::fs::remove_file(&path).ok();
1994 }
1995
1996 #[test]
1997 fn close_is_idempotent() {
1998 let path = temp_path("close_idemp");
1999 let file = H5File::create(&path).unwrap();
2000 file.close().unwrap();
2001 std::fs::remove_file(&path).ok();
2003 }
2004}
2005
2006#[cfg(test)]
2007mod integration_tests {
2008 use super::*;
2009
2010 fn temp_path(name: &str) -> std::path::PathBuf {
2011 super::unique_test_path(name)
2012 }
2013
2014 #[test]
2015 fn write_file_for_h5dump() {
2016 let path = temp_path("integration");
2017 let file = H5File::create(&path).unwrap();
2018
2019 let ds = file
2020 .new_dataset::<u8>()
2021 .shape([4usize, 4])
2022 .create("data_u8")
2023 .unwrap();
2024 let data: Vec<u8> = (0..16).collect();
2025 ds.write_raw(&data).unwrap();
2026
2027 let ds2 = file
2028 .new_dataset::<f64>()
2029 .shape([3usize, 2])
2030 .create("data_f64")
2031 .unwrap();
2032 let fdata: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
2033 ds2.write_raw(&fdata).unwrap();
2034
2035 let ds3 = file
2036 .new_dataset::<i32>()
2037 .shape([5usize])
2038 .create("values")
2039 .unwrap();
2040 let idata: Vec<i32> = vec![-10, -5, 0, 5, 10];
2041 ds3.write_raw(&idata).unwrap();
2042
2043 file.close().unwrap();
2044
2045 assert!(path.exists());
2047 }
2048
2049 #[test]
2050 fn write_chunked_file_for_h5dump() {
2051 let path = temp_path("chunked");
2052 let file = H5File::create(&path).unwrap();
2053
2054 let ds = file
2056 .new_dataset::<f64>()
2057 .shape([0usize, 4])
2058 .chunk(&[1, 4])
2059 .max_shape(&[None, Some(4)])
2060 .create("streaming_data")
2061 .unwrap();
2062
2063 for frame in 0..5u64 {
2065 let values: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
2066 let raw: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
2067 ds.write_chunk(frame as usize, &raw).unwrap();
2068 }
2069
2070 ds.extend(&[5, 4]).unwrap();
2072 ds.flush().unwrap();
2073
2074 file.close().unwrap();
2075
2076 assert!(path.exists());
2077 }
2078
2079 #[test]
2080 fn write_chunked_many_frames_for_h5dump() {
2081 let path = temp_path("chunked_many");
2082 let file = H5File::create(&path).unwrap();
2083
2084 let ds = file
2085 .new_dataset::<i32>()
2086 .shape([0usize, 3])
2087 .chunk(&[1, 3])
2088 .max_shape(&[None, Some(3)])
2089 .create("data")
2090 .unwrap();
2091
2092 for frame in 0..10u64 {
2094 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2095 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2096 ds.write_chunk(frame as usize, &raw).unwrap();
2097 }
2098 ds.extend(&[10, 3]).unwrap();
2099 file.close().unwrap();
2100
2101 assert!(path.exists());
2102 }
2103
2104 #[test]
2105 fn write_dataset_with_attributes() {
2106 use crate::types::VarLenUnicode;
2107
2108 let path = temp_path("attributes");
2109 let file = H5File::create(&path).unwrap();
2110
2111 let ds = file
2112 .new_dataset::<f32>()
2113 .shape([10usize])
2114 .create("temperature")
2115 .unwrap();
2116 let data: Vec<f32> = (0..10).map(|i| i as f32 * 1.5).collect();
2117 ds.write_raw(&data).unwrap();
2118
2119 let attr = ds
2121 .new_attr::<VarLenUnicode>()
2122 .shape(())
2123 .create("units")
2124 .unwrap();
2125 attr.write_scalar(&VarLenUnicode("kelvin".to_string()))
2126 .unwrap();
2127
2128 let attr2 = ds
2129 .new_attr::<VarLenUnicode>()
2130 .shape(())
2131 .create("description")
2132 .unwrap();
2133 attr2
2134 .write_scalar(&VarLenUnicode("Temperature measurements".to_string()))
2135 .unwrap();
2136
2137 let attr3 = ds
2139 .new_attr::<VarLenUnicode>()
2140 .shape(())
2141 .create("source")
2142 .unwrap();
2143 attr3.write_string("sensor_01").unwrap();
2144
2145 let attr4 = ds
2147 .new_attr::<VarLenUnicode>()
2148 .shape(())
2149 .create("label")
2150 .unwrap();
2151 let s: VarLenUnicode = "test_label".parse().unwrap_or_default();
2152 attr4.write_scalar(&s).unwrap();
2153
2154 file.close().unwrap();
2155
2156 assert!(path.exists());
2157 }
2158
2159 #[test]
2160 fn dataset_writer_reopens_for_attributes() {
2161 let path = temp_path("dataset_writer");
2164 {
2165 let file = H5File::create(&path).unwrap();
2166 {
2167 let ds = file
2168 .new_dataset::<u16>()
2169 .shape([8])
2170 .create("image")
2171 .unwrap();
2172 ds.write_raw(&[0u16; 8]).unwrap();
2173 }
2175
2176 assert!(file.dataset("image").is_err());
2178 let ds = file.dataset_writer("image").unwrap();
2179 assert_eq!(ds.shape(), vec![8]);
2180 ds.new_attr::<i32>()
2181 .shape([3])
2182 .create("NDArrayDimOffset")
2183 .unwrap()
2184 .write_array(&[0i32, 4, 8])
2185 .unwrap();
2186 ds.new_attr::<i32>()
2187 .shape(())
2188 .create("NDUniqueId")
2189 .unwrap()
2190 .write_numeric(&42i32)
2191 .unwrap();
2192
2193 assert!(matches!(
2195 file.dataset_writer("nope"),
2196 Err(crate::error::Hdf5Error::NotFound(_))
2197 ));
2198
2199 file.close().unwrap();
2200 }
2201 {
2202 let file = H5File::open(&path).unwrap();
2203 let ds = file.dataset("image").unwrap();
2204 let off = ds.attr("NDArrayDimOffset").unwrap().read_raw().unwrap();
2205 let got: Vec<i32> = off
2206 .chunks_exact(4)
2207 .map(|b| i32::from_le_bytes([b[0], b[1], b[2], b[3]]))
2208 .collect();
2209 assert_eq!(got, vec![0, 4, 8]);
2210 let uid: i32 = ds.attr("NDUniqueId").unwrap().read_numeric().unwrap();
2211 assert_eq!(uid, 42);
2212 }
2213 std::fs::remove_file(&path).ok();
2214 }
2215
2216 #[test]
2217 fn chunked_write_read_roundtrip() {
2218 let path = temp_path("chunked_roundtrip");
2219
2220 {
2222 let file = H5File::create(&path).unwrap();
2223 let ds = file
2224 .new_dataset::<i32>()
2225 .shape([0usize, 3])
2226 .chunk(&[1, 3])
2227 .max_shape(&[None, Some(3)])
2228 .create("table")
2229 .unwrap();
2230
2231 for frame in 0..8u64 {
2232 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2233 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2234 ds.write_chunk(frame as usize, &raw).unwrap();
2235 }
2236 ds.extend(&[8, 3]).unwrap();
2237 file.close().unwrap();
2238 }
2239
2240 {
2242 let file = H5File::open(&path).unwrap();
2243 let ds = file.dataset("table").unwrap();
2244 assert_eq!(ds.shape(), vec![8, 3]);
2245 let data = ds.read_raw::<i32>().unwrap();
2246 assert_eq!(data.len(), 24);
2247 for (i, val) in data.iter().enumerate() {
2248 assert_eq!(*val, i as i32);
2249 }
2250 }
2251
2252 std::fs::remove_file(&path).ok();
2253 }
2254
2255 #[test]
2256 #[cfg(feature = "deflate")]
2257 fn compressed_chunked_roundtrip() {
2258 let path = temp_path("compressed_roundtrip");
2259
2260 {
2262 let file = H5File::create(&path).unwrap();
2263 let ds = file
2264 .new_dataset::<f64>()
2265 .shape([0usize, 4])
2266 .chunk(&[1, 4])
2267 .max_shape(&[None, Some(4)])
2268 .deflate(6)
2269 .create("compressed")
2270 .unwrap();
2271
2272 for frame in 0..10u64 {
2273 let vals: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
2274 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2275 ds.write_chunk(frame as usize, &raw).unwrap();
2276 }
2277 ds.extend(&[10, 4]).unwrap();
2278 file.close().unwrap();
2279 }
2280
2281 {
2283 let file = H5File::open(&path).unwrap();
2284 let ds = file.dataset("compressed").unwrap();
2285 assert_eq!(ds.shape(), vec![10, 4]);
2286 let data = ds.read_raw::<f64>().unwrap();
2287 assert_eq!(data.len(), 40);
2288 for (i, val) in data.iter().enumerate() {
2289 assert!(
2290 (val - i as f64).abs() < 1e-10,
2291 "mismatch at {}: {} != {}",
2292 i,
2293 val,
2294 i
2295 );
2296 }
2297 }
2298
2299 std::fs::remove_file(&path).ok();
2300 }
2301
2302 #[test]
2303 #[cfg(feature = "deflate")]
2304 fn compressed_chunked_many_frames() {
2305 let path = temp_path("compressed_many");
2306
2307 {
2308 let file = H5File::create(&path).unwrap();
2309 let ds = file
2310 .new_dataset::<i32>()
2311 .shape([0usize, 3])
2312 .chunk(&[1, 3])
2313 .max_shape(&[None, Some(3)])
2314 .deflate(6)
2315 .create("stream")
2316 .unwrap();
2317
2318 for frame in 0..100u64 {
2319 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2320 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2321 ds.write_chunk(frame as usize, &raw).unwrap();
2322 }
2323 ds.extend(&[100, 3]).unwrap();
2324 file.close().unwrap();
2325 }
2326
2327 {
2328 let file = H5File::open(&path).unwrap();
2329 let ds = file.dataset("stream").unwrap();
2330 assert_eq!(ds.shape(), vec![100, 3]);
2331 let data = ds.read_raw::<i32>().unwrap();
2332 assert_eq!(data.len(), 300);
2333 for (i, val) in data.iter().enumerate() {
2334 assert_eq!(*val, i as i32, "mismatch at {}", i);
2335 }
2336 }
2337
2338 std::fs::remove_file(&path).ok();
2339 }
2340 #[test]
2341 fn append_mode() {
2342 let path = temp_path("append");
2343
2344 {
2346 let file = H5File::create(&path).unwrap();
2347 let ds = file
2348 .new_dataset::<i32>()
2349 .shape([3usize])
2350 .create("first")
2351 .unwrap();
2352 ds.write_raw(&[1i32, 2, 3]).unwrap();
2353 file.close().unwrap();
2354 }
2355
2356 {
2358 let file = H5File::open_rw(&path).unwrap();
2359 let ds = file
2360 .new_dataset::<f64>()
2361 .shape([2usize])
2362 .create("second")
2363 .unwrap();
2364 ds.write_raw(&[4.0f64, 5.0]).unwrap();
2365 file.close().unwrap();
2366 }
2367
2368 {
2370 let file = H5File::open(&path).unwrap();
2371 let names = file.dataset_names();
2372 assert!(names.contains(&"first".to_string()));
2373 assert!(names.contains(&"second".to_string()));
2374
2375 let ds1 = file.dataset("first").unwrap();
2376 assert_eq!(ds1.read_raw::<i32>().unwrap(), vec![1, 2, 3]);
2377
2378 let ds2 = file.dataset("second").unwrap();
2379 assert_eq!(ds2.read_raw::<f64>().unwrap(), vec![4.0, 5.0]);
2380 }
2381
2382 std::fs::remove_file(&path).ok();
2383 }
2384
2385 #[test]
2386 fn open_rw_set_attr_preserves_file() {
2387 let path = temp_path("open_rw_attr");
2388 {
2390 let file = H5File::create(&path).unwrap();
2391 let ds = file
2392 .new_dataset::<i32>()
2393 .shape([3usize])
2394 .create("data")
2395 .unwrap();
2396 ds.write_raw(&[10i32, 20, 30]).unwrap();
2397 file.set_attr_string("version", "1.0").unwrap();
2398 file.close().unwrap();
2399 }
2400 {
2402 let file = H5File::open_rw(&path).unwrap();
2403 file.set_attr_string("version", "2.0").unwrap();
2404 file.close().unwrap();
2405 }
2406 {
2408 let file = H5File::open(&path).unwrap();
2409 let ds = file.dataset("data").unwrap();
2410 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![10, 20, 30]);
2411 let ver = file.attr_string("version").unwrap();
2412 assert_eq!(ver, "2.0");
2413 }
2414 std::fs::remove_file(&path).ok();
2415 }
2416
2417 #[test]
2418 #[cfg(feature = "deflate")]
2419 fn open_rw_attr_with_compressed_dataset() {
2420 use crate::format::messages::filter::FilterPipeline;
2421 let path = temp_path("open_rw_compressed");
2422 let input: Vec<&str> = (0..50).map(|_| "test string data").collect();
2423 {
2425 let file = H5File::create(&path).unwrap();
2426 file.write_vlen_strings_compressed("texts", &input, 16, FilterPipeline::deflate(6))
2427 .unwrap();
2428 file.set_attr_string("version", "1.0").unwrap();
2429 file.close().unwrap();
2430 }
2431 {
2433 let file = H5File::open_rw(&path).unwrap();
2434 file.set_attr_string("version", "2.0").unwrap();
2435 file.close().unwrap();
2436 }
2437 {
2439 let file = H5File::open(&path).unwrap();
2440 let ds = file.dataset("texts").unwrap();
2441 let strings = ds.read_vlen_strings().unwrap();
2442 assert_eq!(strings.len(), 50);
2443 assert_eq!(strings[0], "test string data");
2444 let ver = file.attr_string("version").unwrap();
2445 assert_eq!(ver, "2.0");
2446 }
2447 std::fs::remove_file(&path).ok();
2448 }
2449
2450 #[test]
2451 #[cfg(feature = "lz4")]
2452 fn append_vlen_strings_basic() {
2453 use crate::format::messages::filter::FilterPipeline;
2454 let path = temp_path("append_vlen");
2455 {
2456 let file = H5File::create(&path).unwrap();
2457 file.create_appendable_vlen_dataset("names", 4, Some(FilterPipeline::lz4()))
2458 .unwrap();
2459 file.append_vlen_strings("names", &["alice", "bob", "charlie"])
2460 .unwrap();
2461 file.append_vlen_strings("names", &["dave", "eve"]).unwrap();
2462 file.close().unwrap();
2463 }
2464 {
2465 let file = H5File::open(&path).unwrap();
2466 let ds = file.dataset("names").unwrap();
2467 let strings = ds.read_vlen_strings().unwrap();
2468 assert_eq!(strings, vec!["alice", "bob", "charlie", "dave", "eve"]);
2469 }
2470 std::fs::remove_file(&path).ok();
2471 }
2472
2473 #[test]
2474 #[cfg(feature = "lz4")]
2475 fn append_vlen_strings_large() {
2476 use crate::format::messages::filter::FilterPipeline;
2477 let path = temp_path("append_vlen_large");
2478 let batch1: Vec<String> = (0..5000).map(|i| format!("node-{:06}", i)).collect();
2479 let batch2: Vec<String> = (5000..7189).map(|i| format!("node-{:06}", i)).collect();
2480 {
2481 let file = H5File::create(&path).unwrap();
2482 file.create_appendable_vlen_dataset("data", 512, Some(FilterPipeline::lz4()))
2483 .unwrap();
2484 let r1: Vec<&str> = batch1.iter().map(|s| s.as_str()).collect();
2485 file.append_vlen_strings("data", &r1).unwrap();
2486 let r2: Vec<&str> = batch2.iter().map(|s| s.as_str()).collect();
2487 file.append_vlen_strings("data", &r2).unwrap();
2488 file.close().unwrap();
2489 }
2490 {
2491 let file = H5File::open(&path).unwrap();
2492 let ds = file.dataset("data").unwrap();
2493 let strings = ds.read_vlen_strings().unwrap();
2494 assert_eq!(strings.len(), 7189);
2495 assert_eq!(strings[0], "node-000000");
2496 assert_eq!(strings[7188], "node-007188");
2497 }
2498 std::fs::remove_file(&path).ok();
2499 }
2500
2501 #[test]
2502 fn append_vlen_strings_uncompressed() {
2503 let path = temp_path("append_vlen_unc");
2504 {
2505 let file = H5File::create(&path).unwrap();
2506 file.create_appendable_vlen_dataset("texts", 8, None)
2507 .unwrap();
2508 file.append_vlen_strings("texts", &["hello", "world"])
2509 .unwrap();
2510 file.append_vlen_strings("texts", &["foo", "bar", "baz"])
2511 .unwrap();
2512 file.close().unwrap();
2513 }
2514 {
2515 let file = H5File::open(&path).unwrap();
2516 let ds = file.dataset("texts").unwrap();
2517 let strings = ds.read_vlen_strings().unwrap();
2518 assert_eq!(strings, vec!["hello", "world", "foo", "bar", "baz"]);
2519 }
2520 std::fs::remove_file(&path).ok();
2521 }
2522
2523 #[test]
2524 fn delete_dataset_roundtrip() {
2525 let path = temp_path("delete_ds");
2526 {
2527 let file = H5File::create(&path).unwrap();
2528 file.write_vlen_strings("keep", &["a", "b"]).unwrap();
2529 file.write_vlen_strings("remove", &["x", "y"]).unwrap();
2530 file.delete_dataset("remove").unwrap();
2531 file.close().unwrap();
2532 }
2533 {
2534 let file = H5File::open(&path).unwrap();
2535 let names = file.dataset_names();
2536 assert!(names.contains(&"keep".to_string()));
2537 assert!(!names.contains(&"remove".to_string()));
2538 let ds = file.dataset("keep").unwrap();
2539 assert_eq!(ds.read_vlen_strings().unwrap(), vec!["a", "b"]);
2540 }
2541 std::fs::remove_file(&path).ok();
2542 }
2543
2544 #[test]
2545 fn delete_group_roundtrip() {
2546 let path = temp_path("delete_grp");
2547 {
2548 let file = H5File::create(&path).unwrap();
2549 let g1 = file.create_group("keep").unwrap();
2550 g1.write_vlen_strings("data", &["a"]).unwrap();
2551 let g2 = file.create_group("remove").unwrap();
2552 g2.write_vlen_strings("data", &["x"]).unwrap();
2553 file.delete_group("remove").unwrap();
2554 file.close().unwrap();
2555 }
2556 {
2557 let file = H5File::open(&path).unwrap();
2558 let names = file.dataset_names();
2559 assert!(names.contains(&"keep/data".to_string()));
2560 assert!(!names.contains(&"remove/data".to_string()));
2561 }
2562 std::fs::remove_file(&path).ok();
2563 }
2564
2565 #[test]
2566 fn open_rw_delete_recreate_group() {
2567 let path = temp_path("rw_delete_recreate");
2568 {
2570 let file = H5File::create(&path).unwrap();
2571 let n = file.create_group("nodes").unwrap();
2572 n.write_vlen_strings("id", &["a", "b", "c"]).unwrap();
2573 let e = file.create_group("edges").unwrap();
2574 e.write_vlen_strings("src", &["x", "y"]).unwrap();
2575 file.close().unwrap();
2576 }
2577 {
2579 let file = H5File::open_rw(&path).unwrap();
2580 file.delete_group("nodes").unwrap();
2581 let n = file.create_group("nodes").unwrap();
2582 n.write_vlen_strings("id", &["new1", "new2"]).unwrap();
2583 file.close().unwrap();
2584 }
2585 {
2587 let file = H5File::open(&path).unwrap();
2588 let ds = file.dataset("nodes/id").unwrap();
2589 let s = ds.read_vlen_strings().unwrap();
2590 assert_eq!(s, vec!["new1", "new2"]);
2591 let ds = file.dataset("edges/src").unwrap();
2593 let s = ds.read_vlen_strings().unwrap();
2594 assert_eq!(s, vec!["x", "y"]);
2595 }
2596 std::fs::remove_file(&path).ok();
2597 }
2598
2599 #[test]
2600 fn delete_and_recreate_group() {
2601 let path = temp_path("delete_recreate");
2602 {
2603 let file = H5File::create(&path).unwrap();
2604 let g = file.create_group("nodes").unwrap();
2605 g.write_vlen_strings("id", &["old1", "old2"]).unwrap();
2606 file.delete_group("nodes").unwrap();
2607 let g = file.create_group("nodes").unwrap();
2608 g.write_vlen_strings("id", &["new1", "new2", "new3"])
2609 .unwrap();
2610 file.close().unwrap();
2611 }
2612 {
2613 let file = H5File::open(&path).unwrap();
2614 let ds = file.dataset("nodes/id").unwrap();
2615 let strings = ds.read_vlen_strings().unwrap();
2616 assert_eq!(strings, vec!["new1", "new2", "new3"]);
2617 }
2618 std::fs::remove_file(&path).ok();
2619 }
2620
2621 #[test]
2622 #[cfg(feature = "deflate")]
2623 fn vlen_string_compressed_large_roundtrip() {
2624 use crate::format::messages::filter::FilterPipeline;
2625 let path = temp_path("vlen_large");
2626 let input: Vec<String> = (0..7189)
2628 .map(|i| format!("node-{:08x}-{}", i, "a".repeat(20 + (i % 30))))
2629 .collect();
2630 let input_refs: Vec<&str> = input.iter().map(|s| s.as_str()).collect();
2631 {
2632 let file = H5File::create(&path).unwrap();
2633 file.create_group("nodes").unwrap();
2634 file.write_vlen_strings_compressed(
2635 "nodes/id",
2636 &input_refs,
2637 512,
2638 FilterPipeline::deflate(6),
2639 )
2640 .unwrap();
2641 file.close().unwrap();
2642 }
2643 {
2645 let file = H5File::open(&path).unwrap();
2646 let ds = file.dataset("nodes/id").unwrap();
2647 let strings = ds.read_vlen_strings().unwrap();
2648 assert_eq!(strings.len(), 7189);
2649 assert_eq!(strings[0], input[0]);
2650 assert_eq!(strings[7188], input[7188]);
2651 }
2652 {
2654 let file = H5File::open_rw(&path).unwrap();
2655 file.set_attr_string("version", "1.0").unwrap();
2656 file.close().unwrap();
2657 }
2658 {
2659 let file = H5File::open(&path).unwrap();
2660 let ds = file.dataset("nodes/id").unwrap();
2661 let strings = ds.read_vlen_strings().unwrap();
2662 assert_eq!(strings.len(), 7189);
2663 assert_eq!(strings[0], input[0]);
2664 }
2665 std::fs::remove_file(&path).ok();
2666 }
2667
2668 #[test]
2669 fn vlen_string_write_read() {
2670 let path = temp_path("vlen_wr");
2671 {
2672 let file = H5File::create(&path).unwrap();
2673 file.write_vlen_strings("names", &["alice", "bob", "charlie"])
2674 .unwrap();
2675 file.close().unwrap();
2676 }
2677 {
2678 let file = H5File::open(&path).unwrap();
2679 let ds = file.dataset("names").unwrap();
2680 let strings = ds.read_vlen_strings().unwrap();
2681 assert_eq!(strings, vec!["alice", "bob", "charlie"]);
2682 }
2683 std::fs::remove_file(&path).ok();
2684 }
2685
2686 #[test]
2691 fn vlen_string_writers_declare_their_character_set() {
2692 use crate::format::messages::datatype::DatatypeMessage;
2693
2694 let path = temp_path("vlen_cset");
2695 let file = H5File::create(&path).unwrap();
2696 file.write_vlen_strings_ascii("ascii", &["alpha", "b", ""])
2697 .unwrap();
2698 file.write_vlen_strings("utf8", &["été", "日本"]).unwrap();
2699 let err = file
2700 .write_vlen_strings_ascii("rejected", &["ok", "안녕"])
2701 .err()
2702 .expect("a non-ASCII string was accepted under an ASCII datatype")
2703 .to_string();
2704 assert!(
2705 err.contains("string 1") && err.contains("is not ASCII"),
2706 "got: {err}"
2707 );
2708 file.close().unwrap();
2709
2710 let file = H5File::open(&path).unwrap();
2711 let ascii = file.dataset("ascii").unwrap();
2712 assert_eq!(
2713 ascii.datatype().unwrap(),
2714 DatatypeMessage::VarLenString {
2715 padding: 0,
2716 charset: 0,
2717 }
2718 );
2719 assert_eq!(ascii.read_strings().unwrap(), vec!["alpha", "b", ""]);
2720 let utf8 = file.dataset("utf8").unwrap();
2721 assert_eq!(
2722 utf8.datatype().unwrap(),
2723 DatatypeMessage::VarLenString {
2724 padding: 0,
2725 charset: 1,
2726 }
2727 );
2728 assert_eq!(utf8.read_strings().unwrap(), vec!["été", "日本"]);
2729 assert!(file.dataset("rejected").is_err());
2731 std::fs::remove_file(&path).ok();
2732 }
2733
2734 #[test]
2737 fn group_vlen_string_writer_declares_ascii() {
2738 use crate::format::messages::datatype::DatatypeMessage;
2739
2740 let path = temp_path("vlen_cset_group");
2741 let file = H5File::create(&path).unwrap();
2742 let g = file.create_group("entry").unwrap();
2743 g.write_vlen_strings_ascii("notes", &["alpha", "b"])
2744 .unwrap();
2745 let err = g
2746 .write_vlen_strings_ascii("rejected", &["안녕"])
2747 .err()
2748 .expect("a non-ASCII string was accepted under an ASCII datatype")
2749 .to_string();
2750 assert!(err.contains("is not ASCII"), "got: {err}");
2751 file.close().unwrap();
2752
2753 let file = H5File::open(&path).unwrap();
2754 let ds = file.dataset("entry/notes").unwrap();
2755 assert_eq!(
2756 ds.datatype().unwrap(),
2757 DatatypeMessage::VarLenString {
2758 padding: 0,
2759 charset: 0,
2760 }
2761 );
2762 assert_eq!(ds.read_strings().unwrap(), vec!["alpha", "b"]);
2763 std::fs::remove_file(&path).ok();
2764 }
2765
2766 #[test]
2767 fn vlen_bytes_write_read() {
2768 let path = temp_path("vlen_bytes_wr");
2769 let items: [&[u8]; 4] = [b"abc", b"", &[0u8, 1, 2, 255], b"hi"];
2770 {
2771 let file = H5File::create(&path).unwrap();
2772 file.write_vlen_bytes("blobs", &items).unwrap();
2773 file.close().unwrap();
2774 }
2775 {
2776 let file = H5File::open(&path).unwrap();
2777 let ds = file.dataset("blobs").unwrap();
2778 let got = ds.read_vlen_bytes().unwrap();
2779 let expected: Vec<Vec<u8>> = items.iter().map(|s| s.to_vec()).collect();
2780 assert_eq!(got, expected);
2781 }
2782 std::fs::remove_file(&path).ok();
2783 }
2784
2785 #[test]
2789 fn vlen_numeric_write_read() {
2790 use crate::format::global_heap::decode_vlen_reference;
2791 use crate::format::messages::datatype::DatatypeMessage;
2792
2793 let path = temp_path("vlen_numeric_wr");
2794 let a: &[i32] = &[1, 2, 3];
2795 let b: &[i32] = &[];
2796 let c: &[i32] = &[-7];
2797 {
2798 let file = H5File::create(&path).unwrap();
2799 file.write_vlen_numeric("data", &[a, b, c]).unwrap();
2800 let f64s: &[f64] = &[1.5, -2.5];
2801 file.write_vlen_numeric("wide", &[f64s]).unwrap();
2802 file.close().unwrap();
2803 }
2804 let file = H5File::open(&path).unwrap();
2805 let ds = file.dataset("data").unwrap();
2806 assert_eq!(
2807 ds.datatype().unwrap(),
2808 DatatypeMessage::VarLenSequence {
2809 base: Box::new(DatatypeMessage::i32_type()),
2810 }
2811 );
2812 let decoded: Vec<Vec<i32>> = ds
2813 .read_vlen_bytes()
2814 .unwrap()
2815 .iter()
2816 .map(|item| {
2817 item.chunks_exact(4)
2818 .map(|w| i32::from_le_bytes(w.try_into().unwrap()))
2819 .collect()
2820 })
2821 .collect();
2822 assert_eq!(decoded, vec![a.to_vec(), b.to_vec(), c.to_vec()]);
2823
2824 let ctx = crate::format::FormatContext::default_v3();
2826 let raw = ds.read_raw_bytes().unwrap();
2827 let (seq_len, _, _) = decode_vlen_reference(&raw, &ctx).unwrap();
2828 assert_eq!(seq_len, 3);
2829
2830 let wide = file.dataset("wide").unwrap();
2831 assert_eq!(
2832 wide.datatype().unwrap(),
2833 DatatypeMessage::VarLenSequence {
2834 base: Box::new(DatatypeMessage::f64_type()),
2835 }
2836 );
2837 let (seq_len, _, _) = decode_vlen_reference(&wide.read_raw_bytes().unwrap(), &ctx).unwrap();
2838 assert_eq!(seq_len, 2);
2839 drop(file);
2840 std::fs::remove_file(&path).ok();
2841 }
2842
2843 #[test]
2846 fn vlen_bytes_is_the_u8_case_of_vlen_numeric() {
2847 use crate::format::messages::datatype::DatatypeMessage;
2848
2849 let path = temp_path("vlen_bytes_u8");
2850 let items: [&[u8]; 2] = [b"abc", b""];
2851 {
2852 let file = H5File::create(&path).unwrap();
2853 file.write_vlen_bytes("blobs", &items).unwrap();
2854 file.close().unwrap();
2855 }
2856 let file = H5File::open(&path).unwrap();
2857 let ds = file.dataset("blobs").unwrap();
2858 assert_eq!(ds.datatype().unwrap(), DatatypeMessage::vlen_bytes());
2859 let ctx = crate::format::FormatContext::default_v3();
2860 let (seq_len, _, _) =
2861 crate::format::global_heap::decode_vlen_reference(&ds.read_raw_bytes().unwrap(), &ctx)
2862 .unwrap();
2863 assert_eq!(seq_len, 3);
2864 drop(file);
2865 std::fs::remove_file(&path).ok();
2866 }
2867
2868 #[test]
2869 fn vlen_bytes_in_group_with_attribute() {
2870 use crate::types::VarLenUnicode;
2871 let path = temp_path("vlen_bytes_grp");
2872 let items: [&[u8]; 2] = [&[1u8, 2, 3], &[9u8, 8, 7, 6]];
2873 {
2874 let file = H5File::create(&path).unwrap();
2875 let grp = file.root_group().create_group("payloads").unwrap();
2876 let ds = grp.write_vlen_bytes("frames", &items).unwrap();
2877 ds.new_attr::<VarLenUnicode>()
2878 .shape(())
2879 .create("codec")
2880 .unwrap()
2881 .write_string("raw")
2882 .unwrap();
2883 file.close().unwrap();
2884 }
2885 {
2886 let file = H5File::open(&path).unwrap();
2887 let ds = file.dataset("payloads/frames").unwrap();
2888 let got = ds.read_vlen_bytes().unwrap();
2889 let expected: Vec<Vec<u8>> = items.iter().map(|s| s.to_vec()).collect();
2890 assert_eq!(got, expected);
2891 }
2892 std::fs::remove_file(&path).ok();
2893 }
2894
2895 #[test]
2896 fn vlen_dataset_returns_handle_for_attributes() {
2897 use crate::types::VarLenUnicode;
2898 let path = temp_path("vlen_attr");
2899 {
2900 let file = H5File::create(&path).unwrap();
2901 let grp = file.root_group().create_group("ch").unwrap();
2902 let ds = grp
2905 .write_vlen_strings("labels", &["a", "bb", "ccc"])
2906 .unwrap();
2907 ds.new_attr::<VarLenUnicode>()
2908 .shape(())
2909 .create("unit")
2910 .unwrap()
2911 .write_string("volt")
2912 .unwrap();
2913 let ds2 = grp.dataset_writer("labels").unwrap();
2915 ds2.new_attr::<VarLenUnicode>()
2916 .shape(())
2917 .create("desc")
2918 .unwrap()
2919 .write_string("channel labels")
2920 .unwrap();
2921 file.close().unwrap();
2922 }
2923 {
2924 let file = H5File::open(&path).unwrap();
2925 let ds = file.dataset("ch/labels").unwrap();
2926 assert_eq!(ds.read_vlen_strings().unwrap(), vec!["a", "bb", "ccc"]);
2927 assert_eq!(ds.attr("unit").unwrap().read_string().unwrap(), "volt");
2928 assert_eq!(
2929 ds.attr("desc").unwrap().read_string().unwrap(),
2930 "channel labels"
2931 );
2932 }
2933 std::fs::remove_file(&path).ok();
2934 }
2935
2936 #[test]
2937 #[cfg(feature = "deflate")]
2938 fn vlen_string_deflate_roundtrip() {
2939 use crate::format::messages::filter::FilterPipeline;
2940 let path = temp_path("vlen_deflate");
2941 let input: Vec<&str> = (0..100)
2942 .map(|i| match i % 3 {
2943 0 => "hello world",
2944 1 => "compressed vlen string test",
2945 _ => "rust-hdf5",
2946 })
2947 .collect();
2948 {
2949 let file = H5File::create(&path).unwrap();
2950 file.write_vlen_strings_compressed("texts", &input, 16, FilterPipeline::deflate(6))
2951 .unwrap();
2952 file.close().unwrap();
2953 }
2954 {
2955 let file = H5File::open(&path).unwrap();
2956 let ds = file.dataset("texts").unwrap();
2957 let strings = ds.read_vlen_strings().unwrap();
2958 assert_eq!(strings.len(), 100);
2959 for (i, s) in strings.iter().enumerate() {
2960 assert_eq!(s, input[i]);
2961 }
2962 }
2963 std::fs::remove_file(&path).ok();
2964 }
2965
2966 #[test]
2967 #[cfg(feature = "zstd")]
2968 fn vlen_string_zstd_roundtrip() {
2969 use crate::format::messages::filter::FilterPipeline;
2970 let path = temp_path("vlen_zstd");
2971 let input: Vec<&str> = (0..200)
2972 .map(|i| match i % 4 {
2973 0 => "zstandard compression test",
2974 1 => "variable length string",
2975 2 => "rust-hdf5 chunked storage",
2976 _ => "hello zstd world",
2977 })
2978 .collect();
2979 {
2980 let file = H5File::create(&path).unwrap();
2981 file.write_vlen_strings_compressed("data", &input, 32, FilterPipeline::zstd(3))
2982 .unwrap();
2983 file.close().unwrap();
2984 }
2985 {
2986 let file = H5File::open(&path).unwrap();
2987 let ds = file.dataset("data").unwrap();
2988 let strings = ds.read_vlen_strings().unwrap();
2989 assert_eq!(strings.len(), 200);
2990 for (i, s) in strings.iter().enumerate() {
2991 assert_eq!(s, input[i]);
2992 }
2993 }
2994 std::fs::remove_file(&path).ok();
2995 }
2996
2997 #[test]
2998 #[cfg(feature = "deflate")]
2999 fn shuffle_deflate_roundtrip() {
3000 let path = temp_path("shuf_defl");
3001 {
3002 let file = H5File::create(&path).unwrap();
3003 let ds = file
3004 .new_dataset::<f64>()
3005 .shape([0usize, 4])
3006 .chunk(&[1, 4])
3007 .max_shape(&[None, Some(4)])
3008 .shuffle_deflate(6)
3009 .create("data")
3010 .unwrap();
3011 for frame in 0..20u64 {
3012 let vals: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
3013 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3014 ds.write_chunk(frame as usize, &raw).unwrap();
3015 }
3016 ds.extend(&[20, 4]).unwrap();
3017 file.close().unwrap();
3018 }
3019 {
3020 let file = H5File::open(&path).unwrap();
3021 let ds = file.dataset("data").unwrap();
3022 assert_eq!(ds.shape(), vec![20, 4]);
3023 let data = ds.read_raw::<f64>().unwrap();
3024 assert_eq!(data.len(), 80);
3025 for (i, val) in data.iter().enumerate() {
3026 assert!((val - i as f64).abs() < 1e-10);
3027 }
3028 }
3029 std::fs::remove_file(&path).ok();
3030 }
3031
3032 #[test]
3033 fn file_level_attributes() {
3034 let path = temp_path("file_attr");
3035 {
3036 let file = H5File::create(&path).unwrap();
3037 file.set_attr_string("title", "Test File").unwrap();
3038 file.set_attr_numeric("version", &42i32).unwrap();
3039 let ds = file
3040 .new_dataset::<u8>()
3041 .shape([1usize])
3042 .create("dummy")
3043 .unwrap();
3044 ds.write_raw(&[0u8]).unwrap();
3045 file.close().unwrap();
3046 }
3047 {
3048 let file = H5File::open(&path).unwrap();
3049 assert!(file.dataset_names().contains(&"dummy".to_string()));
3050
3051 let names = file.attr_names().unwrap();
3053 assert!(names.contains(&"title".to_string()));
3054
3055 let title = file.attr_string("title").unwrap();
3056 assert_eq!(title, "Test File");
3057 }
3058 std::fs::remove_file(&path).ok();
3059 }
3060
3061 #[test]
3062 fn scalar_dataset_roundtrip() {
3063 let path = temp_path("scalar");
3064 {
3065 let file = H5File::create(&path).unwrap();
3066 let ds = file.new_dataset::<f64>().scalar().create("pi").unwrap();
3067 ds.write_raw(&[std::f64::consts::PI]).unwrap();
3068 file.close().unwrap();
3069 }
3070 {
3071 let file = H5File::open(&path).unwrap();
3072 let ds = file.dataset("pi").unwrap();
3073 assert_eq!(ds.shape(), Vec::<usize>::new());
3074 assert_eq!(ds.total_elements(), 1);
3075 let data = ds.read_raw::<f64>().unwrap();
3076 assert_eq!(data.len(), 1);
3077 assert!((data[0] - std::f64::consts::PI).abs() < 1e-15);
3078 }
3079 std::fs::remove_file(&path).ok();
3080 }
3081
3082 #[test]
3083 fn append_mode_extend_chunked() {
3084 let path = temp_path("append_extend");
3085
3086 {
3088 let file = H5File::create(&path).unwrap();
3089 let ds = file
3090 .new_dataset::<i32>()
3091 .shape([0usize, 3])
3092 .chunk(&[1, 3])
3093 .max_shape(&[None, Some(3)])
3094 .create("stream")
3095 .unwrap();
3096 for i in 0..5u64 {
3097 let vals: Vec<i32> = (0..3).map(|j| (i * 3 + j) as i32).collect();
3098 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3099 ds.write_chunk(i as usize, &raw).unwrap();
3100 }
3101 ds.extend(&[5, 3]).unwrap();
3102 file.close().unwrap();
3103 }
3104
3105 {
3107 let file = H5File::open_rw(&path).unwrap();
3108 let names = file.dataset_names();
3110 assert!(names.contains(&"stream".to_string()));
3111
3112 let mut inner = crate::file::borrow_inner_mut(&file.inner);
3114 if let crate::file::H5FileInner::Writer(writer) = &mut *inner {
3115 let ds_idx = writer.dataset_index("stream").unwrap();
3116 for i in 5..10u64 {
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 writer.write_chunk(ds_idx, i, &raw).unwrap();
3120 }
3121 writer.extend_dataset(ds_idx, &[10, 3]).unwrap();
3122 }
3123 drop(inner);
3124 file.close().unwrap();
3125 }
3126
3127 {
3129 let file = H5File::open(&path).unwrap();
3130 let ds = file.dataset("stream").unwrap();
3131 assert_eq!(ds.shape(), vec![10, 3]);
3132 let data = ds.read_raw::<i32>().unwrap();
3133 assert_eq!(data.len(), 30);
3134 for (i, val) in data.iter().enumerate() {
3135 assert_eq!(*val, i as i32, "mismatch at {}", i);
3136 }
3137 }
3138
3139 std::fs::remove_file(&path).ok();
3140 }
3141
3142 #[test]
3143 fn group_hierarchy_roundtrip() {
3144 let path = temp_path("groups_rt");
3145
3146 {
3147 let file = H5File::create(&path).unwrap();
3148 let root = file.root_group();
3149
3150 let det = root.create_group("detector").unwrap();
3152 let raw = det.create_group("raw").unwrap();
3153
3154 let ds1 = det
3156 .new_dataset::<f32>()
3157 .shape([10usize])
3158 .create("temperature")
3159 .unwrap();
3160 ds1.write_raw(&[1.0f32; 10]).unwrap();
3161
3162 let ds2 = raw
3163 .new_dataset::<u16>()
3164 .shape([4usize, 4])
3165 .create("image")
3166 .unwrap();
3167 ds2.write_raw(&[42u16; 16]).unwrap();
3168
3169 let ds3 = file
3171 .new_dataset::<i32>()
3172 .shape([3usize])
3173 .create("version")
3174 .unwrap();
3175 ds3.write_raw(&[1i32, 0, 0]).unwrap();
3176
3177 file.close().unwrap();
3178 }
3179
3180 {
3181 let file = H5File::open(&path).unwrap();
3182 let names = file.dataset_names();
3183 assert!(names.contains(&"version".to_string()));
3184 assert!(names.contains(&"detector/temperature".to_string()));
3185 assert!(names.contains(&"detector/raw/image".to_string()));
3186
3187 let ds = file.dataset("version").unwrap();
3189 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![1, 0, 0]);
3190
3191 let ds = file.dataset("detector/temperature").unwrap();
3192 assert_eq!(ds.read_raw::<f32>().unwrap(), vec![1.0f32; 10]);
3193
3194 let ds = file.dataset("detector/raw/image").unwrap();
3195 assert_eq!(ds.shape(), vec![4, 4]);
3196 assert_eq!(ds.read_raw::<u16>().unwrap(), vec![42u16; 16]);
3197
3198 let root = file.root_group();
3200 let group_names = root.group_names().unwrap();
3201 assert!(group_names.contains(&"detector".to_string()));
3202 }
3203
3204 std::fs::remove_file(&path).ok();
3205 }
3206
3207 #[test]
3208 fn nested_groups_via_file_create_group() {
3209 let path = temp_path("file_create_group");
3210
3211 {
3212 let file = H5File::create(&path).unwrap();
3213
3214 let grp = file.create_group("sensors").unwrap();
3216 let sub = grp.create_group("accel").unwrap();
3217
3218 let ds = sub
3219 .new_dataset::<f64>()
3220 .shape([3usize])
3221 .create("xyz")
3222 .unwrap();
3223 ds.write_raw(&[1.0f64, 2.0, 3.0]).unwrap();
3224
3225 file.close().unwrap();
3226 }
3227
3228 {
3229 let file = H5File::open(&path).unwrap();
3230 let names = file.dataset_names();
3231 assert!(names.contains(&"sensors/accel/xyz".to_string()));
3232
3233 let ds = file.dataset("sensors/accel/xyz").unwrap();
3234 assert_eq!(ds.read_raw::<f64>().unwrap(), vec![1.0, 2.0, 3.0]);
3235
3236 let root = file.root_group();
3238 let sensors = root.group("sensors").unwrap();
3239 assert_eq!(sensors.name(), "/sensors");
3240
3241 let accel = sensors.group("accel").unwrap();
3242 assert_eq!(accel.name(), "/sensors/accel");
3243
3244 let top_groups = root.group_names().unwrap();
3246 assert!(top_groups.contains(&"sensors".to_string()));
3247
3248 let sub_groups = sensors.group_names().unwrap();
3250 assert!(sub_groups.contains(&"accel".to_string()));
3251 }
3252
3253 std::fs::remove_file(&path).ok();
3254 }
3255}
3256
3257#[cfg(test)]
3258mod h5py_compat_tests {
3259 use super::*;
3260
3261 fn temp_path(name: &str) -> std::path::PathBuf {
3262 super::unique_test_path(name)
3263 }
3264
3265 #[test]
3267 #[cfg(feature = "deflate")]
3268 fn h5dump_validates_our_files() {
3269 let h5dump = std::process::Command::new("h5dump")
3271 .arg("--version")
3272 .output();
3273 if h5dump.is_err() {
3274 eprintln!("skipping: h5dump not found");
3275 return;
3276 }
3277
3278 let path = temp_path("h5dump_validate");
3279
3280 {
3282 let file = H5File::create(&path).unwrap();
3283
3284 let ds = file
3286 .new_dataset::<f64>()
3287 .shape([3usize, 4])
3288 .create("matrix")
3289 .unwrap();
3290 let data: Vec<f64> = (0..12).map(|i| i as f64).collect();
3291 ds.write_raw(&data).unwrap();
3292
3293 let ds2 = file
3295 .new_dataset::<i32>()
3296 .shape([0usize, 2])
3297 .chunk(&[1, 2])
3298 .max_shape(&[None, Some(2)])
3299 .deflate(6)
3300 .create("stream")
3301 .unwrap();
3302 for i in 0..5u64 {
3303 let vals: Vec<i32> = vec![i as i32 * 2, i as i32 * 2 + 1];
3304 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3305 ds2.write_chunk(i as usize, &raw).unwrap();
3306 }
3307 ds2.extend(&[5, 2]).unwrap();
3308
3309 let grp = file.create_group("meta").unwrap();
3311 let ds3 = grp
3312 .new_dataset::<u8>()
3313 .shape([4usize])
3314 .create("flags")
3315 .unwrap();
3316 ds3.write_raw(&[1u8, 0, 1, 0]).unwrap();
3317
3318 use crate::types::VarLenUnicode;
3320 let attr = ds
3321 .new_attr::<VarLenUnicode>()
3322 .shape(())
3323 .create("units")
3324 .unwrap();
3325 attr.write_string("meters").unwrap();
3326
3327 file.close().unwrap();
3328 }
3329
3330 let output = std::process::Command::new("h5dump")
3332 .arg("-H") .arg(path.to_str().unwrap())
3334 .output()
3335 .unwrap();
3336
3337 assert!(
3338 output.status.success(),
3339 "h5dump failed:\nstdout: {}\nstderr: {}",
3340 String::from_utf8_lossy(&output.stdout),
3341 String::from_utf8_lossy(&output.stderr),
3342 );
3343
3344 let output2 = std::process::Command::new("h5dump")
3346 .arg(path.to_str().unwrap())
3347 .output()
3348 .unwrap();
3349
3350 assert!(
3351 output2.status.success(),
3352 "h5dump (full) failed:\nstderr: {}",
3353 String::from_utf8_lossy(&output2.stderr),
3354 );
3355
3356 std::fs::remove_file(&path).ok();
3357 }
3358
3359 #[test]
3360 fn read_h5py_generated_file() {
3361 let path = "/tmp/test_h5py_default.h5";
3362 if !std::path::Path::new(path).exists() {
3363 eprintln!("skipping: h5py test file not found");
3364 return;
3365 }
3366 let file = H5File::open(path).unwrap();
3367
3368 let ds = file.dataset("data").unwrap();
3369 assert_eq!(ds.shape(), vec![4, 5]);
3370 let data = ds.read_raw::<f64>().unwrap();
3371 assert_eq!(data.len(), 20);
3372 assert!((data[0]).abs() < 1e-10);
3373 assert!((data[19] - 19.0).abs() < 1e-10);
3374
3375 let ds2 = file.dataset("images").unwrap();
3376 assert_eq!(ds2.shape(), vec![3, 64, 64]);
3377 let images = ds2.read_raw::<u16>().unwrap();
3378 assert_eq!(images.len(), 3 * 64 * 64);
3379 }
3380
3381 #[test]
3386 fn open_rw_refuses_every_create_only_option() {
3387 let path = temp_path("open_rw_refuses");
3388 H5File::create(&path).unwrap().close().unwrap();
3389
3390 let err = H5File::options()
3391 .track_order(true)
3392 .open_rw(&path)
3393 .err()
3394 .unwrap();
3395 assert!(err.to_string().contains("track_order"), "{err}");
3396
3397 let err = H5File::options()
3398 .libver(LibverBound::V110)
3399 .open_rw(&path)
3400 .err()
3401 .unwrap();
3402 assert!(err.to_string().contains("libver"), "{err}");
3403
3404 for bound in [
3409 LibverBound::Earliest,
3410 LibverBound::V18,
3411 LibverBound::V112,
3412 LibverBound::V114,
3413 LibverBound::V200,
3414 ] {
3415 let err = H5File::options()
3416 .libver(bound)
3417 .open_rw(&path)
3418 .err()
3419 .unwrap();
3420 assert!(err.to_string().contains("libver"), "{bound:?}: {err}");
3421 }
3422
3423 let err = H5File::options()
3424 .userblock(512)
3425 .open_rw(&path)
3426 .err()
3427 .unwrap();
3428 assert!(err.to_string().contains("userblock"), "{err}");
3429
3430 let types = crate::format::sohm::type_flag(crate::format::messages::MSG_DATATYPE).unwrap();
3431 let err = H5File::options()
3432 .shared_messages(&[(types, 0)], 50, 40)
3433 .open_rw(&path)
3434 .err()
3435 .unwrap();
3436 assert!(err.to_string().contains("shared_messages"), "{err}");
3437
3438 H5File::options().open_rw(&path).unwrap().close().unwrap();
3440
3441 std::fs::remove_file(&path).ok();
3442 }
3443
3444 #[test]
3447 fn open_refuses_a_create_only_option() {
3448 let path = temp_path("open_refuses");
3449 H5File::create(&path).unwrap().close().unwrap();
3450
3451 let err = H5File::options()
3452 .track_order(true)
3453 .open(&path)
3454 .err()
3455 .unwrap();
3456 assert!(err.to_string().contains("track_order"), "{err}");
3457
3458 H5File::options().open(&path).unwrap().close().unwrap();
3459
3460 std::fs::remove_file(&path).ok();
3461 }
3462}