1use std::path::Path;
20
21use crate::format::messages::superblock_ext::FileSpaceStrategy;
22use crate::io::locking::FileLocking;
23use crate::io::reader::SuperblockExtension;
24use crate::io::writer::{FileSpaceConfig, SharedMessageConfig};
25use crate::io::{Hdf5Reader, Hdf5Writer};
26
27use crate::dataset::{DatasetAccess, DatasetBuilder, H5Dataset};
28use crate::error::{Hdf5Error, Result};
29use crate::format::messages::datatype::{DatatypeMessage, DatatypeNodeVersion};
30use crate::format::messages::filter::FilterPipeline;
31use crate::format::messages::shared::MessageStorage;
32use crate::format::LibverBound;
33use crate::group::H5Group;
34use crate::types::H5Type;
35
36#[cfg(not(feature = "threadsafe"))]
42pub(crate) type SharedInner = std::rc::Rc<std::cell::RefCell<H5FileInner>>;
43
44#[cfg(feature = "threadsafe")]
45pub(crate) type SharedInner = std::sync::Arc<std::sync::RwLock<H5FileInner>>;
46
47#[cfg(not(feature = "threadsafe"))]
49pub(crate) fn borrow_inner(inner: &SharedInner) -> std::cell::Ref<'_, H5FileInner> {
50 inner.borrow()
51}
52
53#[cfg(not(feature = "threadsafe"))]
55pub(crate) fn borrow_inner_mut(inner: &SharedInner) -> std::cell::RefMut<'_, H5FileInner> {
56 inner.borrow_mut()
57}
58
59#[cfg(not(feature = "threadsafe"))]
66pub(crate) fn try_borrow_inner_mut(
67 inner: &SharedInner,
68) -> Option<std::cell::RefMut<'_, H5FileInner>> {
69 inner.try_borrow_mut().ok()
70}
71
72#[cfg(not(feature = "threadsafe"))]
74pub(crate) fn clone_inner(inner: &SharedInner) -> SharedInner {
75 std::rc::Rc::clone(inner)
76}
77
78#[cfg(not(feature = "threadsafe"))]
80pub(crate) fn new_shared(inner: H5FileInner) -> SharedInner {
81 std::rc::Rc::new(std::cell::RefCell::new(inner))
82}
83
84#[cfg(not(feature = "threadsafe"))]
87pub(crate) fn same_inner(a: &SharedInner, b: &SharedInner) -> bool {
88 std::rc::Rc::ptr_eq(a, b)
89}
90
91#[cfg(feature = "threadsafe")]
96pub(crate) fn borrow_inner(inner: &SharedInner) -> std::sync::RwLockReadGuard<'_, H5FileInner> {
97 inner.read().unwrap()
98}
99
100#[cfg(feature = "threadsafe")]
104pub(crate) fn borrow_inner_mut(
105 inner: &SharedInner,
106) -> std::sync::RwLockWriteGuard<'_, H5FileInner> {
107 inner.write().unwrap()
108}
109
110#[cfg(feature = "threadsafe")]
113pub(crate) fn try_borrow_inner_mut(
114 inner: &SharedInner,
115) -> Option<std::sync::RwLockWriteGuard<'_, H5FileInner>> {
116 inner.write().ok()
117}
118
119#[cfg(feature = "threadsafe")]
122pub(crate) fn same_inner(a: &SharedInner, b: &SharedInner) -> bool {
123 std::sync::Arc::ptr_eq(a, b)
124}
125
126#[cfg(feature = "threadsafe")]
127pub(crate) fn clone_inner(inner: &SharedInner) -> SharedInner {
128 std::sync::Arc::clone(inner)
129}
130
131#[cfg(feature = "threadsafe")]
132pub(crate) fn new_shared(inner: H5FileInner) -> SharedInner {
133 std::sync::Arc::new(std::sync::RwLock::new(inner))
134}
135
136pub(crate) enum H5FileInner {
142 Writer(Box<Hdf5Writer>),
147 Reader(Box<Hdf5Reader>),
148 Closed,
150}
151
152pub struct H5File {
158 pub(crate) inner: SharedInner,
159}
160
161impl H5File {
162 pub fn create<P: AsRef<Path>>(path: P) -> Result<Self> {
164 let writer = Hdf5Writer::create(path.as_ref())?;
165 Ok(Self {
166 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
167 })
168 }
169
170 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
172 let reader = Hdf5Reader::open(path.as_ref())?;
173 Ok(Self {
174 inner: new_shared(H5FileInner::Reader(Box::new(reader))),
175 })
176 }
177
178 pub fn open_rw<P: AsRef<Path>>(path: P) -> Result<Self> {
192 let writer = Hdf5Writer::open_append(path.as_ref())?;
193 Ok(Self {
194 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
195 })
196 }
197
198 pub fn options() -> H5FileOptions {
212 H5FileOptions::default()
213 }
214
215 pub fn set_libver_latest(&self, latest: bool) -> Result<()> {
242 let mut inner = borrow_inner_mut(&self.inner);
243 match &mut *inner {
244 H5FileInner::Writer(writer) => {
245 writer.set_libver_latest(latest)?;
246 Ok(())
247 }
248 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
249 }
250 }
251
252 pub fn set_libver_bound(&self, libver: LibverBound) -> Result<()> {
276 let mut inner = borrow_inner_mut(&self.inner);
277 match &mut *inner {
278 H5FileInner::Writer(writer) => {
279 writer.set_libver_bound(libver)?;
280 Ok(())
281 }
282 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
283 }
284 }
285
286 pub fn set_track_order(&self, track: bool) -> Result<()> {
301 let mut inner = borrow_inner_mut(&self.inner);
302 match &mut *inner {
303 H5FileInner::Writer(writer) => {
304 writer.set_track_order(track);
305 Ok(())
306 }
307 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
308 }
309 }
310
311 pub fn set_track_times(&self, track: bool) -> Result<()> {
324 let mut inner = borrow_inner_mut(&self.inner);
325 match &mut *inner {
326 H5FileInner::Writer(writer) => {
327 writer.set_track_times(track);
328 Ok(())
329 }
330 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
331 }
332 }
333
334 pub fn root_group(&self) -> H5Group {
338 H5Group::new(clone_inner(&self.inner), "/".to_string())
339 }
340
341 pub fn create_group(&self, name: &str) -> Result<H5Group> {
349 self.root_group().create_group(name)
350 }
351
352 pub fn create_soft_link(&self, link_name: &str, target_path: &str) -> Result<()> {
363 self.root_group().create_soft_link(link_name, target_path)
364 }
365
366 pub fn create_external_link(
376 &self,
377 link_name: &str,
378 target_file: &str,
379 target_path: &str,
380 ) -> Result<()> {
381 self.root_group()
382 .create_external_link(link_name, target_file, target_path)
383 }
384
385 pub fn commit_datatype(
396 &self,
397 name: &str,
398 datatype: crate::format::messages::datatype::DatatypeMessage,
399 ) -> Result<()> {
400 self.root_group().commit_datatype(name, datatype)
401 }
402
403 pub fn new_dataset<T: H5Type>(&self) -> DatasetBuilder<T> {
414 DatasetBuilder::new(clone_inner(&self.inner))
415 }
416
417 pub fn set_attr_string(&self, name: &str, value: &str) -> Result<()> {
422 let inner = borrow_inner(&self.inner);
423 match &*inner {
424 H5FileInner::Writer(writer) => {
425 writer.set_vlen_string_attribute(
426 crate::io::writer::AttrTarget::Root,
427 name,
428 value,
429 )?;
430 Ok(())
431 }
432 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
433 }
434 }
435
436 pub fn set_attr_numeric<T: crate::types::H5Type>(&self, name: &str, value: &T) -> Result<()> {
438 use crate::format::messages::attribute::AttributeMessage;
439 let es = T::element_size();
440 let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
441 let attr = AttributeMessage::scalar_numeric(name, T::hdf5_type(), raw.to_vec());
442 let inner = borrow_inner(&self.inner);
443 match &*inner {
444 H5FileInner::Writer(writer) => {
445 writer.add_root_attribute(attr)?;
446 Ok(())
447 }
448 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
449 }
450 }
451
452 pub fn set_attr_typed(
478 &self,
479 name: &str,
480 datatype: DatatypeMessage,
481 value: Vec<u8>,
482 ) -> Result<()> {
483 let attr = crate::attribute::typed_scalar_attr(name, datatype, value)?;
484 let inner = borrow_inner(&self.inner);
485 match &*inner {
486 H5FileInner::Writer(writer) => {
487 writer.add_root_attribute(attr)?;
488 Ok(())
489 }
490 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
491 }
492 }
493
494 pub fn set_attr_array_numeric<T: crate::types::H5Type>(
502 &self,
503 name: &str,
504 values: &[T],
505 ) -> Result<()> {
506 self.set_attr_array_numeric_nd(name, values, &[values.len()])
507 }
508
509 pub fn set_attr_array_numeric_nd<T: crate::types::H5Type>(
518 &self,
519 name: &str,
520 values: &[T],
521 shape: &[usize],
522 ) -> Result<()> {
523 use crate::format::messages::attribute::AttributeMessage;
524 let n: usize = shape.iter().product();
525 if values.len() != n {
526 return Err(Hdf5Error::InvalidState(format!(
527 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
528 values.len()
529 )));
530 }
531 let es = T::element_size();
532 let raw =
534 unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * es) };
535 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
536 let attr = AttributeMessage::array_numeric(name, T::hdf5_type(), &dims, raw.to_vec());
537 let mut inner = borrow_inner_mut(&self.inner);
538 match &mut *inner {
539 H5FileInner::Writer(writer) => {
540 writer.add_root_attribute(attr)?;
541 Ok(())
542 }
543 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
544 }
545 }
546
547 pub fn set_attr_string_array(&self, name: &str, values: &[&str]) -> Result<()> {
552 self.set_attr_string_array_nd(name, values, &[values.len()])
553 }
554
555 pub fn set_attr_string_array_nd(
565 &self,
566 name: &str,
567 values: &[&str],
568 shape: &[usize],
569 ) -> Result<()> {
570 let n: usize = shape.iter().product();
571 if values.len() != n {
572 return Err(Hdf5Error::InvalidState(format!(
573 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
574 values.len()
575 )));
576 }
577 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
578 let mut inner = borrow_inner_mut(&self.inner);
579 match &mut *inner {
580 H5FileInner::Writer(writer) => {
581 writer.set_vlen_string_array_attribute(
582 crate::io::writer::AttrTarget::Root,
583 name,
584 values,
585 &dims,
586 )?;
587 Ok(())
588 }
589 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
590 }
591 }
592
593 pub fn set_attr_object_reference(&self, name: &str, path: &str) -> Result<()> {
602 self.set_root_reference_attr(name, &[path], &[])
603 }
604
605 pub fn set_attr_object_references(&self, name: &str, paths: &[&str]) -> Result<()> {
609 self.set_root_reference_attr(name, paths, &[paths.len() as u64])
610 }
611
612 fn set_root_reference_attr(&self, name: &str, paths: &[&str], dims: &[u64]) -> Result<()> {
613 let inner = borrow_inner(&self.inner);
614 match &*inner {
615 H5FileInner::Writer(writer) => {
616 writer.set_object_reference_attribute(
617 crate::io::writer::AttrTarget::Root,
618 name,
619 paths,
620 dims,
621 )?;
622 Ok(())
623 }
624 _ => Err(Hdf5Error::InvalidState("cannot write in read mode".into())),
625 }
626 }
627
628 pub fn attr_names(&self) -> Result<Vec<String>> {
630 let inner = borrow_inner(&self.inner);
631 match &*inner {
632 H5FileInner::Reader(reader) => Ok(reader.root_attr_names()?),
633 _ => Ok(vec![]),
634 }
635 }
636
637 pub fn attr_unreadable_reason(&self, name: &str) -> Result<Option<String>> {
640 let inner = borrow_inner(&self.inner);
641 match &*inner {
642 H5FileInner::Reader(reader) => {
643 Ok(reader.root_attr_unreadable_reason(name).map(str::to_string))
644 }
645 _ => Err(Hdf5Error::InvalidState("not in read mode".into())),
646 }
647 }
648
649 pub fn attrs_unreadable_reason(&self) -> Result<Option<String>> {
653 let inner = borrow_inner(&self.inner);
654 match &*inner {
655 H5FileInner::Reader(reader) => {
656 Ok(reader.root_attrs_unreadable_reason().map(str::to_string))
657 }
658 _ => Err(Hdf5Error::InvalidState("not in read mode".into())),
659 }
660 }
661
662 pub fn attr_string(&self, name: &str) -> Result<String> {
664 let mut inner = borrow_inner_mut(&self.inner);
665 match &mut *inner {
666 H5FileInner::Reader(reader) => {
667 let attr = reader.root_attr(name)?.clone();
668 Ok(reader.attr_string_value(&attr)?)
669 }
670 _ => Err(Hdf5Error::InvalidState("not in read mode".into())),
671 }
672 }
673
674 pub fn superblock_extension(&self) -> SuperblockExtension {
681 let inner = borrow_inner(&self.inner);
682 match &*inner {
683 H5FileInner::Reader(reader) => reader.superblock_extension().clone(),
684 _ => SuperblockExtension::default(),
685 }
686 }
687
688 pub fn object_message_storage(&self, path: &str) -> Result<Vec<(u8, MessageStorage)>> {
696 let mut inner = borrow_inner_mut(&self.inner);
697 match &mut *inner {
698 H5FileInner::Reader(reader) => Ok(reader.object_message_storage(path)?),
699 _ => Err(Hdf5Error::InvalidState(
700 "object_message_storage is only available in read mode".into(),
701 )),
702 }
703 }
704
705 pub fn object_message_flags(&self, path: &str) -> Result<Vec<(u8, u8)>> {
714 let mut inner = borrow_inner_mut(&self.inner);
715 match &mut *inner {
716 H5FileInner::Reader(reader) => Ok(reader.object_message_flags(path)?),
717 _ => Err(Hdf5Error::InvalidState(
718 "object_message_flags is only available in read mode".into(),
719 )),
720 }
721 }
722
723 pub fn object_datatype_versions(&self, path: &str) -> Result<Vec<DatatypeNodeVersion>> {
734 let mut inner = borrow_inner_mut(&self.inner);
735 match &mut *inner {
736 H5FileInner::Reader(reader) => Ok(reader.object_datatype_versions(path)?),
737 _ => Err(Hdf5Error::InvalidState(
738 "object_datatype_versions is only available in read mode".into(),
739 )),
740 }
741 }
742
743 pub fn object_records_times(&self, path: &str) -> Result<bool> {
753 let mut inner = borrow_inner_mut(&self.inner);
754 match &mut *inner {
755 H5FileInner::Reader(reader) => Ok(reader.object_records_times(path)?),
756 _ => Err(Hdf5Error::InvalidState(
757 "object_records_times is only available in read mode".into(),
758 )),
759 }
760 }
761
762 pub fn tracked_free_space(&self) -> Result<u64> {
771 let mut inner = borrow_inner_mut(&self.inner);
772 match &mut *inner {
773 H5FileInner::Reader(reader) => Ok(reader.tracked_free_space()?),
774 _ => Err(Hdf5Error::InvalidState(
775 "tracked_free_space is only available in read mode".into(),
776 )),
777 }
778 }
779
780 pub fn userblock_size(&self) -> u64 {
784 let inner = borrow_inner(&self.inner);
785 match &*inner {
786 H5FileInner::Reader(reader) => reader.userblock_size(),
787 H5FileInner::Writer(writer) => writer.userblock_size(),
788 H5FileInner::Closed => 0,
789 }
790 }
791
792 pub fn superblock_version(&self) -> Result<u8> {
796 let inner = borrow_inner(&self.inner);
797 match &*inner {
798 H5FileInner::Reader(reader) => Ok(reader.superblock_version()),
799 _ => Err(Hdf5Error::InvalidState(
800 "superblock_version is only available in read mode".into(),
801 )),
802 }
803 }
804
805 pub fn libver_bound(&self) -> Result<LibverBound> {
812 self.superblock_version()
813 .map(LibverBound::from_superblock_version)
814 }
815
816 pub fn is_writable(&self) -> bool {
818 let inner = borrow_inner(&self.inner);
819 matches!(&*inner, H5FileInner::Writer(_))
820 }
821
822 pub fn write_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<H5Dataset> {
832 self.write_vlen_strings_charset(name, strings, 1)
833 }
834
835 pub fn write_vlen_strings_ascii(&self, name: &str, strings: &[&str]) -> Result<H5Dataset> {
847 self.write_vlen_strings_charset(name, strings, 0)
848 }
849
850 fn write_vlen_strings_charset(
853 &self,
854 name: &str,
855 strings: &[&str],
856 charset: u8,
857 ) -> Result<H5Dataset> {
858 let inner = borrow_inner(&self.inner);
859 match &*inner {
860 H5FileInner::Writer(writer) => {
861 let idx = writer.create_vlen_string_dataset(name, strings, charset)?;
862 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
863 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
864 }
865 H5FileInner::Reader(_) => {
866 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
867 }
868 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
869 }
870 }
871
872 pub fn write_vlen_bytes(&self, name: &str, items: &[&[u8]]) -> Result<H5Dataset> {
881 self.write_vlen_numeric(name, items)
882 }
883
884 pub fn write_vlen_numeric<T: H5Type>(&self, name: &str, items: &[&[T]]) -> Result<H5Dataset> {
901 let images = crate::dataset::vlen_sequence_images(items)?;
902 let images: Vec<&[u8]> = images.iter().map(|c| c.as_ref()).collect();
903 let inner = borrow_inner(&self.inner);
904 match &*inner {
905 H5FileInner::Writer(writer) => {
906 let idx = writer.create_vlen_sequence_dataset(name, T::hdf5_type(), &images)?;
907 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
908 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
909 }
910 H5FileInner::Reader(_) => {
911 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
912 }
913 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
914 }
915 }
916
917 pub fn write_vlen_strings_compressed(
924 &self,
925 name: &str,
926 strings: &[&str],
927 chunk_size: usize,
928 pipeline: FilterPipeline,
929 ) -> Result<H5Dataset> {
930 let inner = borrow_inner(&self.inner);
931 match &*inner {
932 H5FileInner::Writer(writer) => {
933 let idx = writer
934 .create_vlen_string_dataset_compressed(name, strings, chunk_size, pipeline)?;
935 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
936 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
937 }
938 H5FileInner::Reader(_) => {
939 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
940 }
941 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
942 }
943 }
944
945 pub fn create_appendable_vlen_dataset(
950 &self,
951 name: &str,
952 chunk_size: usize,
953 pipeline: Option<FilterPipeline>,
954 ) -> Result<H5Dataset> {
955 let inner = borrow_inner(&self.inner);
956 match &*inner {
957 H5FileInner::Writer(writer) => {
958 let idx =
959 writer.create_appendable_vlen_string_dataset(name, chunk_size, pipeline)?;
960 let parts = writer.dataset_handle_parts(idx, &DatasetAccess::default())?;
961 Ok(H5Dataset::new_writer(clone_inner(&self.inner), idx, parts))
962 }
963 H5FileInner::Reader(_) => {
964 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
965 }
966 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
967 }
968 }
969
970 pub fn append_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
972 let inner = borrow_inner(&self.inner);
973 match &*inner {
974 H5FileInner::Writer(writer) => {
975 let ds_index = writer.open_dataset_index(name)?;
976 writer.append_vlen_strings(ds_index, strings)?;
977 Ok(())
978 }
979 H5FileInner::Reader(_) => {
980 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
981 }
982 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
983 }
984 }
985
986 pub fn delete_dataset(&self, name: &str) -> Result<()> {
995 let inner = borrow_inner(&self.inner);
996 match &*inner {
997 H5FileInner::Writer(writer) => {
998 writer.delete_dataset(name)?;
999 Ok(())
1000 }
1001 _ => Err(Hdf5Error::InvalidState("cannot delete in read mode".into())),
1002 }
1003 }
1004
1005 pub fn delete_group(&self, name: &str) -> Result<()> {
1014 let inner = borrow_inner(&self.inner);
1015 match &*inner {
1016 H5FileInner::Writer(writer) => {
1017 writer.delete_group(name)?;
1018 Ok(())
1019 }
1020 _ => Err(Hdf5Error::InvalidState("cannot delete in read mode".into())),
1021 }
1022 }
1023
1024 pub fn dataset(&self, name: &str) -> Result<H5Dataset> {
1029 self.dataset_with(name, DatasetAccess::default())
1030 }
1031
1032 pub fn dataset_with(&self, name: &str, access: DatasetAccess) -> Result<H5Dataset> {
1066 access.validate()?;
1067 let mut inner = borrow_inner_mut(&self.inner);
1070 match &mut *inner {
1071 H5FileInner::Reader(reader) => {
1072 let (open, info) = reader.open_dataset_with(name, &access)?;
1076 let shape: Vec<usize> = info.dataspace.dims.iter().map(|&d| d as usize).collect();
1077 let element_size = info.datatype.element_size() as usize;
1078 Ok(H5Dataset::new_reader(
1079 clone_inner(&self.inner),
1080 name.to_string(),
1081 shape,
1082 element_size,
1083 open,
1084 ))
1085 }
1086 H5FileInner::Writer(_) => Err(Hdf5Error::InvalidState(
1087 "cannot open a dataset by name in write mode; use new_dataset() instead"
1088 .to_string(),
1089 )),
1090 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".to_string())),
1091 }
1092 }
1093
1094 pub fn dataset_writer(&self, name: &str) -> Result<H5Dataset> {
1108 self.dataset_writer_with(name, DatasetAccess::default())
1109 }
1110
1111 pub fn dataset_writer_with(&self, name: &str, access: DatasetAccess) -> Result<H5Dataset> {
1138 access.validate()?;
1139 let inner = borrow_inner(&self.inner);
1140 match &*inner {
1141 H5FileInner::Writer(writer) => {
1142 let index = writer.open_dataset_index(name)?;
1143 let parts = writer.dataset_handle_parts(index, &access)?;
1144 Ok(H5Dataset::new_writer(
1145 clone_inner(&self.inner),
1146 index,
1147 parts,
1148 ))
1149 }
1150 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
1151 "cannot open a dataset_writer in read mode; use dataset() instead".to_string(),
1152 )),
1153 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".to_string())),
1154 }
1155 }
1156
1157 pub fn dataset_names(&self) -> Vec<String> {
1163 let inner = borrow_inner(&self.inner);
1164 match &*inner {
1165 H5FileInner::Reader(reader) => reader
1166 .dataset_names()
1167 .iter()
1168 .map(|s| s.to_string())
1169 .collect(),
1170 H5FileInner::Writer(writer) => writer
1171 .dataset_names()
1172 .iter()
1173 .map(|s| s.to_string())
1174 .collect(),
1175 H5FileInner::Closed => Vec::new(),
1176 }
1177 }
1178
1179 pub fn named_datatype_names(&self) -> Vec<String> {
1186 let inner = borrow_inner(&self.inner);
1187 match &*inner {
1188 H5FileInner::Reader(reader) => reader
1189 .named_datatype_names()
1190 .iter()
1191 .map(|s| s.to_string())
1192 .collect(),
1193 H5FileInner::Writer(writer) => writer.committed_datatype_names(),
1194 H5FileInner::Closed => Vec::new(),
1195 }
1196 }
1197
1198 pub fn named_datatype(&self, path: &str) -> Result<crate::H5NamedDatatype> {
1209 let mut inner = borrow_inner_mut(&self.inner);
1212 match &mut *inner {
1213 H5FileInner::Reader(reader) => {
1214 reader.named_datatype_info(path)?;
1215 drop(inner);
1216 Ok(crate::H5NamedDatatype::new_reader(
1217 clone_inner(&self.inner),
1218 path.to_string(),
1219 ))
1220 }
1221 H5FileInner::Writer(_) => Err(Hdf5Error::InvalidState(
1222 "committed datatypes are readable only in read mode".to_string(),
1223 )),
1224 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".to_string())),
1225 }
1226 }
1227
1228 pub fn close(self) -> Result<()> {
1234 let old = {
1235 let mut inner = borrow_inner_mut(&self.inner);
1236 std::mem::replace(&mut *inner, H5FileInner::Closed)
1237 };
1238 match old {
1239 H5FileInner::Writer(writer) => {
1240 writer.close()?;
1241 Ok(())
1242 }
1243 H5FileInner::Reader(_) => Ok(()),
1244 H5FileInner::Closed => Ok(()),
1245 }
1246 }
1247
1248 pub fn close_no_sync(self) -> Result<()> {
1264 let old = {
1265 let mut inner = borrow_inner_mut(&self.inner);
1266 std::mem::replace(&mut *inner, H5FileInner::Closed)
1267 };
1268 match old {
1269 H5FileInner::Writer(writer) => {
1270 writer.close_no_sync()?;
1271 Ok(())
1272 }
1273 H5FileInner::Reader(_) => Ok(()),
1274 H5FileInner::Closed => Ok(()),
1275 }
1276 }
1277
1278 pub fn flush(&self) -> Result<()> {
1286 let mut inner = borrow_inner_mut(&self.inner);
1287 match &mut *inner {
1288 H5FileInner::Writer(writer) => Ok(writer.handle().flush()?),
1289 H5FileInner::Reader(_) => Ok(()),
1290 H5FileInner::Closed => Ok(()),
1291 }
1292 }
1293}
1294
1295#[derive(Debug, Default, Clone)]
1302pub struct H5FileOptions {
1303 locking: Option<FileLocking>,
1304 track_order: bool,
1305 track_times: bool,
1306 libver: Option<LibverBound>,
1307 userblock: u64,
1308 shared_messages: SharedMessageConfig,
1309 file_space: Option<FileSpaceConfig>,
1310 file_space_page_size: Option<u64>,
1311 elink_prefix: Option<String>,
1312}
1313
1314impl H5FileOptions {
1315 pub fn new() -> Self {
1317 Self::default()
1318 }
1319
1320 pub fn locking(mut self, policy: FileLocking) -> Self {
1323 self.locking = Some(policy);
1324 self
1325 }
1326
1327 pub fn no_locking(self) -> Self {
1332 self.locking(FileLocking::Disabled)
1333 }
1334
1335 pub fn best_effort_locking(self) -> Self {
1338 self.locking(FileLocking::BestEffort)
1339 }
1340
1341 pub fn elink_prefix(mut self, prefix: impl Into<String>) -> Self {
1372 self.elink_prefix = Some(prefix.into());
1373 self
1374 }
1375
1376 pub fn track_order(mut self, track: bool) -> Self {
1384 self.track_order = track;
1385 self
1386 }
1387
1388 pub fn track_times(mut self, track: bool) -> Self {
1408 self.track_times = track;
1409 self
1410 }
1411
1412 pub fn libver(mut self, libver: LibverBound) -> Self {
1455 self.libver = Some(libver);
1456 self
1457 }
1458
1459 pub fn userblock(mut self, size: u64) -> Self {
1482 self.userblock = size;
1483 self
1484 }
1485
1486 pub fn shared_messages(
1517 mut self,
1518 indexes: &[(u16, u32)],
1519 list_max: u16,
1520 btree_min: u16,
1521 ) -> Self {
1522 self.shared_messages = SharedMessageConfig::new(indexes, list_max, btree_min);
1523 self
1524 }
1525
1526 pub fn file_space(
1560 mut self,
1561 strategy: FileSpaceStrategy,
1562 persist: bool,
1563 threshold: u64,
1564 ) -> Self {
1565 self.file_space = Some(FileSpaceConfig::new(strategy, persist, threshold));
1566 self
1567 }
1568
1569 pub fn file_space_page_size(mut self, size: u64) -> Self {
1597 self.file_space_page_size = Some(size);
1598 self
1599 }
1600
1601 fn resolved_file_space(&self) -> FileSpaceConfig {
1610 let config = self.file_space.unwrap_or_default();
1611 match self.file_space_page_size {
1612 Some(size) => config.with_page_size(size),
1613 None => config,
1614 }
1615 }
1616
1617 fn resolved_locking(&self) -> FileLocking {
1618 match self.locking {
1619 Some(p) => p,
1620 None => FileLocking::from_env_or(FileLocking::default()),
1621 }
1622 }
1623
1624 fn refuse_create_only_options(&self) -> Result<()> {
1642 let mut offending = Vec::new();
1643 if self.track_order {
1644 offending.push("track_order");
1645 }
1646 if self.track_times {
1647 offending.push("track_times");
1648 }
1649 if self.libver.is_some() {
1650 offending.push("libver");
1651 }
1652 if self.userblock != 0 {
1653 offending.push("userblock");
1654 }
1655 if self.shared_messages != SharedMessageConfig::default() {
1656 offending.push("shared_messages");
1657 }
1658 if self.file_space.is_some() {
1659 offending.push("file_space");
1660 }
1661 if self.file_space_page_size.is_some() {
1662 offending.push("file_space_page_size");
1663 }
1664 if offending.is_empty() {
1665 Ok(())
1666 } else {
1667 Err(Hdf5Error::InvalidState(format!(
1668 "these options only take effect when creating a file, not when \
1669 opening an existing one: {}",
1670 offending.join(", ")
1671 )))
1672 }
1673 }
1674
1675 fn refuse_read_only_options(&self) -> Result<()> {
1683 if self.elink_prefix.is_none() {
1684 return Ok(());
1685 }
1686 Err(Hdf5Error::InvalidState(
1687 "these options only take effect when opening a file for reading: \
1688 elink_prefix"
1689 .to_string(),
1690 ))
1691 }
1692
1693 pub fn create<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1695 self.refuse_read_only_options()?;
1696 let writer = Hdf5Writer::create_with_options(
1697 path.as_ref(),
1698 crate::io::writer::FileCreateOptions {
1699 locking: self.resolved_locking(),
1700 track_order: self.track_order,
1701 track_times: self.track_times,
1702 libver: self.libver,
1703 userblock: self.userblock,
1704 shared_messages: self.shared_messages,
1705 file_space: self.resolved_file_space(),
1706 },
1707 )?;
1708 Ok(H5File {
1709 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
1710 })
1711 }
1712
1713 pub fn open<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1715 self.refuse_create_only_options()?;
1716 let mut reader = Hdf5Reader::open_with_locking(path.as_ref(), self.resolved_locking())?;
1717 reader.set_elink_prefix(self.elink_prefix);
1718 Ok(H5File {
1719 inner: new_shared(H5FileInner::Reader(Box::new(reader))),
1720 })
1721 }
1722
1723 pub fn open_rw<P: AsRef<Path>>(self, path: P) -> Result<H5File> {
1725 self.refuse_create_only_options()?;
1726 self.refuse_read_only_options()?;
1727 let writer = Hdf5Writer::open_append_with_locking(path.as_ref(), self.resolved_locking())?;
1728 Ok(H5File {
1729 inner: new_shared(H5FileInner::Writer(Box::new(writer))),
1730 })
1731 }
1732}
1733
1734#[cfg(test)]
1735fn unique_test_path(name: &str) -> std::path::PathBuf {
1736 use std::sync::atomic::{AtomicU64, Ordering};
1741 static COUNTER: AtomicU64 = AtomicU64::new(0);
1742 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
1743 std::env::temp_dir().join(format!(
1744 "rust_hdf5_test_{}_{}_{}.h5",
1745 name,
1746 std::process::id(),
1747 n
1748 ))
1749}
1750
1751#[cfg(test)]
1752mod tests {
1753 use super::*;
1754 use std::path::PathBuf;
1755
1756 fn temp_path(name: &str) -> PathBuf {
1757 super::unique_test_path(name)
1758 }
1759
1760 #[test]
1761 fn create_and_close_empty() {
1762 let path = temp_path("create_empty");
1763 let file = H5File::create(&path).unwrap();
1764 file.close().unwrap();
1765
1766 let file = H5File::open(&path).unwrap();
1768 file.close().unwrap();
1769
1770 std::fs::remove_file(&path).ok();
1771 }
1772
1773 #[test]
1774 fn create_and_drop_empty() {
1775 let path = temp_path("drop_empty");
1776 {
1777 let _file = H5File::create(&path).unwrap();
1778 }
1780 let file = H5File::open(&path).unwrap();
1782 file.close().unwrap();
1783
1784 std::fs::remove_file(&path).ok();
1785 }
1786
1787 #[test]
1788 fn dataset_not_found() {
1789 let path = temp_path("ds_not_found");
1790 {
1791 let _file = H5File::create(&path).unwrap();
1792 }
1793 let file = H5File::open(&path).unwrap();
1794 let result = file.dataset("nonexistent");
1795 assert!(result.is_err());
1796
1797 std::fs::remove_file(&path).ok();
1798 }
1799
1800 #[test]
1801 fn write_and_read_roundtrip() {
1802 let path = temp_path("write_read_rt");
1803
1804 {
1806 let file = H5File::create(&path).unwrap();
1807 let ds = file
1808 .new_dataset::<u8>()
1809 .shape([4, 4])
1810 .create("data")
1811 .unwrap();
1812 ds.write_raw(&[0u8; 16]).unwrap();
1813 file.close().unwrap();
1814 }
1815
1816 {
1818 let file = H5File::open(&path).unwrap();
1819 let ds = file.dataset("data").unwrap();
1820 assert_eq!(ds.shape(), vec![4, 4]);
1821 let data = ds.read_raw::<u8>().unwrap();
1822 assert_eq!(data.len(), 16);
1823 assert!(data.iter().all(|&b| b == 0));
1824 file.close().unwrap();
1825 }
1826
1827 std::fs::remove_file(&path).ok();
1828 }
1829
1830 #[test]
1831 fn close_no_sync_produces_valid_readable_file() {
1832 let path = temp_path("close_no_sync_rt");
1833 let payload: Vec<u8> = (0u8..16).collect();
1834
1835 {
1837 let file = H5File::create(&path).unwrap();
1838 let ds = file
1839 .new_dataset::<u8>()
1840 .shape([4, 4])
1841 .create("data")
1842 .unwrap();
1843 ds.write_raw(&payload).unwrap();
1844 file.close_no_sync().unwrap();
1847 }
1848
1849 {
1852 let file = H5File::open(&path).unwrap();
1853 let ds = file.dataset("data").unwrap();
1854 assert_eq!(ds.shape(), vec![4, 4]);
1855 let data = ds.read_raw::<u8>().unwrap();
1856 assert_eq!(data, payload);
1857 file.close().unwrap();
1858 }
1859
1860 std::fs::remove_file(&path).ok();
1861 }
1862
1863 #[test]
1864 fn create_over_existing_file_truncates() {
1865 let path = temp_path("create_truncates");
1871
1872 {
1873 let file = H5File::create(&path).unwrap();
1874 let ds = file
1875 .new_dataset::<u8>()
1876 .shape([4, 4])
1877 .create("old_data")
1878 .unwrap();
1879 ds.write_raw(&[7u8; 16]).unwrap();
1880 file.close().unwrap();
1881 }
1882 assert!(std::fs::metadata(&path).unwrap().len() > 0);
1883
1884 {
1886 let file = H5File::create(&path).unwrap();
1887 file.close().unwrap();
1888 }
1889
1890 let file = H5File::open(&path).unwrap();
1892 assert!(file.dataset("old_data").is_err());
1893 file.close().unwrap();
1894
1895 std::fs::remove_file(&path).ok();
1896 }
1897
1898 #[test]
1899 fn close_no_sync_chunked_dataset_valid() {
1900 let path = temp_path("close_no_sync_chunked");
1905
1906 {
1907 let file = H5File::create(&path).unwrap();
1908 let ds = file
1909 .new_dataset::<i32>()
1910 .shape([0usize, 3])
1911 .chunk(&[1, 3])
1912 .max_shape(&[None, Some(3)])
1913 .create("data")
1914 .unwrap();
1915 for frame in 0..10u64 {
1917 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
1918 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
1919 ds.write_chunk(frame as usize, &raw).unwrap();
1920 }
1921 ds.extend(&[10, 3]).unwrap();
1922 file.close_no_sync().unwrap();
1923 }
1924
1925 {
1926 let file = H5File::open(&path).unwrap();
1927 let ds = file.dataset("data").unwrap();
1928 assert_eq!(ds.shape(), vec![10, 3]);
1929 let data = ds.read_raw::<i32>().unwrap();
1930 let expected: Vec<i32> = (0..30).collect();
1931 assert_eq!(data, expected);
1932 file.close().unwrap();
1933 }
1934
1935 std::fs::remove_file(&path).ok();
1936 }
1937
1938 #[test]
1939 fn write_and_read_f64() {
1940 let path = temp_path("write_read_f64");
1941
1942 let values: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1943
1944 {
1946 let file = H5File::create(&path).unwrap();
1947 let ds = file
1948 .new_dataset::<f64>()
1949 .shape([2, 3])
1950 .create("matrix")
1951 .unwrap();
1952 ds.write_raw(&values).unwrap();
1953 file.close().unwrap();
1954 }
1955
1956 {
1958 let file = H5File::open(&path).unwrap();
1959 let ds = file.dataset("matrix").unwrap();
1960 assert_eq!(ds.shape(), vec![2, 3]);
1961 let readback = ds.read_raw::<f64>().unwrap();
1962 assert_eq!(readback, values);
1963 }
1964
1965 std::fs::remove_file(&path).ok();
1966 }
1967
1968 #[test]
1969 fn multiple_datasets() {
1970 let path = temp_path("multi_ds");
1971
1972 {
1973 let file = H5File::create(&path).unwrap();
1974 let ds1 = file.new_dataset::<i32>().shape([3]).create("ints").unwrap();
1975 ds1.write_raw(&[10i32, 20, 30]).unwrap();
1976
1977 let ds2 = file
1978 .new_dataset::<f32>()
1979 .shape([2, 2])
1980 .create("floats")
1981 .unwrap();
1982 ds2.write_raw(&[1.0f32, 2.0, 3.0, 4.0]).unwrap();
1983
1984 file.close().unwrap();
1985 }
1986
1987 {
1988 let file = H5File::open(&path).unwrap();
1989
1990 let ds_ints = file.dataset("ints").unwrap();
1991 assert_eq!(ds_ints.shape(), vec![3]);
1992 let ints = ds_ints.read_raw::<i32>().unwrap();
1993 assert_eq!(ints, vec![10, 20, 30]);
1994
1995 let ds_floats = file.dataset("floats").unwrap();
1996 assert_eq!(ds_floats.shape(), vec![2, 2]);
1997 let floats = ds_floats.read_raw::<f32>().unwrap();
1998 assert_eq!(floats, vec![1.0f32, 2.0, 3.0, 4.0]);
1999 }
2000
2001 std::fs::remove_file(&path).ok();
2002 }
2003
2004 #[test]
2005 fn close_is_idempotent() {
2006 let path = temp_path("close_idemp");
2007 let file = H5File::create(&path).unwrap();
2008 file.close().unwrap();
2009 std::fs::remove_file(&path).ok();
2011 }
2012}
2013
2014#[cfg(test)]
2015mod integration_tests {
2016 use super::*;
2017
2018 fn temp_path(name: &str) -> std::path::PathBuf {
2019 super::unique_test_path(name)
2020 }
2021
2022 #[test]
2023 fn write_file_for_h5dump() {
2024 let path = temp_path("integration");
2025 let file = H5File::create(&path).unwrap();
2026
2027 let ds = file
2028 .new_dataset::<u8>()
2029 .shape([4usize, 4])
2030 .create("data_u8")
2031 .unwrap();
2032 let data: Vec<u8> = (0..16).collect();
2033 ds.write_raw(&data).unwrap();
2034
2035 let ds2 = file
2036 .new_dataset::<f64>()
2037 .shape([3usize, 2])
2038 .create("data_f64")
2039 .unwrap();
2040 let fdata: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
2041 ds2.write_raw(&fdata).unwrap();
2042
2043 let ds3 = file
2044 .new_dataset::<i32>()
2045 .shape([5usize])
2046 .create("values")
2047 .unwrap();
2048 let idata: Vec<i32> = vec![-10, -5, 0, 5, 10];
2049 ds3.write_raw(&idata).unwrap();
2050
2051 file.close().unwrap();
2052
2053 assert!(path.exists());
2055 }
2056
2057 #[test]
2058 fn write_chunked_file_for_h5dump() {
2059 let path = temp_path("chunked");
2060 let file = H5File::create(&path).unwrap();
2061
2062 let ds = file
2064 .new_dataset::<f64>()
2065 .shape([0usize, 4])
2066 .chunk(&[1, 4])
2067 .max_shape(&[None, Some(4)])
2068 .create("streaming_data")
2069 .unwrap();
2070
2071 for frame in 0..5u64 {
2073 let values: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
2074 let raw: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
2075 ds.write_chunk(frame as usize, &raw).unwrap();
2076 }
2077
2078 ds.extend(&[5, 4]).unwrap();
2080 ds.flush().unwrap();
2081
2082 file.close().unwrap();
2083
2084 assert!(path.exists());
2085 }
2086
2087 #[test]
2088 fn write_chunked_many_frames_for_h5dump() {
2089 let path = temp_path("chunked_many");
2090 let file = H5File::create(&path).unwrap();
2091
2092 let ds = file
2093 .new_dataset::<i32>()
2094 .shape([0usize, 3])
2095 .chunk(&[1, 3])
2096 .max_shape(&[None, Some(3)])
2097 .create("data")
2098 .unwrap();
2099
2100 for frame in 0..10u64 {
2102 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2103 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2104 ds.write_chunk(frame as usize, &raw).unwrap();
2105 }
2106 ds.extend(&[10, 3]).unwrap();
2107 file.close().unwrap();
2108
2109 assert!(path.exists());
2110 }
2111
2112 #[test]
2113 fn write_dataset_with_attributes() {
2114 use crate::types::VarLenUnicode;
2115
2116 let path = temp_path("attributes");
2117 let file = H5File::create(&path).unwrap();
2118
2119 let ds = file
2120 .new_dataset::<f32>()
2121 .shape([10usize])
2122 .create("temperature")
2123 .unwrap();
2124 let data: Vec<f32> = (0..10).map(|i| i as f32 * 1.5).collect();
2125 ds.write_raw(&data).unwrap();
2126
2127 let attr = ds
2129 .new_attr::<VarLenUnicode>()
2130 .shape(())
2131 .create("units")
2132 .unwrap();
2133 attr.write_scalar(&VarLenUnicode("kelvin".to_string()))
2134 .unwrap();
2135
2136 let attr2 = ds
2137 .new_attr::<VarLenUnicode>()
2138 .shape(())
2139 .create("description")
2140 .unwrap();
2141 attr2
2142 .write_scalar(&VarLenUnicode("Temperature measurements".to_string()))
2143 .unwrap();
2144
2145 let attr3 = ds
2147 .new_attr::<VarLenUnicode>()
2148 .shape(())
2149 .create("source")
2150 .unwrap();
2151 attr3.write_string("sensor_01").unwrap();
2152
2153 let attr4 = ds
2155 .new_attr::<VarLenUnicode>()
2156 .shape(())
2157 .create("label")
2158 .unwrap();
2159 let s: VarLenUnicode = "test_label".parse().unwrap_or_default();
2160 attr4.write_scalar(&s).unwrap();
2161
2162 file.close().unwrap();
2163
2164 assert!(path.exists());
2165 }
2166
2167 #[test]
2168 fn dataset_writer_reopens_for_attributes() {
2169 let path = temp_path("dataset_writer");
2172 {
2173 let file = H5File::create(&path).unwrap();
2174 {
2175 let ds = file
2176 .new_dataset::<u16>()
2177 .shape([8])
2178 .create("image")
2179 .unwrap();
2180 ds.write_raw(&[0u16; 8]).unwrap();
2181 }
2183
2184 assert!(file.dataset("image").is_err());
2186 let ds = file.dataset_writer("image").unwrap();
2187 assert_eq!(ds.shape(), vec![8]);
2188 ds.new_attr::<i32>()
2189 .shape([3])
2190 .create("NDArrayDimOffset")
2191 .unwrap()
2192 .write_array(&[0i32, 4, 8])
2193 .unwrap();
2194 ds.new_attr::<i32>()
2195 .shape(())
2196 .create("NDUniqueId")
2197 .unwrap()
2198 .write_numeric(&42i32)
2199 .unwrap();
2200
2201 assert!(matches!(
2203 file.dataset_writer("nope"),
2204 Err(crate::error::Hdf5Error::NotFound(_))
2205 ));
2206
2207 file.close().unwrap();
2208 }
2209 {
2210 let file = H5File::open(&path).unwrap();
2211 let ds = file.dataset("image").unwrap();
2212 let off = ds.attr("NDArrayDimOffset").unwrap().read_raw().unwrap();
2213 let got: Vec<i32> = off
2214 .as_chunks::<4>()
2215 .0
2216 .iter()
2217 .map(|b| i32::from_le_bytes(*b))
2218 .collect();
2219 assert_eq!(got, vec![0, 4, 8]);
2220 let uid: i32 = ds.attr("NDUniqueId").unwrap().read_numeric().unwrap();
2221 assert_eq!(uid, 42);
2222 }
2223 std::fs::remove_file(&path).ok();
2224 }
2225
2226 #[test]
2227 fn chunked_write_read_roundtrip() {
2228 let path = temp_path("chunked_roundtrip");
2229
2230 {
2232 let file = H5File::create(&path).unwrap();
2233 let ds = file
2234 .new_dataset::<i32>()
2235 .shape([0usize, 3])
2236 .chunk(&[1, 3])
2237 .max_shape(&[None, Some(3)])
2238 .create("table")
2239 .unwrap();
2240
2241 for frame in 0..8u64 {
2242 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2243 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2244 ds.write_chunk(frame as usize, &raw).unwrap();
2245 }
2246 ds.extend(&[8, 3]).unwrap();
2247 file.close().unwrap();
2248 }
2249
2250 {
2252 let file = H5File::open(&path).unwrap();
2253 let ds = file.dataset("table").unwrap();
2254 assert_eq!(ds.shape(), vec![8, 3]);
2255 let data = ds.read_raw::<i32>().unwrap();
2256 assert_eq!(data.len(), 24);
2257 for (i, val) in data.iter().enumerate() {
2258 assert_eq!(*val, i as i32);
2259 }
2260 }
2261
2262 std::fs::remove_file(&path).ok();
2263 }
2264
2265 #[test]
2266 #[cfg(feature = "deflate")]
2267 fn compressed_chunked_roundtrip() {
2268 let path = temp_path("compressed_roundtrip");
2269
2270 {
2272 let file = H5File::create(&path).unwrap();
2273 let ds = file
2274 .new_dataset::<f64>()
2275 .shape([0usize, 4])
2276 .chunk(&[1, 4])
2277 .max_shape(&[None, Some(4)])
2278 .deflate(6)
2279 .create("compressed")
2280 .unwrap();
2281
2282 for frame in 0..10u64 {
2283 let vals: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
2284 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2285 ds.write_chunk(frame as usize, &raw).unwrap();
2286 }
2287 ds.extend(&[10, 4]).unwrap();
2288 file.close().unwrap();
2289 }
2290
2291 {
2293 let file = H5File::open(&path).unwrap();
2294 let ds = file.dataset("compressed").unwrap();
2295 assert_eq!(ds.shape(), vec![10, 4]);
2296 let data = ds.read_raw::<f64>().unwrap();
2297 assert_eq!(data.len(), 40);
2298 for (i, val) in data.iter().enumerate() {
2299 assert!(
2300 (val - i as f64).abs() < 1e-10,
2301 "mismatch at {}: {} != {}",
2302 i,
2303 val,
2304 i
2305 );
2306 }
2307 }
2308
2309 std::fs::remove_file(&path).ok();
2310 }
2311
2312 #[test]
2313 #[cfg(feature = "deflate")]
2314 fn compressed_chunked_many_frames() {
2315 let path = temp_path("compressed_many");
2316
2317 {
2318 let file = H5File::create(&path).unwrap();
2319 let ds = file
2320 .new_dataset::<i32>()
2321 .shape([0usize, 3])
2322 .chunk(&[1, 3])
2323 .max_shape(&[None, Some(3)])
2324 .deflate(6)
2325 .create("stream")
2326 .unwrap();
2327
2328 for frame in 0..100u64 {
2329 let vals: Vec<i32> = (0..3).map(|i| (frame * 3 + i) as i32).collect();
2330 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
2331 ds.write_chunk(frame as usize, &raw).unwrap();
2332 }
2333 ds.extend(&[100, 3]).unwrap();
2334 file.close().unwrap();
2335 }
2336
2337 {
2338 let file = H5File::open(&path).unwrap();
2339 let ds = file.dataset("stream").unwrap();
2340 assert_eq!(ds.shape(), vec![100, 3]);
2341 let data = ds.read_raw::<i32>().unwrap();
2342 assert_eq!(data.len(), 300);
2343 for (i, val) in data.iter().enumerate() {
2344 assert_eq!(*val, i as i32, "mismatch at {}", i);
2345 }
2346 }
2347
2348 std::fs::remove_file(&path).ok();
2349 }
2350 #[test]
2351 fn append_mode() {
2352 let path = temp_path("append");
2353
2354 {
2356 let file = H5File::create(&path).unwrap();
2357 let ds = file
2358 .new_dataset::<i32>()
2359 .shape([3usize])
2360 .create("first")
2361 .unwrap();
2362 ds.write_raw(&[1i32, 2, 3]).unwrap();
2363 file.close().unwrap();
2364 }
2365
2366 {
2368 let file = H5File::open_rw(&path).unwrap();
2369 let ds = file
2370 .new_dataset::<f64>()
2371 .shape([2usize])
2372 .create("second")
2373 .unwrap();
2374 ds.write_raw(&[4.0f64, 5.0]).unwrap();
2375 file.close().unwrap();
2376 }
2377
2378 {
2380 let file = H5File::open(&path).unwrap();
2381 let names = file.dataset_names();
2382 assert!(names.contains(&"first".to_string()));
2383 assert!(names.contains(&"second".to_string()));
2384
2385 let ds1 = file.dataset("first").unwrap();
2386 assert_eq!(ds1.read_raw::<i32>().unwrap(), vec![1, 2, 3]);
2387
2388 let ds2 = file.dataset("second").unwrap();
2389 assert_eq!(ds2.read_raw::<f64>().unwrap(), vec![4.0, 5.0]);
2390 }
2391
2392 std::fs::remove_file(&path).ok();
2393 }
2394
2395 #[test]
2396 fn open_rw_set_attr_preserves_file() {
2397 let path = temp_path("open_rw_attr");
2398 {
2400 let file = H5File::create(&path).unwrap();
2401 let ds = file
2402 .new_dataset::<i32>()
2403 .shape([3usize])
2404 .create("data")
2405 .unwrap();
2406 ds.write_raw(&[10i32, 20, 30]).unwrap();
2407 file.set_attr_string("version", "1.0").unwrap();
2408 file.close().unwrap();
2409 }
2410 {
2412 let file = H5File::open_rw(&path).unwrap();
2413 file.set_attr_string("version", "2.0").unwrap();
2414 file.close().unwrap();
2415 }
2416 {
2418 let file = H5File::open(&path).unwrap();
2419 let ds = file.dataset("data").unwrap();
2420 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![10, 20, 30]);
2421 let ver = file.attr_string("version").unwrap();
2422 assert_eq!(ver, "2.0");
2423 }
2424 std::fs::remove_file(&path).ok();
2425 }
2426
2427 #[test]
2428 #[cfg(feature = "deflate")]
2429 fn open_rw_attr_with_compressed_dataset() {
2430 use crate::format::messages::filter::FilterPipeline;
2431 let path = temp_path("open_rw_compressed");
2432 let input: Vec<&str> = (0..50).map(|_| "test string data").collect();
2433 {
2435 let file = H5File::create(&path).unwrap();
2436 file.write_vlen_strings_compressed("texts", &input, 16, FilterPipeline::deflate(6))
2437 .unwrap();
2438 file.set_attr_string("version", "1.0").unwrap();
2439 file.close().unwrap();
2440 }
2441 {
2443 let file = H5File::open_rw(&path).unwrap();
2444 file.set_attr_string("version", "2.0").unwrap();
2445 file.close().unwrap();
2446 }
2447 {
2449 let file = H5File::open(&path).unwrap();
2450 let ds = file.dataset("texts").unwrap();
2451 let strings = ds.read_vlen_strings().unwrap();
2452 assert_eq!(strings.len(), 50);
2453 assert_eq!(strings[0], "test string data");
2454 let ver = file.attr_string("version").unwrap();
2455 assert_eq!(ver, "2.0");
2456 }
2457 std::fs::remove_file(&path).ok();
2458 }
2459
2460 #[test]
2461 #[cfg(feature = "lz4")]
2462 fn append_vlen_strings_basic() {
2463 use crate::format::messages::filter::FilterPipeline;
2464 let path = temp_path("append_vlen");
2465 {
2466 let file = H5File::create(&path).unwrap();
2467 file.create_appendable_vlen_dataset("names", 4, Some(FilterPipeline::lz4()))
2468 .unwrap();
2469 file.append_vlen_strings("names", &["alice", "bob", "charlie"])
2470 .unwrap();
2471 file.append_vlen_strings("names", &["dave", "eve"]).unwrap();
2472 file.close().unwrap();
2473 }
2474 {
2475 let file = H5File::open(&path).unwrap();
2476 let ds = file.dataset("names").unwrap();
2477 let strings = ds.read_vlen_strings().unwrap();
2478 assert_eq!(strings, vec!["alice", "bob", "charlie", "dave", "eve"]);
2479 }
2480 std::fs::remove_file(&path).ok();
2481 }
2482
2483 #[test]
2484 #[cfg(feature = "lz4")]
2485 fn append_vlen_strings_large() {
2486 use crate::format::messages::filter::FilterPipeline;
2487 let path = temp_path("append_vlen_large");
2488 let batch1: Vec<String> = (0..5000).map(|i| format!("node-{:06}", i)).collect();
2489 let batch2: Vec<String> = (5000..7189).map(|i| format!("node-{:06}", i)).collect();
2490 {
2491 let file = H5File::create(&path).unwrap();
2492 file.create_appendable_vlen_dataset("data", 512, Some(FilterPipeline::lz4()))
2493 .unwrap();
2494 let r1: Vec<&str> = batch1.iter().map(|s| s.as_str()).collect();
2495 file.append_vlen_strings("data", &r1).unwrap();
2496 let r2: Vec<&str> = batch2.iter().map(|s| s.as_str()).collect();
2497 file.append_vlen_strings("data", &r2).unwrap();
2498 file.close().unwrap();
2499 }
2500 {
2501 let file = H5File::open(&path).unwrap();
2502 let ds = file.dataset("data").unwrap();
2503 let strings = ds.read_vlen_strings().unwrap();
2504 assert_eq!(strings.len(), 7189);
2505 assert_eq!(strings[0], "node-000000");
2506 assert_eq!(strings[7188], "node-007188");
2507 }
2508 std::fs::remove_file(&path).ok();
2509 }
2510
2511 #[test]
2512 fn append_vlen_strings_uncompressed() {
2513 let path = temp_path("append_vlen_unc");
2514 {
2515 let file = H5File::create(&path).unwrap();
2516 file.create_appendable_vlen_dataset("texts", 8, None)
2517 .unwrap();
2518 file.append_vlen_strings("texts", &["hello", "world"])
2519 .unwrap();
2520 file.append_vlen_strings("texts", &["foo", "bar", "baz"])
2521 .unwrap();
2522 file.close().unwrap();
2523 }
2524 {
2525 let file = H5File::open(&path).unwrap();
2526 let ds = file.dataset("texts").unwrap();
2527 let strings = ds.read_vlen_strings().unwrap();
2528 assert_eq!(strings, vec!["hello", "world", "foo", "bar", "baz"]);
2529 }
2530 std::fs::remove_file(&path).ok();
2531 }
2532
2533 #[test]
2534 fn delete_dataset_roundtrip() {
2535 let path = temp_path("delete_ds");
2536 {
2537 let file = H5File::create(&path).unwrap();
2538 file.write_vlen_strings("keep", &["a", "b"]).unwrap();
2539 file.write_vlen_strings("remove", &["x", "y"]).unwrap();
2540 file.delete_dataset("remove").unwrap();
2541 file.close().unwrap();
2542 }
2543 {
2544 let file = H5File::open(&path).unwrap();
2545 let names = file.dataset_names();
2546 assert!(names.contains(&"keep".to_string()));
2547 assert!(!names.contains(&"remove".to_string()));
2548 let ds = file.dataset("keep").unwrap();
2549 assert_eq!(ds.read_vlen_strings().unwrap(), vec!["a", "b"]);
2550 }
2551 std::fs::remove_file(&path).ok();
2552 }
2553
2554 #[test]
2555 fn delete_group_roundtrip() {
2556 let path = temp_path("delete_grp");
2557 {
2558 let file = H5File::create(&path).unwrap();
2559 let g1 = file.create_group("keep").unwrap();
2560 g1.write_vlen_strings("data", &["a"]).unwrap();
2561 let g2 = file.create_group("remove").unwrap();
2562 g2.write_vlen_strings("data", &["x"]).unwrap();
2563 file.delete_group("remove").unwrap();
2564 file.close().unwrap();
2565 }
2566 {
2567 let file = H5File::open(&path).unwrap();
2568 let names = file.dataset_names();
2569 assert!(names.contains(&"keep/data".to_string()));
2570 assert!(!names.contains(&"remove/data".to_string()));
2571 }
2572 std::fs::remove_file(&path).ok();
2573 }
2574
2575 #[test]
2576 fn open_rw_delete_recreate_group() {
2577 let path = temp_path("rw_delete_recreate");
2578 {
2580 let file = H5File::create(&path).unwrap();
2581 let n = file.create_group("nodes").unwrap();
2582 n.write_vlen_strings("id", &["a", "b", "c"]).unwrap();
2583 let e = file.create_group("edges").unwrap();
2584 e.write_vlen_strings("src", &["x", "y"]).unwrap();
2585 file.close().unwrap();
2586 }
2587 {
2589 let file = H5File::open_rw(&path).unwrap();
2590 file.delete_group("nodes").unwrap();
2591 let n = file.create_group("nodes").unwrap();
2592 n.write_vlen_strings("id", &["new1", "new2"]).unwrap();
2593 file.close().unwrap();
2594 }
2595 {
2597 let file = H5File::open(&path).unwrap();
2598 let ds = file.dataset("nodes/id").unwrap();
2599 let s = ds.read_vlen_strings().unwrap();
2600 assert_eq!(s, vec!["new1", "new2"]);
2601 let ds = file.dataset("edges/src").unwrap();
2603 let s = ds.read_vlen_strings().unwrap();
2604 assert_eq!(s, vec!["x", "y"]);
2605 }
2606 std::fs::remove_file(&path).ok();
2607 }
2608
2609 #[test]
2610 fn delete_and_recreate_group() {
2611 let path = temp_path("delete_recreate");
2612 {
2613 let file = H5File::create(&path).unwrap();
2614 let g = file.create_group("nodes").unwrap();
2615 g.write_vlen_strings("id", &["old1", "old2"]).unwrap();
2616 file.delete_group("nodes").unwrap();
2617 let g = file.create_group("nodes").unwrap();
2618 g.write_vlen_strings("id", &["new1", "new2", "new3"])
2619 .unwrap();
2620 file.close().unwrap();
2621 }
2622 {
2623 let file = H5File::open(&path).unwrap();
2624 let ds = file.dataset("nodes/id").unwrap();
2625 let strings = ds.read_vlen_strings().unwrap();
2626 assert_eq!(strings, vec!["new1", "new2", "new3"]);
2627 }
2628 std::fs::remove_file(&path).ok();
2629 }
2630
2631 #[test]
2632 #[cfg(feature = "deflate")]
2633 fn vlen_string_compressed_large_roundtrip() {
2634 use crate::format::messages::filter::FilterPipeline;
2635 let path = temp_path("vlen_large");
2636 let input: Vec<String> = (0..7189)
2638 .map(|i| format!("node-{:08x}-{}", i, "a".repeat(20 + (i % 30))))
2639 .collect();
2640 let input_refs: Vec<&str> = input.iter().map(|s| s.as_str()).collect();
2641 {
2642 let file = H5File::create(&path).unwrap();
2643 file.create_group("nodes").unwrap();
2644 file.write_vlen_strings_compressed(
2645 "nodes/id",
2646 &input_refs,
2647 512,
2648 FilterPipeline::deflate(6),
2649 )
2650 .unwrap();
2651 file.close().unwrap();
2652 }
2653 {
2655 let file = H5File::open(&path).unwrap();
2656 let ds = file.dataset("nodes/id").unwrap();
2657 let strings = ds.read_vlen_strings().unwrap();
2658 assert_eq!(strings.len(), 7189);
2659 assert_eq!(strings[0], input[0]);
2660 assert_eq!(strings[7188], input[7188]);
2661 }
2662 {
2664 let file = H5File::open_rw(&path).unwrap();
2665 file.set_attr_string("version", "1.0").unwrap();
2666 file.close().unwrap();
2667 }
2668 {
2669 let file = H5File::open(&path).unwrap();
2670 let ds = file.dataset("nodes/id").unwrap();
2671 let strings = ds.read_vlen_strings().unwrap();
2672 assert_eq!(strings.len(), 7189);
2673 assert_eq!(strings[0], input[0]);
2674 }
2675 std::fs::remove_file(&path).ok();
2676 }
2677
2678 #[test]
2679 fn vlen_string_write_read() {
2680 let path = temp_path("vlen_wr");
2681 {
2682 let file = H5File::create(&path).unwrap();
2683 file.write_vlen_strings("names", &["alice", "bob", "charlie"])
2684 .unwrap();
2685 file.close().unwrap();
2686 }
2687 {
2688 let file = H5File::open(&path).unwrap();
2689 let ds = file.dataset("names").unwrap();
2690 let strings = ds.read_vlen_strings().unwrap();
2691 assert_eq!(strings, vec!["alice", "bob", "charlie"]);
2692 }
2693 std::fs::remove_file(&path).ok();
2694 }
2695
2696 #[test]
2701 fn vlen_string_writers_declare_their_character_set() {
2702 use crate::format::messages::datatype::DatatypeMessage;
2703
2704 let path = temp_path("vlen_cset");
2705 let file = H5File::create(&path).unwrap();
2706 file.write_vlen_strings_ascii("ascii", &["alpha", "b", ""])
2707 .unwrap();
2708 file.write_vlen_strings("utf8", &["été", "日本"]).unwrap();
2709 let err = file
2710 .write_vlen_strings_ascii("rejected", &["ok", "안녕"])
2711 .err()
2712 .expect("a non-ASCII string was accepted under an ASCII datatype")
2713 .to_string();
2714 assert!(
2715 err.contains("string 1") && err.contains("is not ASCII"),
2716 "got: {err}"
2717 );
2718 file.close().unwrap();
2719
2720 let file = H5File::open(&path).unwrap();
2721 let ascii = file.dataset("ascii").unwrap();
2722 assert_eq!(
2723 ascii.datatype().unwrap(),
2724 DatatypeMessage::VarLenString {
2725 padding: 0,
2726 charset: 0,
2727 }
2728 );
2729 assert_eq!(ascii.read_strings().unwrap(), vec!["alpha", "b", ""]);
2730 let utf8 = file.dataset("utf8").unwrap();
2731 assert_eq!(
2732 utf8.datatype().unwrap(),
2733 DatatypeMessage::VarLenString {
2734 padding: 0,
2735 charset: 1,
2736 }
2737 );
2738 assert_eq!(utf8.read_strings().unwrap(), vec!["été", "日本"]);
2739 assert!(file.dataset("rejected").is_err());
2741 std::fs::remove_file(&path).ok();
2742 }
2743
2744 #[test]
2747 fn group_vlen_string_writer_declares_ascii() {
2748 use crate::format::messages::datatype::DatatypeMessage;
2749
2750 let path = temp_path("vlen_cset_group");
2751 let file = H5File::create(&path).unwrap();
2752 let g = file.create_group("entry").unwrap();
2753 g.write_vlen_strings_ascii("notes", &["alpha", "b"])
2754 .unwrap();
2755 let err = g
2756 .write_vlen_strings_ascii("rejected", &["안녕"])
2757 .err()
2758 .expect("a non-ASCII string was accepted under an ASCII datatype")
2759 .to_string();
2760 assert!(err.contains("is not ASCII"), "got: {err}");
2761 file.close().unwrap();
2762
2763 let file = H5File::open(&path).unwrap();
2764 let ds = file.dataset("entry/notes").unwrap();
2765 assert_eq!(
2766 ds.datatype().unwrap(),
2767 DatatypeMessage::VarLenString {
2768 padding: 0,
2769 charset: 0,
2770 }
2771 );
2772 assert_eq!(ds.read_strings().unwrap(), vec!["alpha", "b"]);
2773 std::fs::remove_file(&path).ok();
2774 }
2775
2776 #[test]
2777 fn vlen_bytes_write_read() {
2778 let path = temp_path("vlen_bytes_wr");
2779 let items: [&[u8]; 4] = [b"abc", b"", &[0u8, 1, 2, 255], b"hi"];
2780 {
2781 let file = H5File::create(&path).unwrap();
2782 file.write_vlen_bytes("blobs", &items).unwrap();
2783 file.close().unwrap();
2784 }
2785 {
2786 let file = H5File::open(&path).unwrap();
2787 let ds = file.dataset("blobs").unwrap();
2788 let got = ds.read_vlen_bytes().unwrap();
2789 let expected: Vec<Vec<u8>> = items.iter().map(|s| s.to_vec()).collect();
2790 assert_eq!(got, expected);
2791 }
2792 std::fs::remove_file(&path).ok();
2793 }
2794
2795 #[test]
2799 fn vlen_numeric_write_read() {
2800 use crate::format::global_heap::decode_vlen_reference;
2801 use crate::format::messages::datatype::DatatypeMessage;
2802
2803 let path = temp_path("vlen_numeric_wr");
2804 let a: &[i32] = &[1, 2, 3];
2805 let b: &[i32] = &[];
2806 let c: &[i32] = &[-7];
2807 {
2808 let file = H5File::create(&path).unwrap();
2809 file.write_vlen_numeric("data", &[a, b, c]).unwrap();
2810 let f64s: &[f64] = &[1.5, -2.5];
2811 file.write_vlen_numeric("wide", &[f64s]).unwrap();
2812 file.close().unwrap();
2813 }
2814 let file = H5File::open(&path).unwrap();
2815 let ds = file.dataset("data").unwrap();
2816 assert_eq!(
2817 ds.datatype().unwrap(),
2818 DatatypeMessage::VarLenSequence {
2819 base: Box::new(DatatypeMessage::i32_type()),
2820 }
2821 );
2822 let decoded: Vec<Vec<i32>> = ds
2823 .read_vlen_bytes()
2824 .unwrap()
2825 .iter()
2826 .map(|item| {
2827 item.as_chunks::<4>()
2828 .0
2829 .iter()
2830 .map(|w| i32::from_le_bytes(*w))
2831 .collect()
2832 })
2833 .collect();
2834 assert_eq!(decoded, vec![a.to_vec(), b.to_vec(), c.to_vec()]);
2835
2836 let ctx = crate::format::FormatContext::default_v3();
2838 let raw = ds.read_raw_bytes().unwrap();
2839 let (seq_len, _, _) = decode_vlen_reference(&raw, &ctx).unwrap();
2840 assert_eq!(seq_len, 3);
2841
2842 let wide = file.dataset("wide").unwrap();
2843 assert_eq!(
2844 wide.datatype().unwrap(),
2845 DatatypeMessage::VarLenSequence {
2846 base: Box::new(DatatypeMessage::f64_type()),
2847 }
2848 );
2849 let (seq_len, _, _) = decode_vlen_reference(&wide.read_raw_bytes().unwrap(), &ctx).unwrap();
2850 assert_eq!(seq_len, 2);
2851 drop(file);
2852 std::fs::remove_file(&path).ok();
2853 }
2854
2855 #[test]
2858 fn vlen_bytes_is_the_u8_case_of_vlen_numeric() {
2859 use crate::format::messages::datatype::DatatypeMessage;
2860
2861 let path = temp_path("vlen_bytes_u8");
2862 let items: [&[u8]; 2] = [b"abc", b""];
2863 {
2864 let file = H5File::create(&path).unwrap();
2865 file.write_vlen_bytes("blobs", &items).unwrap();
2866 file.close().unwrap();
2867 }
2868 let file = H5File::open(&path).unwrap();
2869 let ds = file.dataset("blobs").unwrap();
2870 assert_eq!(ds.datatype().unwrap(), DatatypeMessage::vlen_bytes());
2871 let ctx = crate::format::FormatContext::default_v3();
2872 let (seq_len, _, _) =
2873 crate::format::global_heap::decode_vlen_reference(&ds.read_raw_bytes().unwrap(), &ctx)
2874 .unwrap();
2875 assert_eq!(seq_len, 3);
2876 drop(file);
2877 std::fs::remove_file(&path).ok();
2878 }
2879
2880 #[test]
2881 fn vlen_bytes_in_group_with_attribute() {
2882 use crate::types::VarLenUnicode;
2883 let path = temp_path("vlen_bytes_grp");
2884 let items: [&[u8]; 2] = [&[1u8, 2, 3], &[9u8, 8, 7, 6]];
2885 {
2886 let file = H5File::create(&path).unwrap();
2887 let grp = file.root_group().create_group("payloads").unwrap();
2888 let ds = grp.write_vlen_bytes("frames", &items).unwrap();
2889 ds.new_attr::<VarLenUnicode>()
2890 .shape(())
2891 .create("codec")
2892 .unwrap()
2893 .write_string("raw")
2894 .unwrap();
2895 file.close().unwrap();
2896 }
2897 {
2898 let file = H5File::open(&path).unwrap();
2899 let ds = file.dataset("payloads/frames").unwrap();
2900 let got = ds.read_vlen_bytes().unwrap();
2901 let expected: Vec<Vec<u8>> = items.iter().map(|s| s.to_vec()).collect();
2902 assert_eq!(got, expected);
2903 }
2904 std::fs::remove_file(&path).ok();
2905 }
2906
2907 #[test]
2908 fn vlen_dataset_returns_handle_for_attributes() {
2909 use crate::types::VarLenUnicode;
2910 let path = temp_path("vlen_attr");
2911 {
2912 let file = H5File::create(&path).unwrap();
2913 let grp = file.root_group().create_group("ch").unwrap();
2914 let ds = grp
2917 .write_vlen_strings("labels", &["a", "bb", "ccc"])
2918 .unwrap();
2919 ds.new_attr::<VarLenUnicode>()
2920 .shape(())
2921 .create("unit")
2922 .unwrap()
2923 .write_string("volt")
2924 .unwrap();
2925 let ds2 = grp.dataset_writer("labels").unwrap();
2927 ds2.new_attr::<VarLenUnicode>()
2928 .shape(())
2929 .create("desc")
2930 .unwrap()
2931 .write_string("channel labels")
2932 .unwrap();
2933 file.close().unwrap();
2934 }
2935 {
2936 let file = H5File::open(&path).unwrap();
2937 let ds = file.dataset("ch/labels").unwrap();
2938 assert_eq!(ds.read_vlen_strings().unwrap(), vec!["a", "bb", "ccc"]);
2939 assert_eq!(ds.attr("unit").unwrap().read_string().unwrap(), "volt");
2940 assert_eq!(
2941 ds.attr("desc").unwrap().read_string().unwrap(),
2942 "channel labels"
2943 );
2944 }
2945 std::fs::remove_file(&path).ok();
2946 }
2947
2948 #[test]
2949 #[cfg(feature = "deflate")]
2950 fn vlen_string_deflate_roundtrip() {
2951 use crate::format::messages::filter::FilterPipeline;
2952 let path = temp_path("vlen_deflate");
2953 let input: Vec<&str> = (0..100)
2954 .map(|i| match i % 3 {
2955 0 => "hello world",
2956 1 => "compressed vlen string test",
2957 _ => "rust-hdf5",
2958 })
2959 .collect();
2960 {
2961 let file = H5File::create(&path).unwrap();
2962 file.write_vlen_strings_compressed("texts", &input, 16, FilterPipeline::deflate(6))
2963 .unwrap();
2964 file.close().unwrap();
2965 }
2966 {
2967 let file = H5File::open(&path).unwrap();
2968 let ds = file.dataset("texts").unwrap();
2969 let strings = ds.read_vlen_strings().unwrap();
2970 assert_eq!(strings.len(), 100);
2971 for (i, s) in strings.iter().enumerate() {
2972 assert_eq!(s, input[i]);
2973 }
2974 }
2975 std::fs::remove_file(&path).ok();
2976 }
2977
2978 #[test]
2979 #[cfg(feature = "zstd")]
2980 fn vlen_string_zstd_roundtrip() {
2981 use crate::format::messages::filter::FilterPipeline;
2982 let path = temp_path("vlen_zstd");
2983 let input: Vec<&str> = (0..200)
2984 .map(|i| match i % 4 {
2985 0 => "zstandard compression test",
2986 1 => "variable length string",
2987 2 => "rust-hdf5 chunked storage",
2988 _ => "hello zstd world",
2989 })
2990 .collect();
2991 {
2992 let file = H5File::create(&path).unwrap();
2993 file.write_vlen_strings_compressed("data", &input, 32, FilterPipeline::zstd(3))
2994 .unwrap();
2995 file.close().unwrap();
2996 }
2997 {
2998 let file = H5File::open(&path).unwrap();
2999 let ds = file.dataset("data").unwrap();
3000 let strings = ds.read_vlen_strings().unwrap();
3001 assert_eq!(strings.len(), 200);
3002 for (i, s) in strings.iter().enumerate() {
3003 assert_eq!(s, input[i]);
3004 }
3005 }
3006 std::fs::remove_file(&path).ok();
3007 }
3008
3009 #[test]
3010 #[cfg(feature = "deflate")]
3011 fn shuffle_deflate_roundtrip() {
3012 let path = temp_path("shuf_defl");
3013 {
3014 let file = H5File::create(&path).unwrap();
3015 let ds = file
3016 .new_dataset::<f64>()
3017 .shape([0usize, 4])
3018 .chunk(&[1, 4])
3019 .max_shape(&[None, Some(4)])
3020 .shuffle_deflate(6)
3021 .create("data")
3022 .unwrap();
3023 for frame in 0..20u64 {
3024 let vals: Vec<f64> = (0..4).map(|i| (frame * 4 + i) as f64).collect();
3025 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3026 ds.write_chunk(frame as usize, &raw).unwrap();
3027 }
3028 ds.extend(&[20, 4]).unwrap();
3029 file.close().unwrap();
3030 }
3031 {
3032 let file = H5File::open(&path).unwrap();
3033 let ds = file.dataset("data").unwrap();
3034 assert_eq!(ds.shape(), vec![20, 4]);
3035 let data = ds.read_raw::<f64>().unwrap();
3036 assert_eq!(data.len(), 80);
3037 for (i, val) in data.iter().enumerate() {
3038 assert!((val - i as f64).abs() < 1e-10);
3039 }
3040 }
3041 std::fs::remove_file(&path).ok();
3042 }
3043
3044 #[test]
3045 fn file_level_attributes() {
3046 let path = temp_path("file_attr");
3047 {
3048 let file = H5File::create(&path).unwrap();
3049 file.set_attr_string("title", "Test File").unwrap();
3050 file.set_attr_numeric("version", &42i32).unwrap();
3051 let ds = file
3052 .new_dataset::<u8>()
3053 .shape([1usize])
3054 .create("dummy")
3055 .unwrap();
3056 ds.write_raw(&[0u8]).unwrap();
3057 file.close().unwrap();
3058 }
3059 {
3060 let file = H5File::open(&path).unwrap();
3061 assert!(file.dataset_names().contains(&"dummy".to_string()));
3062
3063 let names = file.attr_names().unwrap();
3065 assert!(names.contains(&"title".to_string()));
3066
3067 let title = file.attr_string("title").unwrap();
3068 assert_eq!(title, "Test File");
3069 }
3070 std::fs::remove_file(&path).ok();
3071 }
3072
3073 #[test]
3074 fn scalar_dataset_roundtrip() {
3075 let path = temp_path("scalar");
3076 {
3077 let file = H5File::create(&path).unwrap();
3078 let ds = file.new_dataset::<f64>().scalar().create("pi").unwrap();
3079 ds.write_raw(&[std::f64::consts::PI]).unwrap();
3080 file.close().unwrap();
3081 }
3082 {
3083 let file = H5File::open(&path).unwrap();
3084 let ds = file.dataset("pi").unwrap();
3085 assert_eq!(ds.shape(), Vec::<usize>::new());
3086 assert_eq!(ds.total_elements(), 1);
3087 let data = ds.read_raw::<f64>().unwrap();
3088 assert_eq!(data.len(), 1);
3089 assert!((data[0] - std::f64::consts::PI).abs() < 1e-15);
3090 }
3091 std::fs::remove_file(&path).ok();
3092 }
3093
3094 #[test]
3095 fn append_mode_extend_chunked() {
3096 let path = temp_path("append_extend");
3097
3098 {
3100 let file = H5File::create(&path).unwrap();
3101 let ds = file
3102 .new_dataset::<i32>()
3103 .shape([0usize, 3])
3104 .chunk(&[1, 3])
3105 .max_shape(&[None, Some(3)])
3106 .create("stream")
3107 .unwrap();
3108 for i in 0..5u64 {
3109 let vals: Vec<i32> = (0..3).map(|j| (i * 3 + j) as i32).collect();
3110 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3111 ds.write_chunk(i as usize, &raw).unwrap();
3112 }
3113 ds.extend(&[5, 3]).unwrap();
3114 file.close().unwrap();
3115 }
3116
3117 {
3119 let file = H5File::open_rw(&path).unwrap();
3120 let names = file.dataset_names();
3122 assert!(names.contains(&"stream".to_string()));
3123
3124 let mut inner = crate::file::borrow_inner_mut(&file.inner);
3126 if let crate::file::H5FileInner::Writer(writer) = &mut *inner {
3127 let ds_idx = writer.dataset_index("stream").unwrap();
3128 for i in 5..10u64 {
3129 let vals: Vec<i32> = (0..3).map(|j| (i * 3 + j) as i32).collect();
3130 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3131 writer.write_chunk(ds_idx, i, &raw).unwrap();
3132 }
3133 writer.extend_dataset(ds_idx, &[10, 3]).unwrap();
3134 }
3135 drop(inner);
3136 file.close().unwrap();
3137 }
3138
3139 {
3141 let file = H5File::open(&path).unwrap();
3142 let ds = file.dataset("stream").unwrap();
3143 assert_eq!(ds.shape(), vec![10, 3]);
3144 let data = ds.read_raw::<i32>().unwrap();
3145 assert_eq!(data.len(), 30);
3146 for (i, val) in data.iter().enumerate() {
3147 assert_eq!(*val, i as i32, "mismatch at {}", i);
3148 }
3149 }
3150
3151 std::fs::remove_file(&path).ok();
3152 }
3153
3154 #[test]
3155 fn group_hierarchy_roundtrip() {
3156 let path = temp_path("groups_rt");
3157
3158 {
3159 let file = H5File::create(&path).unwrap();
3160 let root = file.root_group();
3161
3162 let det = root.create_group("detector").unwrap();
3164 let raw = det.create_group("raw").unwrap();
3165
3166 let ds1 = det
3168 .new_dataset::<f32>()
3169 .shape([10usize])
3170 .create("temperature")
3171 .unwrap();
3172 ds1.write_raw(&[1.0f32; 10]).unwrap();
3173
3174 let ds2 = raw
3175 .new_dataset::<u16>()
3176 .shape([4usize, 4])
3177 .create("image")
3178 .unwrap();
3179 ds2.write_raw(&[42u16; 16]).unwrap();
3180
3181 let ds3 = file
3183 .new_dataset::<i32>()
3184 .shape([3usize])
3185 .create("version")
3186 .unwrap();
3187 ds3.write_raw(&[1i32, 0, 0]).unwrap();
3188
3189 file.close().unwrap();
3190 }
3191
3192 {
3193 let file = H5File::open(&path).unwrap();
3194 let names = file.dataset_names();
3195 assert!(names.contains(&"version".to_string()));
3196 assert!(names.contains(&"detector/temperature".to_string()));
3197 assert!(names.contains(&"detector/raw/image".to_string()));
3198
3199 let ds = file.dataset("version").unwrap();
3201 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![1, 0, 0]);
3202
3203 let ds = file.dataset("detector/temperature").unwrap();
3204 assert_eq!(ds.read_raw::<f32>().unwrap(), vec![1.0f32; 10]);
3205
3206 let ds = file.dataset("detector/raw/image").unwrap();
3207 assert_eq!(ds.shape(), vec![4, 4]);
3208 assert_eq!(ds.read_raw::<u16>().unwrap(), vec![42u16; 16]);
3209
3210 let root = file.root_group();
3212 let group_names = root.group_names().unwrap();
3213 assert!(group_names.contains(&"detector".to_string()));
3214 }
3215
3216 std::fs::remove_file(&path).ok();
3217 }
3218
3219 #[test]
3220 fn nested_groups_via_file_create_group() {
3221 let path = temp_path("file_create_group");
3222
3223 {
3224 let file = H5File::create(&path).unwrap();
3225
3226 let grp = file.create_group("sensors").unwrap();
3228 let sub = grp.create_group("accel").unwrap();
3229
3230 let ds = sub
3231 .new_dataset::<f64>()
3232 .shape([3usize])
3233 .create("xyz")
3234 .unwrap();
3235 ds.write_raw(&[1.0f64, 2.0, 3.0]).unwrap();
3236
3237 file.close().unwrap();
3238 }
3239
3240 {
3241 let file = H5File::open(&path).unwrap();
3242 let names = file.dataset_names();
3243 assert!(names.contains(&"sensors/accel/xyz".to_string()));
3244
3245 let ds = file.dataset("sensors/accel/xyz").unwrap();
3246 assert_eq!(ds.read_raw::<f64>().unwrap(), vec![1.0, 2.0, 3.0]);
3247
3248 let root = file.root_group();
3250 let sensors = root.group("sensors").unwrap();
3251 assert_eq!(sensors.name(), "/sensors");
3252
3253 let accel = sensors.group("accel").unwrap();
3254 assert_eq!(accel.name(), "/sensors/accel");
3255
3256 let top_groups = root.group_names().unwrap();
3258 assert!(top_groups.contains(&"sensors".to_string()));
3259
3260 let sub_groups = sensors.group_names().unwrap();
3262 assert!(sub_groups.contains(&"accel".to_string()));
3263 }
3264
3265 std::fs::remove_file(&path).ok();
3266 }
3267}
3268
3269#[cfg(test)]
3270mod h5py_compat_tests {
3271 use super::*;
3272
3273 fn temp_path(name: &str) -> std::path::PathBuf {
3274 super::unique_test_path(name)
3275 }
3276
3277 #[test]
3279 #[cfg(feature = "deflate")]
3280 fn h5dump_validates_our_files() {
3281 let h5dump = std::process::Command::new("h5dump")
3283 .arg("--version")
3284 .output();
3285 if h5dump.is_err() {
3286 eprintln!("skipping: h5dump not found");
3287 return;
3288 }
3289
3290 let path = temp_path("h5dump_validate");
3291
3292 {
3294 let file = H5File::create(&path).unwrap();
3295
3296 let ds = file
3298 .new_dataset::<f64>()
3299 .shape([3usize, 4])
3300 .create("matrix")
3301 .unwrap();
3302 let data: Vec<f64> = (0..12).map(|i| i as f64).collect();
3303 ds.write_raw(&data).unwrap();
3304
3305 let ds2 = file
3307 .new_dataset::<i32>()
3308 .shape([0usize, 2])
3309 .chunk(&[1, 2])
3310 .max_shape(&[None, Some(2)])
3311 .deflate(6)
3312 .create("stream")
3313 .unwrap();
3314 for i in 0..5u64 {
3315 let vals: Vec<i32> = vec![i as i32 * 2, i as i32 * 2 + 1];
3316 let raw: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
3317 ds2.write_chunk(i as usize, &raw).unwrap();
3318 }
3319 ds2.extend(&[5, 2]).unwrap();
3320
3321 let grp = file.create_group("meta").unwrap();
3323 let ds3 = grp
3324 .new_dataset::<u8>()
3325 .shape([4usize])
3326 .create("flags")
3327 .unwrap();
3328 ds3.write_raw(&[1u8, 0, 1, 0]).unwrap();
3329
3330 use crate::types::VarLenUnicode;
3332 let attr = ds
3333 .new_attr::<VarLenUnicode>()
3334 .shape(())
3335 .create("units")
3336 .unwrap();
3337 attr.write_string("meters").unwrap();
3338
3339 file.close().unwrap();
3340 }
3341
3342 let output = std::process::Command::new("h5dump")
3344 .arg("-H") .arg(path.to_str().unwrap())
3346 .output()
3347 .unwrap();
3348
3349 assert!(
3350 output.status.success(),
3351 "h5dump failed:\nstdout: {}\nstderr: {}",
3352 String::from_utf8_lossy(&output.stdout),
3353 String::from_utf8_lossy(&output.stderr),
3354 );
3355
3356 let output2 = std::process::Command::new("h5dump")
3358 .arg(path.to_str().unwrap())
3359 .output()
3360 .unwrap();
3361
3362 assert!(
3363 output2.status.success(),
3364 "h5dump (full) failed:\nstderr: {}",
3365 String::from_utf8_lossy(&output2.stderr),
3366 );
3367
3368 std::fs::remove_file(&path).ok();
3369 }
3370
3371 #[test]
3372 fn read_h5py_generated_file() {
3373 let path = "/tmp/test_h5py_default.h5";
3374 if !std::path::Path::new(path).exists() {
3375 eprintln!("skipping: h5py test file not found");
3376 return;
3377 }
3378 let file = H5File::open(path).unwrap();
3379
3380 let ds = file.dataset("data").unwrap();
3381 assert_eq!(ds.shape(), vec![4, 5]);
3382 let data = ds.read_raw::<f64>().unwrap();
3383 assert_eq!(data.len(), 20);
3384 assert!((data[0]).abs() < 1e-10);
3385 assert!((data[19] - 19.0).abs() < 1e-10);
3386
3387 let ds2 = file.dataset("images").unwrap();
3388 assert_eq!(ds2.shape(), vec![3, 64, 64]);
3389 let images = ds2.read_raw::<u16>().unwrap();
3390 assert_eq!(images.len(), 3 * 64 * 64);
3391 }
3392
3393 #[test]
3398 fn open_rw_refuses_every_create_only_option() {
3399 let path = temp_path("open_rw_refuses");
3400 H5File::create(&path).unwrap().close().unwrap();
3401
3402 let err = H5File::options()
3403 .track_order(true)
3404 .open_rw(&path)
3405 .err()
3406 .unwrap();
3407 assert!(err.to_string().contains("track_order"), "{err}");
3408
3409 let err = H5File::options()
3410 .libver(LibverBound::V110)
3411 .open_rw(&path)
3412 .err()
3413 .unwrap();
3414 assert!(err.to_string().contains("libver"), "{err}");
3415
3416 for bound in [
3421 LibverBound::Earliest,
3422 LibverBound::V18,
3423 LibverBound::V112,
3424 LibverBound::V114,
3425 LibverBound::V200,
3426 ] {
3427 let err = H5File::options()
3428 .libver(bound)
3429 .open_rw(&path)
3430 .err()
3431 .unwrap();
3432 assert!(err.to_string().contains("libver"), "{bound:?}: {err}");
3433 }
3434
3435 let err = H5File::options()
3436 .userblock(512)
3437 .open_rw(&path)
3438 .err()
3439 .unwrap();
3440 assert!(err.to_string().contains("userblock"), "{err}");
3441
3442 let types = crate::format::sohm::type_flag(crate::format::messages::MSG_DATATYPE).unwrap();
3443 let err = H5File::options()
3444 .shared_messages(&[(types, 0)], 50, 40)
3445 .open_rw(&path)
3446 .err()
3447 .unwrap();
3448 assert!(err.to_string().contains("shared_messages"), "{err}");
3449
3450 H5File::options().open_rw(&path).unwrap().close().unwrap();
3452
3453 std::fs::remove_file(&path).ok();
3454 }
3455
3456 #[test]
3459 fn open_refuses_a_create_only_option() {
3460 let path = temp_path("open_refuses");
3461 H5File::create(&path).unwrap().close().unwrap();
3462
3463 let err = H5File::options()
3464 .track_order(true)
3465 .open(&path)
3466 .err()
3467 .unwrap();
3468 assert!(err.to_string().contains("track_order"), "{err}");
3469
3470 H5File::options().open(&path).unwrap().close().unwrap();
3471
3472 std::fs::remove_file(&path).ok();
3473 }
3474}