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 {
1326 self.locking(FileLocking::Disabled)
1327 }
1328
1329 pub fn best_effort_locking(self) -> Self {
1332 self.locking(FileLocking::BestEffort)
1333 }
1334
1335 pub fn elink_prefix(mut self, prefix: impl Into<String>) -> Self {
1366 self.elink_prefix = Some(prefix.into());
1367 self
1368 }
1369
1370 pub fn track_order(mut self, track: bool) -> Self {
1378 self.track_order = track;
1379 self
1380 }
1381
1382 pub fn track_times(mut self, track: bool) -> Self {
1402 self.track_times = track;
1403 self
1404 }
1405
1406 pub fn libver(mut self, libver: LibverBound) -> Self {
1449 self.libver = Some(libver);
1450 self
1451 }
1452
1453 pub fn userblock(mut self, size: u64) -> Self {
1476 self.userblock = size;
1477 self
1478 }
1479
1480 pub fn shared_messages(
1511 mut self,
1512 indexes: &[(u16, u32)],
1513 list_max: u16,
1514 btree_min: u16,
1515 ) -> Self {
1516 self.shared_messages = SharedMessageConfig::new(indexes, list_max, btree_min);
1517 self
1518 }
1519
1520 pub fn file_space(
1554 mut self,
1555 strategy: FileSpaceStrategy,
1556 persist: bool,
1557 threshold: u64,
1558 ) -> Self {
1559 self.file_space = Some(FileSpaceConfig::new(strategy, persist, threshold));
1560 self
1561 }
1562
1563 pub fn file_space_page_size(mut self, size: u64) -> Self {
1591 self.file_space_page_size = Some(size);
1592 self
1593 }
1594
1595 fn resolved_file_space(&self) -> FileSpaceConfig {
1604 let config = self.file_space.unwrap_or_default();
1605 match self.file_space_page_size {
1606 Some(size) => config.with_page_size(size),
1607 None => config,
1608 }
1609 }
1610
1611 fn resolved_locking(&self) -> FileLocking {
1612 match self.locking {
1613 Some(p) => p,
1614 None => FileLocking::from_env_or(FileLocking::default()),
1615 }
1616 }
1617
1618 fn refuse_create_only_options(&self) -> Result<()> {
1636 let mut offending = Vec::new();
1637 if self.track_order {
1638 offending.push("track_order");
1639 }
1640 if self.track_times {
1641 offending.push("track_times");
1642 }
1643 if self.libver.is_some() {
1644 offending.push("libver");
1645 }
1646 if self.userblock != 0 {
1647 offending.push("userblock");
1648 }
1649 if self.shared_messages != SharedMessageConfig::default() {
1650 offending.push("shared_messages");
1651 }
1652 if self.file_space.is_some() {
1653 offending.push("file_space");
1654 }
1655 if self.file_space_page_size.is_some() {
1656 offending.push("file_space_page_size");
1657 }
1658 if offending.is_empty() {
1659 Ok(())
1660 } else {
1661 Err(Hdf5Error::InvalidState(format!(
1662 "these options only take effect when creating a file, not when \
1663 opening an existing one: {}",
1664 offending.join(", ")
1665 )))
1666 }
1667 }
1668
1669 fn refuse_read_only_options(&self) -> Result<()> {
1677 if self.elink_prefix.is_none() {
1678 return Ok(());
1679 }
1680 Err(Hdf5Error::InvalidState(
1681 "these options only take effect when opening a file for reading: \
1682 elink_prefix"
1683 .to_string(),
1684 ))
1685 }
1686
1687 pub fn create<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1689 self.refuse_read_only_options()?;
1690 let writer = Hdf5Writer::create_with_options(
1691 path.as_ref(),
1692 crate::io::writer::FileCreateOptions {
1693 locking: self.resolved_locking(),
1694 track_order: self.track_order,
1695 track_times: self.track_times,
1696 libver: self.libver,
1697 userblock: self.userblock,
1698 shared_messages: self.shared_messages,
1699 file_space: self.resolved_file_space(),
1700 },
1701 )?;
1702 Ok(H5File {
1703 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
1704 })
1705 }
1706
1707 pub fn open<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1709 self.refuse_create_only_options()?;
1710 let mut reader = Hdf5Reader::open_with_locking(path.as_ref(), self.resolved_locking())?;
1711 reader.set_elink_prefix(self.elink_prefix);
1712 Ok(H5File {
1713 inner: new_shared(H5FileInner::Reader(Box::new(reader))),
1714 })
1715 }
1716
1717 pub fn open_rw<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1719 self.refuse_create_only_options()?;
1720 self.refuse_read_only_options()?;
1721 let writer = Hdf5Writer::open_append_with_locking(path.as_ref(), self.resolved_locking())?;
1722 Ok(H5File {
1723 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
1724 })
1725 }
1726}
1727
1728#[cfg(test)]
1729fn unique_test_path(name: &str) -> std::path::PathBuf {
1730 use std::sync::atomic::{AtomicU64, Ordering};
1735 static COUNTER: AtomicU64 = AtomicU64::new(0);
1736 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
1737 std::env::temp_dir().join(format!(
1738 "rust_hdf5_test_{}_{}_{}.h5",
1739 name,
1740 std::process::id(),
1741 n
1742 ))
1743}
1744
1745#[cfg(test)]
1746mod tests {
1747 use super::*;
1748 use std::path::PathBuf;
1749
1750 fn temp_path(name: &str) -> PathBuf {
1751 super::unique_test_path(name)
1752 }
1753
1754 #[test]
1755 fn create_and_close_empty() {
1756 let path = temp_path("create_empty");
1757 let file = H5File::create(&path).unwrap();
1758 file.close().unwrap();
1759
1760 let file = H5File::open(&path).unwrap();
1762 file.close().unwrap();
1763
1764 std::fs::remove_file(&path).ok();
1765 }
1766
1767 #[test]
1768 fn create_and_drop_empty() {
1769 let path = temp_path("drop_empty");
1770 {
1771 let _file = H5File::create(&path).unwrap();
1772 }
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 dataset_not_found() {
1783 let path = temp_path("ds_not_found");
1784 {
1785 let _file = H5File::create(&path).unwrap();
1786 }
1787 let file = H5File::open(&path).unwrap();
1788 let result = file.dataset("nonexistent");
1789 assert!(result.is_err());
1790
1791 std::fs::remove_file(&path).ok();
1792 }
1793
1794 #[test]
1795 fn write_and_read_roundtrip() {
1796 let path = temp_path("write_read_rt");
1797
1798 {
1800 let file = H5File::create(&path).unwrap();
1801 let ds = file
1802 .new_dataset::<u8>()
1803 .shape([4, 4])
1804 .create("data")
1805 .unwrap();
1806 ds.write_raw(&[0u8; 16]).unwrap();
1807 file.close().unwrap();
1808 }
1809
1810 {
1812 let file = H5File::open(&path).unwrap();
1813 let ds = file.dataset("data").unwrap();
1814 assert_eq!(ds.shape(), vec![4, 4]);
1815 let data = ds.read_raw::<u8>().unwrap();
1816 assert_eq!(data.len(), 16);
1817 assert!(data.iter().all(|&b| b == 0));
1818 file.close().unwrap();
1819 }
1820
1821 std::fs::remove_file(&path).ok();
1822 }
1823
1824 #[test]
1825 fn close_no_sync_produces_valid_readable_file() {
1826 let path = temp_path("close_no_sync_rt");
1827 let payload: Vec<u8> = (0u8..16).collect();
1828
1829 {
1831 let file = H5File::create(&path).unwrap();
1832 let ds = file
1833 .new_dataset::<u8>()
1834 .shape([4, 4])
1835 .create("data")
1836 .unwrap();
1837 ds.write_raw(&payload).unwrap();
1838 file.close_no_sync().unwrap();
1841 }
1842
1843 {
1846 let file = H5File::open(&path).unwrap();
1847 let ds = file.dataset("data").unwrap();
1848 assert_eq!(ds.shape(), vec![4, 4]);
1849 let data = ds.read_raw::<u8>().unwrap();
1850 assert_eq!(data, payload);
1851 file.close().unwrap();
1852 }
1853
1854 std::fs::remove_file(&path).ok();
1855 }
1856
1857 #[test]
1858 fn create_over_existing_file_truncates() {
1859 let path = temp_path("create_truncates");
1865
1866 {
1867 let file = H5File::create(&path).unwrap();
1868 let ds = file
1869 .new_dataset::<u8>()
1870 .shape([4, 4])
1871 .create("old_data")
1872 .unwrap();
1873 ds.write_raw(&[7u8; 16]).unwrap();
1874 file.close().unwrap();
1875 }
1876 assert!(std::fs::metadata(&path).unwrap().len() > 0);
1877
1878 {
1880 let file = H5File::create(&path).unwrap();
1881 file.close().unwrap();
1882 }
1883
1884 let file = H5File::open(&path).unwrap();
1886 assert!(file.dataset("old_data").is_err());
1887 file.close().unwrap();
1888
1889 std::fs::remove_file(&path).ok();
1890 }
1891
1892 #[test]
1893 fn close_no_sync_chunked_dataset_valid() {
1894 let path = temp_path("close_no_sync_chunked");
1899
1900 {
1901 let file = H5File::create(&path).unwrap();
1902 let ds = file
1903 .new_dataset::<i32>()
1904 .shape([0usize, 3])
1905 .chunk(&[1, 3])
1906 .max_shape(&[None, Some(3)])
1907 .create("data")
1908 .unwrap();
1909 for frame in 0..10u64 {
1911 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
1912 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
1913 ds.write_chunk(frame as usize, &raw).unwrap();
1914 }
1915 ds.extend(&[10, 3]).unwrap();
1916 file.close_no_sync().unwrap();
1917 }
1918
1919 {
1920 let file = H5File::open(&path).unwrap();
1921 let ds = file.dataset("data").unwrap();
1922 assert_eq!(ds.shape(), vec![10, 3]);
1923 let data = ds.read_raw::<i32>().unwrap();
1924 let expected: Vec<i32> = (0..30).collect();
1925 assert_eq!(data, expected);
1926 file.close().unwrap();
1927 }
1928
1929 std::fs::remove_file(&path).ok();
1930 }
1931
1932 #[test]
1933 fn write_and_read_f64() {
1934 let path = temp_path("write_read_f64");
1935
1936 let values: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1937
1938 {
1940 let file = H5File::create(&path).unwrap();
1941 let ds = file
1942 .new_dataset::<f64>()
1943 .shape([2, 3])
1944 .create("matrix")
1945 .unwrap();
1946 ds.write_raw(&values).unwrap();
1947 file.close().unwrap();
1948 }
1949
1950 {
1952 let file = H5File::open(&path).unwrap();
1953 let ds = file.dataset("matrix").unwrap();
1954 assert_eq!(ds.shape(), vec![2, 3]);
1955 let readback = ds.read_raw::<f64>().unwrap();
1956 assert_eq!(readback, values);
1957 }
1958
1959 std::fs::remove_file(&path).ok();
1960 }
1961
1962 #[test]
1963 fn multiple_datasets() {
1964 let path = temp_path("multi_ds");
1965
1966 {
1967 let file = H5File::create(&path).unwrap();
1968 let ds1 = file.new_dataset::<i32>().shape([3]).create("ints").unwrap();
1969 ds1.write_raw(&[10i32, 20, 30]).unwrap();
1970
1971 let ds2 = file
1972 .new_dataset::<f32>()
1973 .shape([2, 2])
1974 .create("floats")
1975 .unwrap();
1976 ds2.write_raw(&[1.0f32, 2.0, 3.0, 4.0]).unwrap();
1977
1978 file.close().unwrap();
1979 }
1980
1981 {
1982 let file = H5File::open(&path).unwrap();
1983
1984 let ds_ints = file.dataset("ints").unwrap();
1985 assert_eq!(ds_ints.shape(), vec![3]);
1986 let ints = ds_ints.read_raw::<i32>().unwrap();
1987 assert_eq!(ints, vec![10, 20, 30]);
1988
1989 let ds_floats = file.dataset("floats").unwrap();
1990 assert_eq!(ds_floats.shape(), vec![2, 2]);
1991 let floats = ds_floats.read_raw::<f32>().unwrap();
1992 assert_eq!(floats, vec![1.0f32, 2.0, 3.0, 4.0]);
1993 }
1994
1995 std::fs::remove_file(&path).ok();
1996 }
1997
1998 #[test]
1999 fn close_is_idempotent() {
2000 let path = temp_path("close_idemp");
2001 let file = H5File::create(&path).unwrap();
2002 file.close().unwrap();
2003 std::fs::remove_file(&path).ok();
2005 }
2006}
2007
2008#[cfg(test)]
2009mod integration_tests {
2010 use super::*;
2011
2012 fn temp_path(name: &str) -> std::path::PathBuf {
2013 super::unique_test_path(name)
2014 }
2015
2016 #[test]
2017 fn write_file_for_h5dump() {
2018 let path = temp_path("integration");
2019 let file = H5File::create(&path).unwrap();
2020
2021 let ds = file
2022 .new_dataset::<u8>()
2023 .shape([4usize, 4])
2024 .create("data_u8")
2025 .unwrap();
2026 let data: Vec<u8> = (0..16).collect();
2027 ds.write_raw(&data).unwrap();
2028
2029 let ds2 = file
2030 .new_dataset::<f64>()
2031 .shape([3usize, 2])
2032 .create("data_f64")
2033 .unwrap();
2034 let fdata: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
2035 ds2.write_raw(&fdata).unwrap();
2036
2037 let ds3 = file
2038 .new_dataset::<i32>()
2039 .shape([5usize])
2040 .create("values")
2041 .unwrap();
2042 let idata: Vec<i32> = vec![-10, -5, 0, 5, 10];
2043 ds3.write_raw(&idata).unwrap();
2044
2045 file.close().unwrap();
2046
2047 assert!(path.exists());
2049 }
2050
2051 #[test]
2052 fn write_chunked_file_for_h5dump() {
2053 let path = temp_path("chunked");
2054 let file = H5File::create(&path).unwrap();
2055
2056 let ds = file
2058 .new_dataset::<f64>()
2059 .shape([0usize, 4])
2060 .chunk(&[1, 4])
2061 .max_shape(&[None, Some(4)])
2062 .create("streaming_data")
2063 .unwrap();
2064
2065 for frame in 0..5u64 {
2067 let values: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
2068 let raw: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
2069 ds.write_chunk(frame as usize, &raw).unwrap();
2070 }
2071
2072 ds.extend(&[5, 4]).unwrap();
2074 ds.flush().unwrap();
2075
2076 file.close().unwrap();
2077
2078 assert!(path.exists());
2079 }
2080
2081 #[test]
2082 fn write_chunked_many_frames_for_h5dump() {
2083 let path = temp_path("chunked_many");
2084 let file = H5File::create(&path).unwrap();
2085
2086 let ds = file
2087 .new_dataset::<i32>()
2088 .shape([0usize, 3])
2089 .chunk(&[1, 3])
2090 .max_shape(&[None, Some(3)])
2091 .create("data")
2092 .unwrap();
2093
2094 for frame in 0..10u64 {
2096 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2097 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2098 ds.write_chunk(frame as usize, &raw).unwrap();
2099 }
2100 ds.extend(&[10, 3]).unwrap();
2101 file.close().unwrap();
2102
2103 assert!(path.exists());
2104 }
2105
2106 #[test]
2107 fn write_dataset_with_attributes() {
2108 use crate::types::VarLenUnicode;
2109
2110 let path = temp_path("attributes");
2111 let file = H5File::create(&path).unwrap();
2112
2113 let ds = file
2114 .new_dataset::<f32>()
2115 .shape([10usize])
2116 .create("temperature")
2117 .unwrap();
2118 let data: Vec<f32> = (0..10).map(|i| i as f32 * 1.5).collect();
2119 ds.write_raw(&data).unwrap();
2120
2121 let attr = ds
2123 .new_attr::<VarLenUnicode>()
2124 .shape(())
2125 .create("units")
2126 .unwrap();
2127 attr.write_scalar(&VarLenUnicode("kelvin".to_string()))
2128 .unwrap();
2129
2130 let attr2 = ds
2131 .new_attr::<VarLenUnicode>()
2132 .shape(())
2133 .create("description")
2134 .unwrap();
2135 attr2
2136 .write_scalar(&VarLenUnicode("Temperature measurements".to_string()))
2137 .unwrap();
2138
2139 let attr3 = ds
2141 .new_attr::<VarLenUnicode>()
2142 .shape(())
2143 .create("source")
2144 .unwrap();
2145 attr3.write_string("sensor_01").unwrap();
2146
2147 let attr4 = ds
2149 .new_attr::<VarLenUnicode>()
2150 .shape(())
2151 .create("label")
2152 .unwrap();
2153 let s: VarLenUnicode = "test_label".parse().unwrap_or_default();
2154 attr4.write_scalar(&s).unwrap();
2155
2156 file.close().unwrap();
2157
2158 assert!(path.exists());
2159 }
2160
2161 #[test]
2162 fn dataset_writer_reopens_for_attributes() {
2163 let path = temp_path("dataset_writer");
2166 {
2167 let file = H5File::create(&path).unwrap();
2168 {
2169 let ds = file
2170 .new_dataset::<u16>()
2171 .shape([8])
2172 .create("image")
2173 .unwrap();
2174 ds.write_raw(&[0u16; 8]).unwrap();
2175 }
2177
2178 assert!(file.dataset("image").is_err());
2180 let ds = file.dataset_writer("image").unwrap();
2181 assert_eq!(ds.shape(), vec![8]);
2182 ds.new_attr::<i32>()
2183 .shape([3])
2184 .create("NDArrayDimOffset")
2185 .unwrap()
2186 .write_array(&[0i32, 4, 8])
2187 .unwrap();
2188 ds.new_attr::<i32>()
2189 .shape(())
2190 .create("NDUniqueId")
2191 .unwrap()
2192 .write_numeric(&42i32)
2193 .unwrap();
2194
2195 assert!(matches!(
2197 file.dataset_writer("nope"),
2198 Err(crate::error::Hdf5Error::NotFound(_))
2199 ));
2200
2201 file.close().unwrap();
2202 }
2203 {
2204 let file = H5File::open(&path).unwrap();
2205 let ds = file.dataset("image").unwrap();
2206 let off = ds.attr("NDArrayDimOffset").unwrap().read_raw().unwrap();
2207 let got: Vec<i32> = off
2208 .as_chunks::<4>()
2209 .0
2210 .iter()
2211 .map(|b| i32::from_le_bytes(*b))
2212 .collect();
2213 assert_eq!(got, vec![0, 4, 8]);
2214 let uid: i32 = ds.attr("NDUniqueId").unwrap().read_numeric().unwrap();
2215 assert_eq!(uid, 42);
2216 }
2217 std::fs::remove_file(&path).ok();
2218 }
2219
2220 #[test]
2221 fn chunked_write_read_roundtrip() {
2222 let path = temp_path("chunked_roundtrip");
2223
2224 {
2226 let file = H5File::create(&path).unwrap();
2227 let ds = file
2228 .new_dataset::<i32>()
2229 .shape([0usize, 3])
2230 .chunk(&[1, 3])
2231 .max_shape(&[None, Some(3)])
2232 .create("table")
2233 .unwrap();
2234
2235 for frame in 0..8u64 {
2236 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2237 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2238 ds.write_chunk(frame as usize, &raw).unwrap();
2239 }
2240 ds.extend(&[8, 3]).unwrap();
2241 file.close().unwrap();
2242 }
2243
2244 {
2246 let file = H5File::open(&path).unwrap();
2247 let ds = file.dataset("table").unwrap();
2248 assert_eq!(ds.shape(), vec![8, 3]);
2249 let data = ds.read_raw::<i32>().unwrap();
2250 assert_eq!(data.len(), 24);
2251 for (i, val) in data.iter().enumerate() {
2252 assert_eq!(*val, i as i32);
2253 }
2254 }
2255
2256 std::fs::remove_file(&path).ok();
2257 }
2258
2259 #[test]
2260 #[cfg(feature = "deflate")]
2261 fn compressed_chunked_roundtrip() {
2262 let path = temp_path("compressed_roundtrip");
2263
2264 {
2266 let file = H5File::create(&path).unwrap();
2267 let ds = file
2268 .new_dataset::<f64>()
2269 .shape([0usize, 4])
2270 .chunk(&[1, 4])
2271 .max_shape(&[None, Some(4)])
2272 .deflate(6)
2273 .create("compressed")
2274 .unwrap();
2275
2276 for frame in 0..10u64 {
2277 let vals: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
2278 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2279 ds.write_chunk(frame as usize, &raw).unwrap();
2280 }
2281 ds.extend(&[10, 4]).unwrap();
2282 file.close().unwrap();
2283 }
2284
2285 {
2287 let file = H5File::open(&path).unwrap();
2288 let ds = file.dataset("compressed").unwrap();
2289 assert_eq!(ds.shape(), vec![10, 4]);
2290 let data = ds.read_raw::<f64>().unwrap();
2291 assert_eq!(data.len(), 40);
2292 for (i, val) in data.iter().enumerate() {
2293 assert!(
2294 (val - i as f64).abs() < 1e-10,
2295 "mismatch at {}: {} != {}",
2296 i,
2297 val,
2298 i
2299 );
2300 }
2301 }
2302
2303 std::fs::remove_file(&path).ok();
2304 }
2305
2306 #[test]
2307 #[cfg(feature = "deflate")]
2308 fn compressed_chunked_many_frames() {
2309 let path = temp_path("compressed_many");
2310
2311 {
2312 let file = H5File::create(&path).unwrap();
2313 let ds = file
2314 .new_dataset::<i32>()
2315 .shape([0usize, 3])
2316 .chunk(&[1, 3])
2317 .max_shape(&[None, Some(3)])
2318 .deflate(6)
2319 .create("stream")
2320 .unwrap();
2321
2322 for frame in 0..100u64 {
2323 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2324 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2325 ds.write_chunk(frame as usize, &raw).unwrap();
2326 }
2327 ds.extend(&[100, 3]).unwrap();
2328 file.close().unwrap();
2329 }
2330
2331 {
2332 let file = H5File::open(&path).unwrap();
2333 let ds = file.dataset("stream").unwrap();
2334 assert_eq!(ds.shape(), vec![100, 3]);
2335 let data = ds.read_raw::<i32>().unwrap();
2336 assert_eq!(data.len(), 300);
2337 for (i, val) in data.iter().enumerate() {
2338 assert_eq!(*val, i as i32, "mismatch at {}", i);
2339 }
2340 }
2341
2342 std::fs::remove_file(&path).ok();
2343 }
2344 #[test]
2345 fn append_mode() {
2346 let path = temp_path("append");
2347
2348 {
2350 let file = H5File::create(&path).unwrap();
2351 let ds = file
2352 .new_dataset::<i32>()
2353 .shape([3usize])
2354 .create("first")
2355 .unwrap();
2356 ds.write_raw(&[1i32, 2, 3]).unwrap();
2357 file.close().unwrap();
2358 }
2359
2360 {
2362 let file = H5File::open_rw(&path).unwrap();
2363 let ds = file
2364 .new_dataset::<f64>()
2365 .shape([2usize])
2366 .create("second")
2367 .unwrap();
2368 ds.write_raw(&[4.0f64, 5.0]).unwrap();
2369 file.close().unwrap();
2370 }
2371
2372 {
2374 let file = H5File::open(&path).unwrap();
2375 let names = file.dataset_names();
2376 assert!(names.contains(&"first".to_string()));
2377 assert!(names.contains(&"second".to_string()));
2378
2379 let ds1 = file.dataset("first").unwrap();
2380 assert_eq!(ds1.read_raw::<i32>().unwrap(), vec![1, 2, 3]);
2381
2382 let ds2 = file.dataset("second").unwrap();
2383 assert_eq!(ds2.read_raw::<f64>().unwrap(), vec![4.0, 5.0]);
2384 }
2385
2386 std::fs::remove_file(&path).ok();
2387 }
2388
2389 #[test]
2390 fn open_rw_set_attr_preserves_file() {
2391 let path = temp_path("open_rw_attr");
2392 {
2394 let file = H5File::create(&path).unwrap();
2395 let ds = file
2396 .new_dataset::<i32>()
2397 .shape([3usize])
2398 .create("data")
2399 .unwrap();
2400 ds.write_raw(&[10i32, 20, 30]).unwrap();
2401 file.set_attr_string("version", "1.0").unwrap();
2402 file.close().unwrap();
2403 }
2404 {
2406 let file = H5File::open_rw(&path).unwrap();
2407 file.set_attr_string("version", "2.0").unwrap();
2408 file.close().unwrap();
2409 }
2410 {
2412 let file = H5File::open(&path).unwrap();
2413 let ds = file.dataset("data").unwrap();
2414 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![10, 20, 30]);
2415 let ver = file.attr_string("version").unwrap();
2416 assert_eq!(ver, "2.0");
2417 }
2418 std::fs::remove_file(&path).ok();
2419 }
2420
2421 #[test]
2422 #[cfg(feature = "deflate")]
2423 fn open_rw_attr_with_compressed_dataset() {
2424 use crate::format::messages::filter::FilterPipeline;
2425 let path = temp_path("open_rw_compressed");
2426 let input: Vec<&str> = (0..50).map(|_| "test string data").collect();
2427 {
2429 let file = H5File::create(&path).unwrap();
2430 file.write_vlen_strings_compressed("texts", &input, 16, FilterPipeline::deflate(6))
2431 .unwrap();
2432 file.set_attr_string("version", "1.0").unwrap();
2433 file.close().unwrap();
2434 }
2435 {
2437 let file = H5File::open_rw(&path).unwrap();
2438 file.set_attr_string("version", "2.0").unwrap();
2439 file.close().unwrap();
2440 }
2441 {
2443 let file = H5File::open(&path).unwrap();
2444 let ds = file.dataset("texts").unwrap();
2445 let strings = ds.read_vlen_strings().unwrap();
2446 assert_eq!(strings.len(), 50);
2447 assert_eq!(strings[0], "test string data");
2448 let ver = file.attr_string("version").unwrap();
2449 assert_eq!(ver, "2.0");
2450 }
2451 std::fs::remove_file(&path).ok();
2452 }
2453
2454 #[test]
2455 #[cfg(feature = "lz4")]
2456 fn append_vlen_strings_basic() {
2457 use crate::format::messages::filter::FilterPipeline;
2458 let path = temp_path("append_vlen");
2459 {
2460 let file = H5File::create(&path).unwrap();
2461 file.create_appendable_vlen_dataset("names", 4, Some(FilterPipeline::lz4()))
2462 .unwrap();
2463 file.append_vlen_strings("names", &["alice", "bob", "charlie"])
2464 .unwrap();
2465 file.append_vlen_strings("names", &["dave", "eve"]).unwrap();
2466 file.close().unwrap();
2467 }
2468 {
2469 let file = H5File::open(&path).unwrap();
2470 let ds = file.dataset("names").unwrap();
2471 let strings = ds.read_vlen_strings().unwrap();
2472 assert_eq!(strings, vec!["alice", "bob", "charlie", "dave", "eve"]);
2473 }
2474 std::fs::remove_file(&path).ok();
2475 }
2476
2477 #[test]
2478 #[cfg(feature = "lz4")]
2479 fn append_vlen_strings_large() {
2480 use crate::format::messages::filter::FilterPipeline;
2481 let path = temp_path("append_vlen_large");
2482 let batch1: Vec<String> = (0..5000).map(|i| format!("node-{:06}", i)).collect();
2483 let batch2: Vec<String> = (5000..7189).map(|i| format!("node-{:06}", i)).collect();
2484 {
2485 let file = H5File::create(&path).unwrap();
2486 file.create_appendable_vlen_dataset("data", 512, Some(FilterPipeline::lz4()))
2487 .unwrap();
2488 let r1: Vec<&str> = batch1.iter().map(|s| s.as_str()).collect();
2489 file.append_vlen_strings("data", &r1).unwrap();
2490 let r2: Vec<&str> = batch2.iter().map(|s| s.as_str()).collect();
2491 file.append_vlen_strings("data", &r2).unwrap();
2492 file.close().unwrap();
2493 }
2494 {
2495 let file = H5File::open(&path).unwrap();
2496 let ds = file.dataset("data").unwrap();
2497 let strings = ds.read_vlen_strings().unwrap();
2498 assert_eq!(strings.len(), 7189);
2499 assert_eq!(strings[0], "node-000000");
2500 assert_eq!(strings[7188], "node-007188");
2501 }
2502 std::fs::remove_file(&path).ok();
2503 }
2504
2505 #[test]
2506 fn append_vlen_strings_uncompressed() {
2507 let path = temp_path("append_vlen_unc");
2508 {
2509 let file = H5File::create(&path).unwrap();
2510 file.create_appendable_vlen_dataset("texts", 8, None)
2511 .unwrap();
2512 file.append_vlen_strings("texts", &["hello", "world"])
2513 .unwrap();
2514 file.append_vlen_strings("texts", &["foo", "bar", "baz"])
2515 .unwrap();
2516 file.close().unwrap();
2517 }
2518 {
2519 let file = H5File::open(&path).unwrap();
2520 let ds = file.dataset("texts").unwrap();
2521 let strings = ds.read_vlen_strings().unwrap();
2522 assert_eq!(strings, vec!["hello", "world", "foo", "bar", "baz"]);
2523 }
2524 std::fs::remove_file(&path).ok();
2525 }
2526
2527 #[test]
2528 fn delete_dataset_roundtrip() {
2529 let path = temp_path("delete_ds");
2530 {
2531 let file = H5File::create(&path).unwrap();
2532 file.write_vlen_strings("keep", &["a", "b"]).unwrap();
2533 file.write_vlen_strings("remove", &["x", "y"]).unwrap();
2534 file.delete_dataset("remove").unwrap();
2535 file.close().unwrap();
2536 }
2537 {
2538 let file = H5File::open(&path).unwrap();
2539 let names = file.dataset_names();
2540 assert!(names.contains(&"keep".to_string()));
2541 assert!(!names.contains(&"remove".to_string()));
2542 let ds = file.dataset("keep").unwrap();
2543 assert_eq!(ds.read_vlen_strings().unwrap(), vec!["a", "b"]);
2544 }
2545 std::fs::remove_file(&path).ok();
2546 }
2547
2548 #[test]
2549 fn delete_group_roundtrip() {
2550 let path = temp_path("delete_grp");
2551 {
2552 let file = H5File::create(&path).unwrap();
2553 let g1 = file.create_group("keep").unwrap();
2554 g1.write_vlen_strings("data", &["a"]).unwrap();
2555 let g2 = file.create_group("remove").unwrap();
2556 g2.write_vlen_strings("data", &["x"]).unwrap();
2557 file.delete_group("remove").unwrap();
2558 file.close().unwrap();
2559 }
2560 {
2561 let file = H5File::open(&path).unwrap();
2562 let names = file.dataset_names();
2563 assert!(names.contains(&"keep/data".to_string()));
2564 assert!(!names.contains(&"remove/data".to_string()));
2565 }
2566 std::fs::remove_file(&path).ok();
2567 }
2568
2569 #[test]
2570 fn open_rw_delete_recreate_group() {
2571 let path = temp_path("rw_delete_recreate");
2572 {
2574 let file = H5File::create(&path).unwrap();
2575 let n = file.create_group("nodes").unwrap();
2576 n.write_vlen_strings("id", &["a", "b", "c"]).unwrap();
2577 let e = file.create_group("edges").unwrap();
2578 e.write_vlen_strings("src", &["x", "y"]).unwrap();
2579 file.close().unwrap();
2580 }
2581 {
2583 let file = H5File::open_rw(&path).unwrap();
2584 file.delete_group("nodes").unwrap();
2585 let n = file.create_group("nodes").unwrap();
2586 n.write_vlen_strings("id", &["new1", "new2"]).unwrap();
2587 file.close().unwrap();
2588 }
2589 {
2591 let file = H5File::open(&path).unwrap();
2592 let ds = file.dataset("nodes/id").unwrap();
2593 let s = ds.read_vlen_strings().unwrap();
2594 assert_eq!(s, vec!["new1", "new2"]);
2595 let ds = file.dataset("edges/src").unwrap();
2597 let s = ds.read_vlen_strings().unwrap();
2598 assert_eq!(s, vec!["x", "y"]);
2599 }
2600 std::fs::remove_file(&path).ok();
2601 }
2602
2603 #[test]
2604 fn delete_and_recreate_group() {
2605 let path = temp_path("delete_recreate");
2606 {
2607 let file = H5File::create(&path).unwrap();
2608 let g = file.create_group("nodes").unwrap();
2609 g.write_vlen_strings("id", &["old1", "old2"]).unwrap();
2610 file.delete_group("nodes").unwrap();
2611 let g = file.create_group("nodes").unwrap();
2612 g.write_vlen_strings("id", &["new1", "new2", "new3"])
2613 .unwrap();
2614 file.close().unwrap();
2615 }
2616 {
2617 let file = H5File::open(&path).unwrap();
2618 let ds = file.dataset("nodes/id").unwrap();
2619 let strings = ds.read_vlen_strings().unwrap();
2620 assert_eq!(strings, vec!["new1", "new2", "new3"]);
2621 }
2622 std::fs::remove_file(&path).ok();
2623 }
2624
2625 #[test]
2626 #[cfg(feature = "deflate")]
2627 fn vlen_string_compressed_large_roundtrip() {
2628 use crate::format::messages::filter::FilterPipeline;
2629 let path = temp_path("vlen_large");
2630 let input: Vec<String> = (0..7189)
2632 .map(|i| format!("node-{:08x}-{}", i, "a".repeat(20 + (i % 30))))
2633 .collect();
2634 let input_refs: Vec<&str> = input.iter().map(|s| s.as_str()).collect();
2635 {
2636 let file = H5File::create(&path).unwrap();
2637 file.create_group("nodes").unwrap();
2638 file.write_vlen_strings_compressed(
2639 "nodes/id",
2640 &input_refs,
2641 512,
2642 FilterPipeline::deflate(6),
2643 )
2644 .unwrap();
2645 file.close().unwrap();
2646 }
2647 {
2649 let file = H5File::open(&path).unwrap();
2650 let ds = file.dataset("nodes/id").unwrap();
2651 let strings = ds.read_vlen_strings().unwrap();
2652 assert_eq!(strings.len(), 7189);
2653 assert_eq!(strings[0], input[0]);
2654 assert_eq!(strings[7188], input[7188]);
2655 }
2656 {
2658 let file = H5File::open_rw(&path).unwrap();
2659 file.set_attr_string("version", "1.0").unwrap();
2660 file.close().unwrap();
2661 }
2662 {
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 }
2669 std::fs::remove_file(&path).ok();
2670 }
2671
2672 #[test]
2673 fn vlen_string_write_read() {
2674 let path = temp_path("vlen_wr");
2675 {
2676 let file = H5File::create(&path).unwrap();
2677 file.write_vlen_strings("names", &["alice", "bob", "charlie"])
2678 .unwrap();
2679 file.close().unwrap();
2680 }
2681 {
2682 let file = H5File::open(&path).unwrap();
2683 let ds = file.dataset("names").unwrap();
2684 let strings = ds.read_vlen_strings().unwrap();
2685 assert_eq!(strings, vec!["alice", "bob", "charlie"]);
2686 }
2687 std::fs::remove_file(&path).ok();
2688 }
2689
2690 #[test]
2695 fn vlen_string_writers_declare_their_character_set() {
2696 use crate::format::messages::datatype::DatatypeMessage;
2697
2698 let path = temp_path("vlen_cset");
2699 let file = H5File::create(&path).unwrap();
2700 file.write_vlen_strings_ascii("ascii", &["alpha", "b", ""])
2701 .unwrap();
2702 file.write_vlen_strings("utf8", &["été", "日本"]).unwrap();
2703 let err = file
2704 .write_vlen_strings_ascii("rejected", &["ok", "안녕"])
2705 .err()
2706 .expect("a non-ASCII string was accepted under an ASCII datatype")
2707 .to_string();
2708 assert!(
2709 err.contains("string 1") && err.contains("is not ASCII"),
2710 "got: {err}"
2711 );
2712 file.close().unwrap();
2713
2714 let file = H5File::open(&path).unwrap();
2715 let ascii = file.dataset("ascii").unwrap();
2716 assert_eq!(
2717 ascii.datatype().unwrap(),
2718 DatatypeMessage::VarLenString {
2719 padding: 0,
2720 charset: 0,
2721 }
2722 );
2723 assert_eq!(ascii.read_strings().unwrap(), vec!["alpha", "b", ""]);
2724 let utf8 = file.dataset("utf8").unwrap();
2725 assert_eq!(
2726 utf8.datatype().unwrap(),
2727 DatatypeMessage::VarLenString {
2728 padding: 0,
2729 charset: 1,
2730 }
2731 );
2732 assert_eq!(utf8.read_strings().unwrap(), vec!["été", "日本"]);
2733 assert!(file.dataset("rejected").is_err());
2735 std::fs::remove_file(&path).ok();
2736 }
2737
2738 #[test]
2741 fn group_vlen_string_writer_declares_ascii() {
2742 use crate::format::messages::datatype::DatatypeMessage;
2743
2744 let path = temp_path("vlen_cset_group");
2745 let file = H5File::create(&path).unwrap();
2746 let g = file.create_group("entry").unwrap();
2747 g.write_vlen_strings_ascii("notes", &["alpha", "b"])
2748 .unwrap();
2749 let err = g
2750 .write_vlen_strings_ascii("rejected", &["안녕"])
2751 .err()
2752 .expect("a non-ASCII string was accepted under an ASCII datatype")
2753 .to_string();
2754 assert!(err.contains("is not ASCII"), "got: {err}");
2755 file.close().unwrap();
2756
2757 let file = H5File::open(&path).unwrap();
2758 let ds = file.dataset("entry/notes").unwrap();
2759 assert_eq!(
2760 ds.datatype().unwrap(),
2761 DatatypeMessage::VarLenString {
2762 padding: 0,
2763 charset: 0,
2764 }
2765 );
2766 assert_eq!(ds.read_strings().unwrap(), vec!["alpha", "b"]);
2767 std::fs::remove_file(&path).ok();
2768 }
2769
2770 #[test]
2771 fn vlen_bytes_write_read() {
2772 let path = temp_path("vlen_bytes_wr");
2773 let items: [&[u8]; 4] = [b"abc", b"", &[0u8, 1, 2, 255], b"hi"];
2774 {
2775 let file = H5File::create(&path).unwrap();
2776 file.write_vlen_bytes("blobs", &items).unwrap();
2777 file.close().unwrap();
2778 }
2779 {
2780 let file = H5File::open(&path).unwrap();
2781 let ds = file.dataset("blobs").unwrap();
2782 let got = ds.read_vlen_bytes().unwrap();
2783 let expected: Vec<Vec<u8>> = items.iter().map(|s| s.to_vec()).collect();
2784 assert_eq!(got, expected);
2785 }
2786 std::fs::remove_file(&path).ok();
2787 }
2788
2789 #[test]
2793 fn vlen_numeric_write_read() {
2794 use crate::format::global_heap::decode_vlen_reference;
2795 use crate::format::messages::datatype::DatatypeMessage;
2796
2797 let path = temp_path("vlen_numeric_wr");
2798 let a: &[i32] = &[1, 2, 3];
2799 let b: &[i32] = &[];
2800 let c: &[i32] = &[-7];
2801 {
2802 let file = H5File::create(&path).unwrap();
2803 file.write_vlen_numeric("data", &[a, b, c]).unwrap();
2804 let f64s: &[f64] = &[1.5, -2.5];
2805 file.write_vlen_numeric("wide", &[f64s]).unwrap();
2806 file.close().unwrap();
2807 }
2808 let file = H5File::open(&path).unwrap();
2809 let ds = file.dataset("data").unwrap();
2810 assert_eq!(
2811 ds.datatype().unwrap(),
2812 DatatypeMessage::VarLenSequence {
2813 base: Box::new(DatatypeMessage::i32_type()),
2814 }
2815 );
2816 let decoded: Vec<Vec<i32>> = ds
2817 .read_vlen_bytes()
2818 .unwrap()
2819 .iter()
2820 .map(|item| {
2821 item.as_chunks::<4>()
2822 .0
2823 .iter()
2824 .map(|w| i32::from_le_bytes(*w))
2825 .collect()
2826 })
2827 .collect();
2828 assert_eq!(decoded, vec![a.to_vec(), b.to_vec(), c.to_vec()]);
2829
2830 let ctx = crate::format::FormatContext::default_v3();
2832 let raw = ds.read_raw_bytes().unwrap();
2833 let (seq_len, _, _) = decode_vlen_reference(&raw, &ctx).unwrap();
2834 assert_eq!(seq_len, 3);
2835
2836 let wide = file.dataset("wide").unwrap();
2837 assert_eq!(
2838 wide.datatype().unwrap(),
2839 DatatypeMessage::VarLenSequence {
2840 base: Box::new(DatatypeMessage::f64_type()),
2841 }
2842 );
2843 let (seq_len, _, _) = decode_vlen_reference(&wide.read_raw_bytes().unwrap(), &ctx).unwrap();
2844 assert_eq!(seq_len, 2);
2845 drop(file);
2846 std::fs::remove_file(&path).ok();
2847 }
2848
2849 #[test]
2852 fn vlen_bytes_is_the_u8_case_of_vlen_numeric() {
2853 use crate::format::messages::datatype::DatatypeMessage;
2854
2855 let path = temp_path("vlen_bytes_u8");
2856 let items: [&[u8]; 2] = [b"abc", b""];
2857 {
2858 let file = H5File::create(&path).unwrap();
2859 file.write_vlen_bytes("blobs", &items).unwrap();
2860 file.close().unwrap();
2861 }
2862 let file = H5File::open(&path).unwrap();
2863 let ds = file.dataset("blobs").unwrap();
2864 assert_eq!(ds.datatype().unwrap(), DatatypeMessage::vlen_bytes());
2865 let ctx = crate::format::FormatContext::default_v3();
2866 let (seq_len, _, _) =
2867 crate::format::global_heap::decode_vlen_reference(&ds.read_raw_bytes().unwrap(), &ctx)
2868 .unwrap();
2869 assert_eq!(seq_len, 3);
2870 drop(file);
2871 std::fs::remove_file(&path).ok();
2872 }
2873
2874 #[test]
2875 fn vlen_bytes_in_group_with_attribute() {
2876 use crate::types::VarLenUnicode;
2877 let path = temp_path("vlen_bytes_grp");
2878 let items: [&[u8]; 2] = [&[1u8, 2, 3], &[9u8, 8, 7, 6]];
2879 {
2880 let file = H5File::create(&path).unwrap();
2881 let grp = file.root_group().create_group("payloads").unwrap();
2882 let ds = grp.write_vlen_bytes("frames", &items).unwrap();
2883 ds.new_attr::<VarLenUnicode>()
2884 .shape(())
2885 .create("codec")
2886 .unwrap()
2887 .write_string("raw")
2888 .unwrap();
2889 file.close().unwrap();
2890 }
2891 {
2892 let file = H5File::open(&path).unwrap();
2893 let ds = file.dataset("payloads/frames").unwrap();
2894 let got = ds.read_vlen_bytes().unwrap();
2895 let expected: Vec<Vec<u8>> = items.iter().map(|s| s.to_vec()).collect();
2896 assert_eq!(got, expected);
2897 }
2898 std::fs::remove_file(&path).ok();
2899 }
2900
2901 #[test]
2902 fn vlen_dataset_returns_handle_for_attributes() {
2903 use crate::types::VarLenUnicode;
2904 let path = temp_path("vlen_attr");
2905 {
2906 let file = H5File::create(&path).unwrap();
2907 let grp = file.root_group().create_group("ch").unwrap();
2908 let ds = grp
2911 .write_vlen_strings("labels", &["a", "bb", "ccc"])
2912 .unwrap();
2913 ds.new_attr::<VarLenUnicode>()
2914 .shape(())
2915 .create("unit")
2916 .unwrap()
2917 .write_string("volt")
2918 .unwrap();
2919 let ds2 = grp.dataset_writer("labels").unwrap();
2921 ds2.new_attr::<VarLenUnicode>()
2922 .shape(())
2923 .create("desc")
2924 .unwrap()
2925 .write_string("channel labels")
2926 .unwrap();
2927 file.close().unwrap();
2928 }
2929 {
2930 let file = H5File::open(&path).unwrap();
2931 let ds = file.dataset("ch/labels").unwrap();
2932 assert_eq!(ds.read_vlen_strings().unwrap(), vec!["a", "bb", "ccc"]);
2933 assert_eq!(ds.attr("unit").unwrap().read_string().unwrap(), "volt");
2934 assert_eq!(
2935 ds.attr("desc").unwrap().read_string().unwrap(),
2936 "channel labels"
2937 );
2938 }
2939 std::fs::remove_file(&path).ok();
2940 }
2941
2942 #[test]
2943 #[cfg(feature = "deflate")]
2944 fn vlen_string_deflate_roundtrip() {
2945 use crate::format::messages::filter::FilterPipeline;
2946 let path = temp_path("vlen_deflate");
2947 let input: Vec<&str> = (0..100)
2948 .map(|i| match i % 3 {
2949 0 => "hello world",
2950 1 => "compressed vlen string test",
2951 _ => "rust-hdf5",
2952 })
2953 .collect();
2954 {
2955 let file = H5File::create(&path).unwrap();
2956 file.write_vlen_strings_compressed("texts", &input, 16, FilterPipeline::deflate(6))
2957 .unwrap();
2958 file.close().unwrap();
2959 }
2960 {
2961 let file = H5File::open(&path).unwrap();
2962 let ds = file.dataset("texts").unwrap();
2963 let strings = ds.read_vlen_strings().unwrap();
2964 assert_eq!(strings.len(), 100);
2965 for (i, s) in strings.iter().enumerate() {
2966 assert_eq!(s, input[i]);
2967 }
2968 }
2969 std::fs::remove_file(&path).ok();
2970 }
2971
2972 #[test]
2973 #[cfg(feature = "zstd")]
2974 fn vlen_string_zstd_roundtrip() {
2975 use crate::format::messages::filter::FilterPipeline;
2976 let path = temp_path("vlen_zstd");
2977 let input: Vec<&str> = (0..200)
2978 .map(|i| match i % 4 {
2979 0 => "zstandard compression test",
2980 1 => "variable length string",
2981 2 => "rust-hdf5 chunked storage",
2982 _ => "hello zstd world",
2983 })
2984 .collect();
2985 {
2986 let file = H5File::create(&path).unwrap();
2987 file.write_vlen_strings_compressed("data", &input, 32, FilterPipeline::zstd(3))
2988 .unwrap();
2989 file.close().unwrap();
2990 }
2991 {
2992 let file = H5File::open(&path).unwrap();
2993 let ds = file.dataset("data").unwrap();
2994 let strings = ds.read_vlen_strings().unwrap();
2995 assert_eq!(strings.len(), 200);
2996 for (i, s) in strings.iter().enumerate() {
2997 assert_eq!(s, input[i]);
2998 }
2999 }
3000 std::fs::remove_file(&path).ok();
3001 }
3002
3003 #[test]
3004 #[cfg(feature = "deflate")]
3005 fn shuffle_deflate_roundtrip() {
3006 let path = temp_path("shuf_defl");
3007 {
3008 let file = H5File::create(&path).unwrap();
3009 let ds = file
3010 .new_dataset::<f64>()
3011 .shape([0usize, 4])
3012 .chunk(&[1, 4])
3013 .max_shape(&[None, Some(4)])
3014 .shuffle_deflate(6)
3015 .create("data")
3016 .unwrap();
3017 for frame in 0..20u64 {
3018 let vals: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
3019 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3020 ds.write_chunk(frame as usize, &raw).unwrap();
3021 }
3022 ds.extend(&[20, 4]).unwrap();
3023 file.close().unwrap();
3024 }
3025 {
3026 let file = H5File::open(&path).unwrap();
3027 let ds = file.dataset("data").unwrap();
3028 assert_eq!(ds.shape(), vec![20, 4]);
3029 let data = ds.read_raw::<f64>().unwrap();
3030 assert_eq!(data.len(), 80);
3031 for (i, val) in data.iter().enumerate() {
3032 assert!((val - i as f64).abs() < 1e-10);
3033 }
3034 }
3035 std::fs::remove_file(&path).ok();
3036 }
3037
3038 #[test]
3039 fn file_level_attributes() {
3040 let path = temp_path("file_attr");
3041 {
3042 let file = H5File::create(&path).unwrap();
3043 file.set_attr_string("title", "Test File").unwrap();
3044 file.set_attr_numeric("version", &42i32).unwrap();
3045 let ds = file
3046 .new_dataset::<u8>()
3047 .shape([1usize])
3048 .create("dummy")
3049 .unwrap();
3050 ds.write_raw(&[0u8]).unwrap();
3051 file.close().unwrap();
3052 }
3053 {
3054 let file = H5File::open(&path).unwrap();
3055 assert!(file.dataset_names().contains(&"dummy".to_string()));
3056
3057 let names = file.attr_names().unwrap();
3059 assert!(names.contains(&"title".to_string()));
3060
3061 let title = file.attr_string("title").unwrap();
3062 assert_eq!(title, "Test File");
3063 }
3064 std::fs::remove_file(&path).ok();
3065 }
3066
3067 #[test]
3068 fn scalar_dataset_roundtrip() {
3069 let path = temp_path("scalar");
3070 {
3071 let file = H5File::create(&path).unwrap();
3072 let ds = file.new_dataset::<f64>().scalar().create("pi").unwrap();
3073 ds.write_raw(&[std::f64::consts::PI]).unwrap();
3074 file.close().unwrap();
3075 }
3076 {
3077 let file = H5File::open(&path).unwrap();
3078 let ds = file.dataset("pi").unwrap();
3079 assert_eq!(ds.shape(), Vec::<usize>::new());
3080 assert_eq!(ds.total_elements(), 1);
3081 let data = ds.read_raw::<f64>().unwrap();
3082 assert_eq!(data.len(), 1);
3083 assert!((data[0] - std::f64::consts::PI).abs() < 1e-15);
3084 }
3085 std::fs::remove_file(&path).ok();
3086 }
3087
3088 #[test]
3089 fn append_mode_extend_chunked() {
3090 let path = temp_path("append_extend");
3091
3092 {
3094 let file = H5File::create(&path).unwrap();
3095 let ds = file
3096 .new_dataset::<i32>()
3097 .shape([0usize, 3])
3098 .chunk(&[1, 3])
3099 .max_shape(&[None, Some(3)])
3100 .create("stream")
3101 .unwrap();
3102 for i in 0..5u64 {
3103 let vals: Vec<i32> = (0..3).map(|j| (i * 3 + j) as i32).collect();
3104 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3105 ds.write_chunk(i as usize, &raw).unwrap();
3106 }
3107 ds.extend(&[5, 3]).unwrap();
3108 file.close().unwrap();
3109 }
3110
3111 {
3113 let file = H5File::open_rw(&path).unwrap();
3114 let names = file.dataset_names();
3116 assert!(names.contains(&"stream".to_string()));
3117
3118 let mut inner = crate::file::borrow_inner_mut(&file.inner);
3120 if let crate::file::H5FileInner::Writer(writer) = &mut *inner {
3121 let ds_idx = writer.dataset_index("stream").unwrap();
3122 for i in 5..10u64 {
3123 let vals: Vec<i32> = (0..3).map(|j| (i * 3 + j) as i32).collect();
3124 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3125 writer.write_chunk(ds_idx, i, &raw).unwrap();
3126 }
3127 writer.extend_dataset(ds_idx, &[10, 3]).unwrap();
3128 }
3129 drop(inner);
3130 file.close().unwrap();
3131 }
3132
3133 {
3135 let file = H5File::open(&path).unwrap();
3136 let ds = file.dataset("stream").unwrap();
3137 assert_eq!(ds.shape(), vec![10, 3]);
3138 let data = ds.read_raw::<i32>().unwrap();
3139 assert_eq!(data.len(), 30);
3140 for (i, val) in data.iter().enumerate() {
3141 assert_eq!(*val, i as i32, "mismatch at {}", i);
3142 }
3143 }
3144
3145 std::fs::remove_file(&path).ok();
3146 }
3147
3148 #[test]
3149 fn group_hierarchy_roundtrip() {
3150 let path = temp_path("groups_rt");
3151
3152 {
3153 let file = H5File::create(&path).unwrap();
3154 let root = file.root_group();
3155
3156 let det = root.create_group("detector").unwrap();
3158 let raw = det.create_group("raw").unwrap();
3159
3160 let ds1 = det
3162 .new_dataset::<f32>()
3163 .shape([10usize])
3164 .create("temperature")
3165 .unwrap();
3166 ds1.write_raw(&[1.0f32; 10]).unwrap();
3167
3168 let ds2 = raw
3169 .new_dataset::<u16>()
3170 .shape([4usize, 4])
3171 .create("image")
3172 .unwrap();
3173 ds2.write_raw(&[42u16; 16]).unwrap();
3174
3175 let ds3 = file
3177 .new_dataset::<i32>()
3178 .shape([3usize])
3179 .create("version")
3180 .unwrap();
3181 ds3.write_raw(&[1i32, 0, 0]).unwrap();
3182
3183 file.close().unwrap();
3184 }
3185
3186 {
3187 let file = H5File::open(&path).unwrap();
3188 let names = file.dataset_names();
3189 assert!(names.contains(&"version".to_string()));
3190 assert!(names.contains(&"detector/temperature".to_string()));
3191 assert!(names.contains(&"detector/raw/image".to_string()));
3192
3193 let ds = file.dataset("version").unwrap();
3195 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![1, 0, 0]);
3196
3197 let ds = file.dataset("detector/temperature").unwrap();
3198 assert_eq!(ds.read_raw::<f32>().unwrap(), vec![1.0f32; 10]);
3199
3200 let ds = file.dataset("detector/raw/image").unwrap();
3201 assert_eq!(ds.shape(), vec![4, 4]);
3202 assert_eq!(ds.read_raw::<u16>().unwrap(), vec![42u16; 16]);
3203
3204 let root = file.root_group();
3206 let group_names = root.group_names().unwrap();
3207 assert!(group_names.contains(&"detector".to_string()));
3208 }
3209
3210 std::fs::remove_file(&path).ok();
3211 }
3212
3213 #[test]
3214 fn nested_groups_via_file_create_group() {
3215 let path = temp_path("file_create_group");
3216
3217 {
3218 let file = H5File::create(&path).unwrap();
3219
3220 let grp = file.create_group("sensors").unwrap();
3222 let sub = grp.create_group("accel").unwrap();
3223
3224 let ds = sub
3225 .new_dataset::<f64>()
3226 .shape([3usize])
3227 .create("xyz")
3228 .unwrap();
3229 ds.write_raw(&[1.0f64, 2.0, 3.0]).unwrap();
3230
3231 file.close().unwrap();
3232 }
3233
3234 {
3235 let file = H5File::open(&path).unwrap();
3236 let names = file.dataset_names();
3237 assert!(names.contains(&"sensors/accel/xyz".to_string()));
3238
3239 let ds = file.dataset("sensors/accel/xyz").unwrap();
3240 assert_eq!(ds.read_raw::<f64>().unwrap(), vec![1.0, 2.0, 3.0]);
3241
3242 let root = file.root_group();
3244 let sensors = root.group("sensors").unwrap();
3245 assert_eq!(sensors.name(), "/sensors");
3246
3247 let accel = sensors.group("accel").unwrap();
3248 assert_eq!(accel.name(), "/sensors/accel");
3249
3250 let top_groups = root.group_names().unwrap();
3252 assert!(top_groups.contains(&"sensors".to_string()));
3253
3254 let sub_groups = sensors.group_names().unwrap();
3256 assert!(sub_groups.contains(&"accel".to_string()));
3257 }
3258
3259 std::fs::remove_file(&path).ok();
3260 }
3261}
3262
3263#[cfg(test)]
3264mod h5py_compat_tests {
3265 use super::*;
3266
3267 fn temp_path(name: &str) -> std::path::PathBuf {
3268 super::unique_test_path(name)
3269 }
3270
3271 #[test]
3273 #[cfg(feature = "deflate")]
3274 fn h5dump_validates_our_files() {
3275 let h5dump = std::process::Command::new("h5dump")
3277 .arg("--version")
3278 .output();
3279 if h5dump.is_err() {
3280 eprintln!("skipping: h5dump not found");
3281 return;
3282 }
3283
3284 let path = temp_path("h5dump_validate");
3285
3286 {
3288 let file = H5File::create(&path).unwrap();
3289
3290 let ds = file
3292 .new_dataset::<f64>()
3293 .shape([3usize, 4])
3294 .create("matrix")
3295 .unwrap();
3296 let data: Vec<f64> = (0..12).map(|i| i as f64).collect();
3297 ds.write_raw(&data).unwrap();
3298
3299 let ds2 = file
3301 .new_dataset::<i32>()
3302 .shape([0usize, 2])
3303 .chunk(&[1, 2])
3304 .max_shape(&[None, Some(2)])
3305 .deflate(6)
3306 .create("stream")
3307 .unwrap();
3308 for i in 0..5u64 {
3309 let vals: Vec<i32> = vec![i as i32 * 2, i as i32 * 2 + 1];
3310 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3311 ds2.write_chunk(i as usize, &raw).unwrap();
3312 }
3313 ds2.extend(&[5, 2]).unwrap();
3314
3315 let grp = file.create_group("meta").unwrap();
3317 let ds3 = grp
3318 .new_dataset::<u8>()
3319 .shape([4usize])
3320 .create("flags")
3321 .unwrap();
3322 ds3.write_raw(&[1u8, 0, 1, 0]).unwrap();
3323
3324 use crate::types::VarLenUnicode;
3326 let attr = ds
3327 .new_attr::<VarLenUnicode>()
3328 .shape(())
3329 .create("units")
3330 .unwrap();
3331 attr.write_string("meters").unwrap();
3332
3333 file.close().unwrap();
3334 }
3335
3336 let output = std::process::Command::new("h5dump")
3338 .arg("-H") .arg(path.to_str().unwrap())
3340 .output()
3341 .unwrap();
3342
3343 assert!(
3344 output.status.success(),
3345 "h5dump failed:\nstdout: {}\nstderr: {}",
3346 String::from_utf8_lossy(&output.stdout),
3347 String::from_utf8_lossy(&output.stderr),
3348 );
3349
3350 let output2 = std::process::Command::new("h5dump")
3352 .arg(path.to_str().unwrap())
3353 .output()
3354 .unwrap();
3355
3356 assert!(
3357 output2.status.success(),
3358 "h5dump (full) failed:\nstderr: {}",
3359 String::from_utf8_lossy(&output2.stderr),
3360 );
3361
3362 std::fs::remove_file(&path).ok();
3363 }
3364
3365 #[test]
3366 fn read_h5py_generated_file() {
3367 let path = "/tmp/test_h5py_default.h5";
3368 if !std::path::Path::new(path).exists() {
3369 eprintln!("skipping: h5py test file not found");
3370 return;
3371 }
3372 let file = H5File::open(path).unwrap();
3373
3374 let ds = file.dataset("data").unwrap();
3375 assert_eq!(ds.shape(), vec![4, 5]);
3376 let data = ds.read_raw::<f64>().unwrap();
3377 assert_eq!(data.len(), 20);
3378 assert!((data[0]).abs() < 1e-10);
3379 assert!((data[19] - 19.0).abs() < 1e-10);
3380
3381 let ds2 = file.dataset("images").unwrap();
3382 assert_eq!(ds2.shape(), vec![3, 64, 64]);
3383 let images = ds2.read_raw::<u16>().unwrap();
3384 assert_eq!(images.len(), 3 * 64 * 64);
3385 }
3386
3387 #[test]
3392 fn open_rw_refuses_every_create_only_option() {
3393 let path = temp_path("open_rw_refuses");
3394 H5File::create(&path).unwrap().close().unwrap();
3395
3396 let err = H5File::options()
3397 .track_order(true)
3398 .open_rw(&path)
3399 .err()
3400 .unwrap();
3401 assert!(err.to_string().contains("track_order"), "{err}");
3402
3403 let err = H5File::options()
3404 .libver(LibverBound::V110)
3405 .open_rw(&path)
3406 .err()
3407 .unwrap();
3408 assert!(err.to_string().contains("libver"), "{err}");
3409
3410 for bound in [
3415 LibverBound::Earliest,
3416 LibverBound::V18,
3417 LibverBound::V112,
3418 LibverBound::V114,
3419 LibverBound::V200,
3420 ] {
3421 let err = H5File::options()
3422 .libver(bound)
3423 .open_rw(&path)
3424 .err()
3425 .unwrap();
3426 assert!(err.to_string().contains("libver"), "{bound:?}: {err}");
3427 }
3428
3429 let err = H5File::options()
3430 .userblock(512)
3431 .open_rw(&path)
3432 .err()
3433 .unwrap();
3434 assert!(err.to_string().contains("userblock"), "{err}");
3435
3436 let types = crate::format::sohm::type_flag(crate::format::messages::MSG_DATATYPE).unwrap();
3437 let err = H5File::options()
3438 .shared_messages(&[(types, 0)], 50, 40)
3439 .open_rw(&path)
3440 .err()
3441 .unwrap();
3442 assert!(err.to_string().contains("shared_messages"), "{err}");
3443
3444 H5File::options().open_rw(&path).unwrap().close().unwrap();
3446
3447 std::fs::remove_file(&path).ok();
3448 }
3449
3450 #[test]
3453 fn open_refuses_a_create_only_option() {
3454 let path = temp_path("open_refuses");
3455 H5File::create(&path).unwrap().close().unwrap();
3456
3457 let err = H5File::options()
3458 .track_order(true)
3459 .open(&path)
3460 .err()
3461 .unwrap();
3462 assert!(err.to_string().contains("track_order"), "{err}");
3463
3464 H5File::options().open(&path).unwrap().close().unwrap();
3465
3466 std::fs::remove_file(&path).ok();
3467 }
3468}