1use std::borrow::Cow;
8
9use crate::attribute::AttrBuilder;
10use crate::error::{Hdf5Error, Result};
11use crate::file::{
12 borrow_inner, borrow_inner_mut, clone_inner, same_inner, H5FileInner, SharedInner,
13};
14use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
15use crate::format::messages::filter::Filter;
16use crate::format::messages::virtual_mapping::VirtualMapping;
17use crate::format::reference::{Reference, ReferenceTarget};
18use crate::format::selection::check_hyperslab;
19use crate::format::selection::Selection;
20use crate::format::storage_kind::AttributeStorage;
21use crate::io::file_handle::ReadDst;
22use crate::io::reader::{read_image_into_new, ExternalFileSegment};
23use crate::io::writer::ChunkIndexKind;
24use crate::types::H5Type;
25
26pub struct DatasetBuilder<T: H5Type> {
43 file_inner: SharedInner,
44 shape: Option<Vec<usize>>,
45 is_null: bool,
46 chunk_dims: Option<Vec<usize>>,
47 max_shape: Option<Vec<Option<usize>>>,
48 is_compact: bool,
49 early_allocation: bool,
50 deflate_level: Option<u32>,
51 shuffle: bool,
52 custom_pipeline: Option<crate::format::messages::filter::FilterPipeline>,
53 group_path: Option<String>,
54 fill_value: Option<Vec<u8>>,
55 fill_time: Option<FillTime>,
56 datatype_override: Option<crate::format::messages::datatype::DatatypeMessage>,
57 committed_type: Option<String>,
58 references: Option<ReferenceElement>,
59 external: Option<Vec<(String, u64, u64)>>,
60 efile_prefix: Option<String>,
61 virtual_mappings: Vec<VirtualMapping>,
62 _marker: std::marker::PhantomData<T>,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76enum ReferenceElement {
77 Object,
79 Region,
81 Revised,
86}
87
88impl ReferenceElement {
89 fn datatype(
91 self,
92 ctx: &crate::format::FormatContext,
93 ) -> crate::format::messages::datatype::DatatypeMessage {
94 use crate::format::messages::datatype::DatatypeMessage;
95 match self {
96 Self::Object => DatatypeMessage::object_reference(ctx),
97 Self::Region => DatatypeMessage::region_reference(ctx),
98 Self::Revised => DatatypeMessage::std_object_reference(ctx),
99 }
100 }
101}
102
103impl<T: H5Type> DatasetBuilder<T> {
104 pub(crate) fn new(file_inner: SharedInner) -> Self {
105 Self {
106 file_inner,
107 shape: None,
108 is_null: false,
109 chunk_dims: None,
110 max_shape: None,
111 is_compact: false,
112 early_allocation: false,
113 deflate_level: None,
114 shuffle: false,
115 custom_pipeline: None,
116 group_path: None,
117 fill_value: None,
118 fill_time: None,
119 datatype_override: None,
120 committed_type: None,
121 references: None,
122 external: None,
123 efile_prefix: None,
124 virtual_mappings: Vec::new(),
125 _marker: std::marker::PhantomData,
126 }
127 }
128
129 pub(crate) fn new_in_group(file_inner: SharedInner, group_path: String) -> Self {
130 Self {
131 file_inner,
132 shape: None,
133 is_null: false,
134 chunk_dims: None,
135 max_shape: None,
136 is_compact: false,
137 early_allocation: false,
138 deflate_level: None,
139 shuffle: false,
140 custom_pipeline: None,
141 group_path: Some(group_path),
142 fill_value: None,
143 fill_time: None,
144 datatype_override: None,
145 committed_type: None,
146 references: None,
147 external: None,
148 efile_prefix: None,
149 virtual_mappings: Vec::new(),
150 _marker: std::marker::PhantomData,
151 }
152 }
153
154 #[must_use]
160 pub fn shape<S: AsRef<[usize]>>(mut self, dims: S) -> Self {
161 self.shape = Some(dims.as_ref().to_vec());
162 self
163 }
164
165 #[must_use]
167 pub fn scalar(mut self) -> Self {
168 self.shape = Some(vec![]);
169 self
170 }
171
172 #[must_use]
180 pub fn null(mut self) -> Self {
181 self.is_null = true;
182 self
183 }
184
185 #[must_use]
191 pub fn chunk(mut self, chunk_dims: &[usize]) -> Self {
192 self.chunk_dims = Some(chunk_dims.to_vec());
193 self
194 }
195
196 #[must_use]
200 pub fn resizable(mut self) -> Self {
201 self.max_shape = Some(vec![None; self.shape.as_ref().map_or(0, |s| s.len())]);
202 self
203 }
204
205 #[must_use]
207 pub fn max_shape(mut self, max: &[Option<usize>]) -> Self {
208 self.max_shape = Some(max.to_vec());
209 self
210 }
211
212 #[must_use]
234 pub fn compact(mut self) -> Self {
235 self.is_compact = true;
236 self
237 }
238
239 #[must_use]
267 pub fn early_allocation(mut self) -> Self {
268 self.early_allocation = true;
269 self
270 }
271
272 #[must_use]
277 pub fn deflate(mut self, level: u32) -> Self {
278 self.deflate_level = Some(level);
279 self
280 }
281
282 #[must_use]
297 pub fn shuffle(mut self) -> Self {
298 self.shuffle = true;
299 self
300 }
301
302 #[must_use]
309 pub fn shuffle_deflate(mut self, level: u32) -> Self {
310 self.shuffle = true;
311 self.deflate_level = Some(level);
312 self
313 }
314
315 #[must_use]
319 pub fn zstd(mut self, level: u32) -> Self {
320 self.custom_pipeline = Some(crate::format::messages::filter::FilterPipeline::zstd(level));
321 self
322 }
323
324 #[must_use]
329 pub fn filter_pipeline(
330 mut self,
331 pipeline: crate::format::messages::filter::FilterPipeline,
332 ) -> Self {
333 self.custom_pipeline = Some(pipeline);
334 self
335 }
336
337 #[must_use]
350 pub fn datatype(mut self, dt: crate::format::messages::datatype::DatatypeMessage) -> Self {
351 self.datatype_override = Some(dt);
352 self
353 }
354
355 #[must_use]
380 pub fn committed_type(mut self, path: &str) -> Self {
381 self.committed_type = Some(path.to_string());
382 self
383 }
384
385 #[must_use]
406 pub fn object_references(mut self) -> Self {
407 self.references = Some(ReferenceElement::Object);
408 self
409 }
410
411 #[must_use]
432 pub fn std_object_references(mut self) -> Self {
433 self.references = Some(ReferenceElement::Revised);
434 self
435 }
436
437 #[must_use]
467 pub fn std_region_references(self) -> Self {
468 self.std_object_references()
469 }
470
471 #[must_use]
494 pub fn attribute_references(self) -> Self {
495 self.std_object_references()
496 }
497
498 #[must_use]
524 pub fn region_references(mut self) -> Self {
525 self.references = Some(ReferenceElement::Region);
526 self
527 }
528
529 #[must_use]
563 pub fn external(mut self, files: &[(&str, u64, u64)]) -> Self {
564 self.external = Some(
565 files
566 .iter()
567 .map(|&(name, offset, size)| (name.to_string(), offset, size))
568 .collect(),
569 );
570 self
571 }
572
573 #[must_use]
598 pub fn efile_prefix(mut self, prefix: impl Into<String>) -> Self {
599 self.efile_prefix = Some(prefix.into());
600 self
601 }
602
603 #[must_use]
649 pub fn virtual_mapping(
650 mut self,
651 virtual_selection: Selection,
652 source_file: &str,
653 source_dataset: &str,
654 source_selection: Selection,
655 ) -> Self {
656 self.virtual_mappings.push(VirtualMapping {
657 source_file_name: source_file.to_string(),
658 source_dset_name: source_dataset.to_string(),
659 source_selection,
660 virtual_selection,
661 });
662 self
663 }
664
665 #[must_use]
682 pub fn fill_value(mut self, value: T) -> Self {
683 let es = T::element_size();
684 let raw = unsafe { std::slice::from_raw_parts(&value as *const T as *const u8, es) };
688 self.fill_value = Some(raw.to_vec());
689 self
690 }
691
692 #[must_use]
701 pub fn fill_value_bytes(mut self, bytes: &[u8]) -> Self {
702 self.fill_value = Some(bytes.to_vec());
703 self
704 }
705
706 #[must_use]
728 pub fn fill_time(mut self, time: FillTime) -> Self {
729 self.fill_time = Some(time);
730 self
731 }
732
733 pub fn create(self, name: &str) -> Result<H5Dataset> {
738 let committed = self.resolve_committed_type()?;
744 let file_inner = clone_inner(&self.file_inner);
745 let ds = self.create_object(name, committed.as_ref().map(|(_, dt)| dt.clone()))?;
746 if let (Some((share, _)), DatasetInfo::Writer { index, .. }) = (committed, &ds.info) {
747 let inner = borrow_inner(&file_inner);
748 if let H5FileInner::Writer(writer) = &*inner {
749 writer.share_committed_type(*index, share);
750 }
751 }
752 Ok(ds)
753 }
754
755 fn resolve_committed_type(&self) -> Result<Option<(usize, DatatypeMessage)>> {
759 let Some(path) = self.committed_type.as_deref() else {
760 return Ok(None);
761 };
762 if self.references.is_some() {
763 return Err(Hdf5Error::InvalidState(
769 "a dataset cannot be built on a committed datatype and hold references".into(),
770 ));
771 }
772 let inner = borrow_inner(&self.file_inner);
773 match &*inner {
774 H5FileInner::Writer(writer) => Ok(Some(writer.committed_datatype_for_share(path)?)),
775 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
776 "cannot create a dataset in read mode".into(),
777 )),
778 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
779 }
780 }
781
782 fn create_object(self, name: &str, committed: Option<DatatypeMessage>) -> Result<H5Dataset> {
785 let full_name = if let Some(ref gp) = self.group_path {
787 if gp == "/" {
788 name.to_string()
789 } else {
790 let trimmed = gp.trim_start_matches('/');
791 format!("{}/{}", trimmed, name)
792 }
793 } else {
794 name.to_string()
795 };
796
797 let datatype = if let Some(kind) = self.references {
798 let inner = borrow_inner(&self.file_inner);
801 match &*inner {
802 H5FileInner::Writer(writer) => kind.datatype(writer.ctx()),
803 H5FileInner::Reader(_) => {
804 return Err(Hdf5Error::InvalidState(
805 "cannot create a dataset in read mode".into(),
806 ))
807 }
808 H5FileInner::Closed => {
809 return Err(Hdf5Error::InvalidState("file is closed".into()))
810 }
811 }
812 } else if let Some(dt) = committed {
813 dt
817 } else {
818 self.datatype_override.clone().unwrap_or_else(T::hdf5_type)
819 };
820 let element_size = datatype.element_size() as usize;
827 let fill_value = match self.fill_value.as_deref() {
833 Some(bytes) if bytes.len() != element_size => {
834 return Err(Hdf5Error::TypeMismatch(format!(
835 "fill value is {} bytes but datatype {datatype} stores {element_size}",
836 bytes.len()
837 )))
838 }
839 Some(bytes) => Some(to_stored_byte_order(bytes, &datatype, element_size)?.into_owned()),
840 None => None,
841 };
842
843 let wants_filter =
844 self.custom_pipeline.is_some() || self.shuffle || self.deflate_level.is_some();
845
846 if self.external.is_some() {
851 if self.chunk_dims.is_some() || wants_filter || self.is_compact || self.is_null {
852 return Err(Hdf5Error::InvalidState(
853 "a dataset whose raw data lives in external files is contiguous, so it \
854 cannot also be chunked, filtered, compact or NULL"
855 .into(),
856 ));
857 }
858 if self.references.is_some() {
859 return Err(Hdf5Error::InvalidState(
860 "object and region references are stamped into the dataset's own \
861 contiguous block, which a dataset stored in external files has none of"
862 .into(),
863 ));
864 }
865 }
866
867 if !self.virtual_mappings.is_empty() {
872 if self.chunk_dims.is_some()
873 || wants_filter
874 || self.is_compact
875 || self.is_null
876 || self.external.is_some()
877 {
878 return Err(Hdf5Error::InvalidState(
879 "a virtual dataset's elements live in the datasets its mappings name, \
880 so it cannot also be chunked, filtered, compact, NULL or stored in \
881 external files"
882 .into(),
883 ));
884 }
885 if self.references.is_some() {
886 return Err(Hdf5Error::InvalidState(
887 "object and region references are stamped into the dataset's own \
888 contiguous block, which a virtual dataset has none of"
889 .into(),
890 ));
891 }
892 }
893
894 if self.is_null {
895 if self.chunk_dims.is_some() || wants_filter || self.is_compact {
901 return Err(Hdf5Error::InvalidState(
902 "a NULL dataspace dataset cannot be chunked, filtered or compact".into(),
903 ));
904 }
905 if fill_value.is_some() {
906 return Err(Hdf5Error::InvalidState(
907 "a NULL dataspace dataset cannot have a fill value".into(),
908 ));
909 }
910 if self.fill_time.is_some() {
911 return Err(Hdf5Error::InvalidState(
912 "a NULL dataspace dataset cannot have a fill time".into(),
913 ));
914 }
915
916 let index = {
917 let inner = borrow_inner(&self.file_inner);
918 match &*inner {
919 H5FileInner::Writer(writer) => {
920 let idx = writer.create_null_dataset(&full_name, datatype)?;
921 if let Some(ref gp) = self.group_path {
922 if gp != "/" {
923 writer.assign_dataset_to_group(gp, idx)?;
924 }
925 }
926 idx
927 }
928 H5FileInner::Reader(_) => {
929 return Err(Hdf5Error::InvalidState(
930 "cannot create a dataset in read mode".into(),
931 ));
932 }
933 H5FileInner::Closed => {
934 return Err(Hdf5Error::InvalidState("file is closed".into()));
935 }
936 }
937 };
938
939 return Ok(H5Dataset {
940 file_inner: clone_inner(&self.file_inner),
941 info: DatasetInfo::Writer {
942 index,
943 shape: Vec::new(),
944 element_size,
945 chunk_index: None,
946 is_null: true,
947 },
948 _open: None,
949 });
950 }
951
952 let shape = self.shape.ok_or_else(|| {
953 Hdf5Error::InvalidState("shape must be set before calling create()".into())
954 })?;
955 let dims_u64: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
956
957 if self.is_compact {
958 if self.chunk_dims.is_some() || wants_filter {
963 return Err(Hdf5Error::InvalidState(
964 "a compact dataset stores its data in the object header, so it \
965 cannot be chunked or filtered"
966 .into(),
967 ));
968 }
969 if self
970 .max_shape
971 .as_ref()
972 .is_some_and(|max| max.iter().zip(&shape).any(|(m, &d)| *m != Some(d)))
973 {
974 return Err(Hdf5Error::InvalidState(
975 "a compact dataset cannot be extendible: its maximum shape must \
976 equal its shape"
977 .into(),
978 ));
979 }
980
981 let index = {
982 let inner = borrow_inner(&self.file_inner);
983 match &*inner {
984 H5FileInner::Writer(writer) => {
985 let idx = writer.create_compact_dataset(&full_name, datatype, &dims_u64)?;
986 if let Some(time) = self.fill_time {
989 writer.set_dataset_fill_time(idx, time.wire_byte())?;
990 }
991 if let Some(ref fv) = fill_value {
992 writer.set_dataset_fill_value(idx, fv.clone())?;
993 }
994 idx
995 }
996 H5FileInner::Reader(_) => {
997 return Err(Hdf5Error::InvalidState(
998 "cannot create a dataset in read mode".into(),
999 ));
1000 }
1001 H5FileInner::Closed => {
1002 return Err(Hdf5Error::InvalidState("file is closed".into()));
1003 }
1004 }
1005 };
1006
1007 return Ok(H5Dataset {
1008 file_inner: clone_inner(&self.file_inner),
1009 info: DatasetInfo::Writer {
1010 index,
1011 shape,
1012 element_size,
1013 chunk_index: None,
1014 is_null: false,
1015 },
1016 _open: None,
1017 });
1018 }
1019
1020 if !self.virtual_mappings.is_empty() {
1021 let index = {
1022 let inner = borrow_inner(&self.file_inner);
1023 match &*inner {
1024 H5FileInner::Writer(writer) => {
1025 let idx = writer.create_virtual_dataset(
1026 &full_name,
1027 datatype,
1028 &dims_u64,
1029 self.max_shape
1030 .as_ref()
1031 .map(|max| {
1032 max.iter()
1033 .map(|m| m.map_or(u64::MAX, |v| v as u64))
1034 .collect::<Vec<u64>>()
1035 })
1036 .as_deref(),
1037 &self.virtual_mappings,
1038 )?;
1039 if let Some(time) = self.fill_time {
1047 writer.set_dataset_fill_time(idx, time.wire_byte())?;
1048 }
1049 if let Some(ref fv) = fill_value {
1050 writer.set_dataset_fill_value(idx, fv.clone())?;
1051 }
1052 idx
1053 }
1054 H5FileInner::Reader(_) => {
1055 return Err(Hdf5Error::InvalidState(
1056 "cannot create a dataset in read mode".into(),
1057 ));
1058 }
1059 H5FileInner::Closed => {
1060 return Err(Hdf5Error::InvalidState("file is closed".into()));
1061 }
1062 }
1063 };
1064
1065 return Ok(H5Dataset {
1066 file_inner: clone_inner(&self.file_inner),
1067 info: DatasetInfo::Writer {
1068 index,
1069 shape,
1070 element_size,
1071 chunk_index: None,
1072 is_null: false,
1073 },
1074 _open: None,
1075 });
1076 }
1077
1078 let auto_chunk: Option<Vec<usize>> =
1085 if self.chunk_dims.is_none() && wants_filter && !shape.is_empty() {
1086 Some(shape.iter().map(|&d| d.max(1)).collect())
1087 } else {
1088 None
1089 };
1090
1091 if let Some(chunk_dims) = self.chunk_dims.as_ref().or(auto_chunk.as_ref()) {
1092 let chunk_u64: Vec<u64> = chunk_dims.iter().map(|&d| d as u64).collect();
1094 let max_u64: Vec<u64> = if let Some(ref max) = self.max_shape {
1095 max.iter()
1096 .map(|m| m.map_or(u64::MAX, |v| v as u64))
1097 .collect()
1098 } else {
1099 dims_u64.clone()
1101 };
1102
1103 let chunk_bytes = chunk_u64.iter().product::<u64>() * element_size as u64;
1113 let v110_indexing = match &*borrow_inner(&self.file_inner) {
1114 H5FileInner::Writer(writer) => writer.uses_v110_chunk_indexing(chunk_bytes),
1115 _ => true,
1118 };
1119
1120 let n_unlimited = max_u64.iter().filter(|&&m| m == u64::MAX).count();
1130 let one_chunk = chunk_u64 == dims_u64 && max_u64 == dims_u64;
1131 let kind = if !v110_indexing {
1132 ChunkIndexKind::BtreeV1
1133 } else if n_unlimited >= 2 {
1134 ChunkIndexKind::BtreeV2
1135 } else if n_unlimited == 1 {
1136 ChunkIndexKind::ExtensibleArray
1137 } else if one_chunk {
1138 ChunkIndexKind::SingleChunk
1139 } else if self.early_allocation && !wants_filter {
1140 ChunkIndexKind::Implicit
1141 } else {
1142 ChunkIndexKind::FixedArray
1143 };
1144
1145 let index = {
1146 let inner = borrow_inner(&self.file_inner);
1147 match &*inner {
1148 H5FileInner::Writer(writer) => {
1149 let explicit_pipeline = || {
1155 use crate::format::messages::filter::FilterPipeline;
1156 if let Some(p) = self.custom_pipeline.clone() {
1157 return p;
1158 }
1159 let es = element_size as u32;
1163 match (self.shuffle, self.deflate_level) {
1164 (true, Some(level)) => FilterPipeline::shuffle_deflate(es, level),
1165 (true, None) => FilterPipeline::shuffle(es),
1166 (false, level) => FilterPipeline::deflate(level.unwrap()),
1168 }
1169 };
1170 let idx = if kind == ChunkIndexKind::BtreeV1 {
1171 let pipeline = wants_filter.then(explicit_pipeline);
1176 writer.create_btree_v1_dataset(
1177 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64, pipeline,
1178 )?
1179 } else if kind == ChunkIndexKind::BtreeV2 {
1180 if wants_filter {
1185 writer.create_btree_v2_dataset_with_pipeline(
1186 &full_name,
1187 datatype,
1188 &dims_u64,
1189 &max_u64,
1190 &chunk_u64,
1191 explicit_pipeline(),
1192 )?
1193 } else {
1194 writer.create_btree_v2_dataset(
1195 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64,
1196 )?
1197 }
1198 } else if kind == ChunkIndexKind::Implicit {
1199 writer.create_implicit_dataset(
1205 &full_name, datatype, &dims_u64, &chunk_u64,
1206 )?
1207 } else if kind == ChunkIndexKind::SingleChunk {
1208 if wants_filter {
1217 writer.create_single_chunk_dataset_with_pipeline(
1218 &full_name,
1219 datatype,
1220 &dims_u64,
1221 &chunk_u64,
1222 explicit_pipeline(),
1223 )?
1224 } else {
1225 writer.create_single_chunk_dataset(
1226 &full_name,
1227 datatype,
1228 &dims_u64,
1229 &chunk_u64,
1230 self.early_allocation,
1231 )?
1232 }
1233 } else if kind == ChunkIndexKind::FixedArray {
1234 let pipeline = wants_filter.then(explicit_pipeline);
1242 writer.create_fixed_array_dataset_with_max(
1243 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64, pipeline,
1244 )?
1245 } else if wants_filter {
1246 writer.create_chunked_dataset_with_pipeline(
1250 &full_name,
1251 datatype,
1252 &dims_u64,
1253 &max_u64,
1254 &chunk_u64,
1255 explicit_pipeline(),
1256 )?
1257 } else {
1258 writer.create_chunked_dataset(
1259 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64,
1260 )?
1261 };
1262 if let Some(time) = self.fill_time {
1265 writer.set_dataset_fill_time(idx, time.wire_byte())?;
1266 }
1267 if let Some(ref fv) = fill_value {
1268 writer.set_dataset_fill_value(idx, fv.clone())?;
1269 }
1270 idx
1271 }
1272 H5FileInner::Reader(_) => {
1273 return Err(Hdf5Error::InvalidState(
1274 "cannot create a dataset in read mode".into(),
1275 ));
1276 }
1277 H5FileInner::Closed => {
1278 return Err(Hdf5Error::InvalidState("file is closed".into()));
1279 }
1280 }
1281 };
1282
1283 Ok(H5Dataset {
1284 file_inner: clone_inner(&self.file_inner),
1285 info: DatasetInfo::Writer {
1286 index,
1287 shape,
1288 element_size,
1289 chunk_index: Some(kind),
1290 is_null: false,
1291 },
1292 _open: None,
1293 })
1294 } else {
1295 let efile_access = match self.efile_prefix.as_deref() {
1297 Some(p) => DatasetAccess::new().efile_prefix(p),
1298 None => DatasetAccess::new(),
1299 };
1300 let (index, open) = {
1301 let inner = borrow_inner(&self.file_inner);
1302 match &*inner {
1303 H5FileInner::Writer(writer) => {
1304 let idx = match self.external.as_deref() {
1305 Some(files) => {
1306 let slots: Vec<(&str, u64, u64)> = files
1307 .iter()
1308 .map(|(name, offset, size)| (name.as_str(), *offset, *size))
1309 .collect();
1310 writer.create_external_dataset(
1311 &full_name,
1312 datatype,
1313 &dims_u64,
1314 self.max_shape
1315 .as_ref()
1316 .map(|max| {
1317 max.iter()
1318 .map(|m| m.map_or(u64::MAX, |v| v as u64))
1319 .collect::<Vec<u64>>()
1320 })
1321 .as_deref(),
1322 &slots,
1323 )?
1324 }
1325 None => writer.create_dataset(&full_name, datatype, &dims_u64)?,
1326 };
1327 let open = writer.bind_efile_prefix(idx, &efile_access)?;
1333 if let Some(time) = self.fill_time {
1336 writer.set_dataset_fill_time(idx, time.wire_byte())?;
1337 }
1338 if let Some(ref fv) = fill_value {
1339 writer.set_dataset_fill_value(idx, fv.clone())?;
1340 }
1341 (idx, open)
1342 }
1343 H5FileInner::Reader(_) => {
1344 return Err(Hdf5Error::InvalidState(
1345 "cannot create a dataset in read mode".into(),
1346 ));
1347 }
1348 H5FileInner::Closed => {
1349 return Err(Hdf5Error::InvalidState("file is closed".into()));
1350 }
1351 }
1352 };
1353
1354 Ok(H5Dataset {
1355 file_inner: clone_inner(&self.file_inner),
1356 info: DatasetInfo::Writer {
1357 index,
1358 shape,
1359 element_size,
1360 chunk_index: None,
1361 is_null: false,
1362 },
1363 _open: open,
1364 })
1365 }
1366 }
1367}
1368
1369enum DatasetInfo {
1375 Writer {
1377 index: usize,
1379 shape: Vec<usize>,
1381 element_size: usize,
1383 chunk_index: Option<ChunkIndexKind>,
1390 is_null: bool,
1394 },
1395 Reader {
1397 name: String,
1399 shape: Vec<usize>,
1401 element_size: usize,
1403 },
1404}
1405
1406pub struct H5Dataset {
1416 file_inner: SharedInner,
1417 info: DatasetInfo,
1418 _open: Option<crate::io::reader::DatasetOpenToken>,
1428}
1429
1430impl Drop for H5Dataset {
1431 fn drop(&mut self) {
1443 let Some(open) = self._open.take() else {
1444 return;
1445 };
1446 drop(open);
1447 if matches!(self.info, DatasetInfo::Writer { .. }) {
1448 return;
1449 }
1450 let Some(mut inner) = crate::file::try_borrow_inner_mut(&self.file_inner) else {
1451 return;
1452 };
1453 if let crate::file::H5FileInner::Reader(reader) = &mut *inner {
1454 reader.release_closed_virtual_sources();
1455 }
1456 }
1457}
1458
1459#[derive(Clone, Copy)]
1464enum ChunkBytes<'a> {
1465 Unfiltered(&'a [u8]),
1468 Prefiltered { data: &'a [u8], filter_mask: u32 },
1471}
1472
1473pub(crate) const HOST_BYTE_ORDER: ByteOrder = if cfg!(target_endian = "big") {
1475 ByteOrder::BigEndian
1476} else {
1477 ByteOrder::LittleEndian
1478};
1479
1480pub(crate) const FOREIGN_BYTE_ORDER: ByteOrder = match HOST_BYTE_ORDER {
1482 ByteOrder::LittleEndian => ByteOrder::BigEndian,
1483 ByteOrder::BigEndian => ByteOrder::LittleEndian,
1484};
1485
1486#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1488enum ByteOrderAction {
1489 Keep,
1491 SwapElements,
1493 Refuse,
1495}
1496
1497fn byte_order_action(datatype: &DatatypeMessage, width: usize) -> ByteOrderAction {
1506 match datatype.scalar_byte_order() {
1507 Some(order) if order == FOREIGN_BYTE_ORDER && width > 1 => ByteOrderAction::SwapElements,
1508 Some(_) => ByteOrderAction::Keep,
1509 None if datatype.contains_byte_order(FOREIGN_BYTE_ORDER) => ByteOrderAction::Refuse,
1510 None => ByteOrderAction::Keep,
1511 }
1512}
1513
1514#[cfg(feature = "mmap")]
1525pub(crate) fn stored_image_mismatch(
1526 datatype: &DatatypeMessage,
1527 width: usize,
1528) -> Option<&'static str> {
1529 match byte_order_action(datatype, width) {
1530 ByteOrderAction::SwapElements => return Some("they are stored in the foreign byte order"),
1531 ByteOrderAction::Refuse => {
1532 return Some("it is a composite storing members in the foreign byte order")
1533 }
1534 ByteOrderAction::Keep => {}
1535 }
1536 if crate::format::nbit_scaleoffset::datatype_needs_bit_conversion(datatype) {
1537 return Some("the significant bits do not fill the stored element");
1538 }
1539 None
1540}
1541
1542pub(crate) fn to_host_byte_order(
1555 bytes: &mut [u8],
1556 datatype: &DatatypeMessage,
1557 width: usize,
1558) -> Result<()> {
1559 match byte_order_action(datatype, width) {
1560 ByteOrderAction::Keep => {}
1561 ByteOrderAction::SwapElements => {
1562 for elem in bytes.chunks_exact_mut(width) {
1563 elem.reverse();
1564 }
1565 }
1566 ByteOrderAction::Refuse => {
1567 return Err(Hdf5Error::TypeMismatch(format!(
1568 "dataset datatype {datatype} stores {FOREIGN_BYTE_ORDER:?} values, which a \
1569 typed read cannot reinterpret element by element; read_raw_bytes() returns \
1570 the image to decode member by member"
1571 )))
1572 }
1573 }
1574 Ok(())
1575}
1576
1577pub(crate) fn vlen_sequence_images<'a, T: H5Type>(
1586 items: &'a [&'a [T]],
1587) -> Result<Vec<std::borrow::Cow<'a, [u8]>>> {
1588 let base = T::hdf5_type();
1589 items
1590 .iter()
1591 .map(|item| {
1592 let host = unsafe {
1597 std::slice::from_raw_parts(item.as_ptr() as *const u8, std::mem::size_of_val(*item))
1598 };
1599 to_stored_byte_order(host, &base, T::element_size())
1600 })
1601 .collect()
1602}
1603
1604pub(crate) fn to_stored_byte_order<'a>(
1618 bytes: &'a [u8],
1619 datatype: &DatatypeMessage,
1620 width: usize,
1621) -> Result<std::borrow::Cow<'a, [u8]>> {
1622 match byte_order_action(datatype, width) {
1623 ByteOrderAction::Keep => Ok(std::borrow::Cow::Borrowed(bytes)),
1624 ByteOrderAction::SwapElements => {
1625 let mut owned = bytes.to_vec();
1626 for elem in owned.chunks_exact_mut(width) {
1627 elem.reverse();
1628 }
1629 Ok(std::borrow::Cow::Owned(owned))
1630 }
1631 ByteOrderAction::Refuse => Err(Hdf5Error::TypeMismatch(format!(
1632 "dataset datatype {datatype} stores {FOREIGN_BYTE_ORDER:?} values, which a typed \
1633 write cannot lay out element by element; write_raw_bytes() takes the image the \
1634 caller encodes member by member"
1635 ))),
1636 }
1637}
1638
1639fn trim_fixed_string(elem: &[u8], padding: u8, index: usize) -> Result<&[u8]> {
1646 crate::format::messages::datatype::fixed_string_content(elem, padding).ok_or_else(|| {
1647 Hdf5Error::InvalidState(format!(
1648 "string {index} uses padding rule {padding}, which the format reserves"
1649 ))
1650 })
1651}
1652
1653fn decode_string(bytes: &[u8], charset: u8, lossy: bool, index: usize) -> Result<String> {
1658 if lossy {
1659 return Ok(String::from_utf8_lossy(bytes).into_owned());
1660 }
1661 match charset {
1662 0 => match bytes.iter().position(|&b| b >= 0x80) {
1664 None => Ok(String::from_utf8_lossy(bytes).into_owned()),
1665 Some(at) => Err(Hdf5Error::InvalidState(format!(
1666 "string {index} declares the ASCII character set but byte {at} is {:#04x}",
1667 bytes[at]
1668 ))),
1669 },
1670 1 => String::from_utf8(bytes.to_vec()).map_err(|e| {
1671 Hdf5Error::InvalidState(format!(
1672 "string {index} declares UTF-8 but is not valid UTF-8: {e}"
1673 ))
1674 }),
1675 other => Err(Hdf5Error::InvalidState(format!(
1676 "string {index} uses character set {other}, which the format reserves"
1677 ))),
1678 }
1679}
1680
1681#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1688pub enum StorageLayout {
1689 Compact,
1691 Contiguous,
1696 Chunked,
1700 Virtual,
1703}
1704
1705#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1714pub enum ChunkIndex {
1715 BtreeV1,
1718 BtreeV2,
1720 SingleChunk,
1723 Implicit,
1726 FixedArray,
1728 ExtensibleArray,
1730}
1731
1732#[derive(Debug, Clone, PartialEq, Eq)]
1735pub enum FillValue {
1736 Default,
1739 Undefined,
1742 UserDefined(Vec<u8>),
1744}
1745
1746#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1755pub enum FillTime {
1756 Alloc,
1758 Never,
1760 IfSet,
1764}
1765
1766impl FillTime {
1767 fn wire_byte(self) -> u8 {
1770 match self {
1771 Self::Alloc => 0,
1772 Self::Never => 1,
1773 Self::IfSet => 2,
1774 }
1775 }
1776}
1777
1778#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1790pub enum AllocTime {
1791 Early,
1793 Late,
1795 Incr,
1798}
1799
1800#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1807pub enum VirtualView {
1808 #[default]
1812 LastAvailable,
1813 FirstMissing,
1822}
1823
1824#[derive(Debug, Clone, PartialEq, Eq, Default)]
1849pub struct DatasetAccess {
1850 view: VirtualView,
1851 printf_gap: u64,
1852 virtual_prefix: Option<String>,
1853 efile_prefix: Option<String>,
1854}
1855
1856impl DatasetAccess {
1857 pub fn new() -> Self {
1862 Self::default()
1863 }
1864
1865 pub fn virtual_view(mut self, view: VirtualView) -> Self {
1869 self.view = view;
1870 self
1871 }
1872
1873 pub fn virtual_printf_gap(mut self, gap: u64) -> Self {
1882 self.printf_gap = gap;
1883 self
1884 }
1885
1886 pub fn view(&self) -> VirtualView {
1888 self.view
1889 }
1890
1891 pub fn virtual_prefix(mut self, prefix: impl Into<String>) -> Self {
1910 self.virtual_prefix = Some(prefix.into());
1911 self
1912 }
1913
1914 pub fn efile_prefix(mut self, prefix: impl Into<String>) -> Self {
1944 self.efile_prefix = Some(prefix.into());
1945 self
1946 }
1947
1948 pub fn printf_gap(&self) -> u64 {
1952 self.printf_gap
1953 }
1954
1955 pub fn virtual_prefix_value(&self) -> Option<&str> {
1960 self.virtual_prefix.as_deref()
1961 }
1962
1963 pub fn efile_prefix_value(&self) -> Option<&str> {
1968 self.efile_prefix.as_deref()
1969 }
1970
1971 pub(crate) fn effective_printf_gap(&self) -> u64 {
1979 match self.view {
1980 VirtualView::LastAvailable => self.printf_gap,
1981 VirtualView::FirstMissing => 0,
1982 }
1983 }
1984
1985 pub(crate) fn validate(&self) -> Result<()> {
1988 if self.printf_gap == u64::MAX {
1989 return Err(Hdf5Error::InvalidState(
1990 "virtual_printf_gap(u64::MAX) is libhdf5's HSIZE_UNDEF, which \
1991 H5Pset_virtual_printf_gap refuses as \"not a valid printf gap size\""
1992 .into(),
1993 ));
1994 }
1995 Ok(())
1996 }
1997}
1998
1999fn element_count(counts: &[u64]) -> Result<usize> {
2002 counts
2003 .iter()
2004 .try_fold(1usize, |acc, &c| {
2005 usize::try_from(c).ok().and_then(|c| acc.checked_mul(c))
2006 })
2007 .ok_or_else(|| {
2008 Hdf5Error::InvalidState(format!("selection {counts:?} has more elements than usize"))
2009 })
2010}
2011
2012impl H5Dataset {
2013 pub(crate) fn new_reader(
2015 file_inner: SharedInner,
2016 name: String,
2017 shape: Vec<usize>,
2018 element_size: usize,
2019 open: Option<crate::io::reader::DatasetOpenToken>,
2020 ) -> Self {
2021 Self {
2022 file_inner,
2023 info: DatasetInfo::Reader {
2024 name,
2025 shape,
2026 element_size,
2027 },
2028 _open: open,
2029 }
2030 }
2031
2032 pub(crate) fn new_writer(
2043 file_inner: SharedInner,
2044 index: usize,
2045 parts: crate::io::writer::DatasetHandleParts,
2046 ) -> Self {
2047 Self {
2048 file_inner,
2049 info: DatasetInfo::Writer {
2050 index,
2051 shape: parts.shape,
2052 element_size: parts.element_size,
2053 chunk_index: parts.chunk_index,
2054 is_null: false,
2055 },
2056 _open: parts.open,
2057 }
2058 }
2059
2060 pub fn shape(&self) -> Vec<usize> {
2062 match &self.info {
2063 DatasetInfo::Writer { shape, .. } => shape.clone(),
2064 DatasetInfo::Reader { shape, .. } => shape.clone(),
2065 }
2066 }
2067
2068 pub fn ndims(&self) -> usize {
2070 match &self.info {
2071 DatasetInfo::Writer { shape, .. } => shape.len(),
2072 DatasetInfo::Reader { shape, .. } => shape.len(),
2073 }
2074 }
2075
2076 pub fn total_elements(&self) -> usize {
2082 if self.is_null() {
2083 return 0;
2084 }
2085 match &self.info {
2086 DatasetInfo::Writer { shape, .. } => shape.iter().product(),
2087 DatasetInfo::Reader { shape, .. } => shape.iter().product(),
2088 }
2089 }
2090
2091 pub fn element_size(&self) -> usize {
2093 match &self.info {
2094 DatasetInfo::Writer { element_size, .. } => *element_size,
2095 DatasetInfo::Reader { element_size, .. } => *element_size,
2096 }
2097 }
2098
2099 pub fn is_null(&self) -> bool {
2104 match &self.info {
2105 DatasetInfo::Writer { is_null, .. } => *is_null,
2106 DatasetInfo::Reader { name, .. } => {
2107 let mut inner = borrow_inner_mut(&self.file_inner);
2108 match &mut *inner {
2109 H5FileInner::Reader(reader) => reader
2110 .dataset_info(name)
2111 .map(|info| info.dataspace.is_null())
2112 .unwrap_or(false),
2113 _ => false,
2114 }
2115 }
2116 }
2117 }
2118
2119 pub fn datatype(&self) -> Result<DatatypeMessage> {
2150 match &self.info {
2151 DatasetInfo::Reader { name, .. } => {
2152 let mut inner = borrow_inner_mut(&self.file_inner);
2153 match &mut *inner {
2154 H5FileInner::Reader(reader) => reader
2155 .dataset_info(name)
2156 .map(|info| info.datatype.clone())
2157 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2158 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2159 }
2160 }
2161 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2162 "datatype() is only available in read mode".into(),
2163 )),
2164 }
2165 }
2166
2167 pub fn chunk_dims(&self) -> Option<Vec<usize>> {
2169 match &self.info {
2170 DatasetInfo::Reader { name, .. } => {
2171 let mut inner = borrow_inner_mut(&self.file_inner);
2172 if let H5FileInner::Reader(reader) = &mut *inner {
2173 if let Some(info) = reader.dataset_info(name) {
2174 use crate::format::messages::data_layout::DataLayoutMessage;
2175 let chunk_dims = match &info.layout {
2176 DataLayoutMessage::ChunkedV4 { chunk_dims, .. }
2177 | DataLayoutMessage::ChunkedV3 { chunk_dims, .. } => Some(chunk_dims),
2178 _ => None,
2179 };
2180 if let Some(chunk_dims) = chunk_dims {
2181 return Some(
2183 chunk_dims[..chunk_dims.len() - 1]
2184 .iter()
2185 .map(|&d| d as usize)
2186 .collect(),
2187 );
2188 }
2189 }
2190 }
2191 None
2192 }
2193 DatasetInfo::Writer { .. } => None,
2194 }
2195 }
2196
2197 pub fn is_chunked(&self) -> bool {
2199 match &self.info {
2200 DatasetInfo::Writer { chunk_index, .. } => chunk_index.is_some(),
2201 DatasetInfo::Reader { name, .. } => {
2202 let mut inner = borrow_inner_mut(&self.file_inner);
2203 match &mut *inner {
2204 H5FileInner::Reader(reader) => {
2205 if let Some(info) = reader.dataset_info(name) {
2206 use crate::format::messages::data_layout::DataLayoutMessage;
2207 matches!(
2208 info.layout,
2209 DataLayoutMessage::ChunkedV4 { .. }
2210 | DataLayoutMessage::ChunkedV3 { .. }
2211 )
2212 } else {
2213 false
2214 }
2215 }
2216 _ => false,
2217 }
2218 }
2219 }
2220 }
2221
2222 pub fn storage_layout(&self) -> Result<StorageLayout> {
2229 match &self.info {
2230 DatasetInfo::Reader { name, .. } => {
2231 let mut inner = borrow_inner_mut(&self.file_inner);
2232 match &mut *inner {
2233 H5FileInner::Reader(reader) => {
2234 use crate::format::messages::data_layout::DataLayoutMessage;
2235 reader
2236 .dataset_info(name)
2237 .map(|info| match &info.layout {
2238 DataLayoutMessage::Compact { .. } => StorageLayout::Compact,
2239 DataLayoutMessage::Contiguous { .. } => StorageLayout::Contiguous,
2240 DataLayoutMessage::ChunkedV3 { .. }
2241 | DataLayoutMessage::ChunkedV4 { .. } => StorageLayout::Chunked,
2242 DataLayoutMessage::Virtual { .. } => StorageLayout::Virtual,
2243 })
2244 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))
2245 }
2246 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2247 }
2248 }
2249 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2250 "storage_layout() is only available in read mode".into(),
2251 )),
2252 }
2253 }
2254
2255 pub fn chunk_index(&self) -> Result<Option<ChunkIndex>> {
2263 match &self.info {
2264 DatasetInfo::Reader { name, .. } => {
2265 let mut inner = borrow_inner_mut(&self.file_inner);
2266 match &mut *inner {
2267 H5FileInner::Reader(reader) => {
2268 use crate::format::messages::data_layout::{
2269 ChunkIndexType, DataLayoutMessage,
2270 };
2271 reader
2272 .dataset_info(name)
2273 .map(|info| match &info.layout {
2274 DataLayoutMessage::ChunkedV3 { .. } => Some(ChunkIndex::BtreeV1),
2275 DataLayoutMessage::ChunkedV4 { index_type, .. } => {
2276 Some(match index_type {
2277 ChunkIndexType::SingleChunk => ChunkIndex::SingleChunk,
2278 ChunkIndexType::Implicit => ChunkIndex::Implicit,
2279 ChunkIndexType::FixedArray => ChunkIndex::FixedArray,
2280 ChunkIndexType::ExtensibleArray => {
2281 ChunkIndex::ExtensibleArray
2282 }
2283 ChunkIndexType::BTreeV2 => ChunkIndex::BtreeV2,
2284 })
2285 }
2286 _ => None,
2287 })
2288 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))
2289 }
2290 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2291 }
2292 }
2293 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2294 "chunk_index() is only available in read mode".into(),
2295 )),
2296 }
2297 }
2298
2299 pub fn filters(&self) -> Result<Vec<Filter>> {
2308 match &self.info {
2309 DatasetInfo::Reader { name, .. } => {
2310 let mut inner = borrow_inner_mut(&self.file_inner);
2311 match &mut *inner {
2312 H5FileInner::Reader(reader) => reader
2313 .dataset_info(name)
2314 .map(|info| {
2315 info.filter_pipeline
2316 .as_ref()
2317 .map(|fp| fp.filters.clone())
2318 .unwrap_or_default()
2319 })
2320 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2321 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2322 }
2323 }
2324 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2325 "filters() is only available in read mode".into(),
2326 )),
2327 }
2328 }
2329
2330 pub fn fill_value(&self) -> Result<FillValue> {
2337 match &self.info {
2338 DatasetInfo::Reader { name, .. } => {
2339 let mut inner = borrow_inner_mut(&self.file_inner);
2340 match &mut *inner {
2341 H5FileInner::Reader(reader) => reader
2342 .dataset_info(name)
2343 .map(|info| match info.fill_defined {
2344 0 => FillValue::Undefined,
2345 2 => {
2346 FillValue::UserDefined(info.fill_value.clone().unwrap_or_default())
2347 }
2348 _ => FillValue::Default,
2349 })
2350 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2351 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2352 }
2353 }
2354 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2355 "fill_value() is only available in read mode".into(),
2356 )),
2357 }
2358 }
2359
2360 pub fn fill_time(&self) -> Result<FillTime> {
2368 match &self.info {
2369 DatasetInfo::Reader { name, .. } => {
2370 let mut inner = borrow_inner_mut(&self.file_inner);
2371 match &mut *inner {
2372 H5FileInner::Reader(reader) => reader
2373 .dataset_info(name)
2374 .map(|info| match info.fill_write_time {
2375 0 => FillTime::Alloc,
2376 1 => FillTime::Never,
2377 _ => FillTime::IfSet,
2378 })
2379 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2380 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2381 }
2382 }
2383 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2384 "fill_time() is only available in read mode".into(),
2385 )),
2386 }
2387 }
2388
2389 pub fn alloc_time(&self) -> Result<AllocTime> {
2397 match &self.info {
2398 DatasetInfo::Reader { name, .. } => {
2399 let mut inner = borrow_inner_mut(&self.file_inner);
2400 match &mut *inner {
2401 H5FileInner::Reader(reader) => reader
2402 .dataset_info(name)
2403 .map(|info| match info.alloc_time {
2404 1 => AllocTime::Early,
2405 3 => AllocTime::Incr,
2406 _ => AllocTime::Late,
2407 })
2408 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2409 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2410 }
2411 }
2412 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2413 "alloc_time() is only available in read mode".into(),
2414 )),
2415 }
2416 }
2417
2418 pub fn external_files(&self) -> Result<Vec<ExternalFileSegment>> {
2427 match &self.info {
2428 DatasetInfo::Reader { name, .. } => {
2429 let mut inner = borrow_inner_mut(&self.file_inner);
2430 match &mut *inner {
2431 H5FileInner::Reader(reader) => reader
2432 .dataset_info(name)
2433 .map(|info| info.external_files.clone())
2434 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2435 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2436 }
2437 }
2438 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2439 "external_files() is only available in read mode".into(),
2440 )),
2441 }
2442 }
2443
2444 pub fn max_shape(&self) -> Result<Vec<Option<usize>>> {
2454 match &self.info {
2455 DatasetInfo::Reader { name, .. } => {
2456 let mut inner = borrow_inner_mut(&self.file_inner);
2457 match &mut *inner {
2458 H5FileInner::Reader(reader) => reader
2459 .dataset_info(name)
2460 .map(|info| match &info.dataspace.max_dims {
2461 Some(max_dims) => max_dims
2462 .iter()
2463 .map(|&d| (d != u64::MAX).then_some(d as usize))
2464 .collect(),
2465 None => info
2466 .dataspace
2467 .dims
2468 .iter()
2469 .map(|&d| Some(d as usize))
2470 .collect(),
2471 })
2472 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2473 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2474 }
2475 }
2476 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2477 "max_shape() is only available in read mode".into(),
2478 )),
2479 }
2480 }
2481
2482 pub fn virtual_mappings(&self) -> Result<Vec<VirtualMapping>> {
2491 match &self.info {
2492 DatasetInfo::Reader { name, .. } => {
2493 let mut inner = borrow_inner_mut(&self.file_inner);
2494 match &mut *inner {
2495 H5FileInner::Reader(reader) => reader
2496 .dataset_info(name)
2497 .map(|info| {
2498 info.virtual_mappings
2499 .as_ref()
2500 .map(|vml| vml.mappings.clone())
2501 .unwrap_or_default()
2502 })
2503 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2504 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2505 }
2506 }
2507 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2508 "virtual_mappings() is only available in read mode".into(),
2509 )),
2510 }
2511 }
2512
2513 pub fn attr_names(&self) -> Result<Vec<String>> {
2515 match &self.info {
2516 DatasetInfo::Reader { name, .. } => {
2517 let mut inner = borrow_inner_mut(&self.file_inner);
2518 match &mut *inner {
2519 H5FileInner::Reader(reader) => Ok(reader.dataset_attr_names(name)?),
2520 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2521 }
2522 }
2523 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2524 "attr_names not available in write mode".into(),
2525 )),
2526 }
2527 }
2528
2529 pub fn attr_unreadable_reason(&self, attr_name: &str) -> Result<Option<String>> {
2537 match &self.info {
2538 DatasetInfo::Reader { name, .. } => {
2539 let mut inner = borrow_inner_mut(&self.file_inner);
2540 match &mut *inner {
2541 H5FileInner::Reader(reader) => Ok(reader
2542 .dataset_attr_unreadable_reason(name, attr_name)
2543 .map(str::to_string)),
2544 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2545 }
2546 }
2547 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2548 "attr_unreadable_reason not available in write mode".into(),
2549 )),
2550 }
2551 }
2552
2553 pub fn attrs_unreadable_reason(&self) -> Result<Option<String>> {
2563 match &self.info {
2564 DatasetInfo::Reader { name, .. } => {
2565 let mut inner = borrow_inner_mut(&self.file_inner);
2566 match &mut *inner {
2567 H5FileInner::Reader(reader) => Ok(reader
2568 .dataset_attrs_unreadable_reason(name)
2569 .map(str::to_string)),
2570 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2571 }
2572 }
2573 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2574 "attrs_unreadable_reason not available in write mode".into(),
2575 )),
2576 }
2577 }
2578
2579 pub fn attr_storage(&self) -> Result<AttributeStorage> {
2583 match &self.info {
2584 DatasetInfo::Reader { name, .. } => {
2585 let mut inner = borrow_inner_mut(&self.file_inner);
2586 match &mut *inner {
2587 H5FileInner::Reader(reader) => Ok(reader.dataset_attr_storage(name)?),
2588 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2589 }
2590 }
2591 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2592 "attr_storage not available in write mode".into(),
2593 )),
2594 }
2595 }
2596
2597 pub fn header_attr_count(&self) -> Result<u64> {
2600 match &self.info {
2601 DatasetInfo::Reader { name, .. } => {
2602 let mut inner = borrow_inner_mut(&self.file_inner);
2603 match &mut *inner {
2604 H5FileInner::Reader(reader) => Ok(reader.dataset_header_attr_count(name)?),
2605 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2606 }
2607 }
2608 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2609 "header_attr_count not available in write mode".into(),
2610 )),
2611 }
2612 }
2613
2614 pub fn attr(&self, attr_name: &str) -> Result<crate::attribute::H5Attribute> {
2616 match &self.info {
2617 DatasetInfo::Reader { name, .. } => {
2618 let mut inner = borrow_inner_mut(&self.file_inner);
2619 match &mut *inner {
2620 H5FileInner::Reader(reader) => {
2621 let attr_msg = reader.dataset_attr(name, attr_name)?.clone();
2622 Ok(crate::attribute::H5Attribute::new_reader(
2623 clone_inner(&self.file_inner),
2624 attr_msg,
2625 ))
2626 }
2627 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2628 }
2629 }
2630 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2631 "attr() not available in write mode".into(),
2632 )),
2633 }
2634 }
2635
2636 pub fn new_attr<T: 'static>(&self) -> AttrBuilder<'_, T> {
2652 let ds_index = match &self.info {
2653 DatasetInfo::Writer { index, .. } => *index,
2654 DatasetInfo::Reader { .. } => {
2655 usize::MAX
2658 }
2659 };
2660 AttrBuilder::new(&self.file_inner, ds_index)
2661 }
2662
2663 pub fn write_raw<T: H5Type>(&self, data: &[T]) -> Result<()> {
2678 match &self.info {
2679 DatasetInfo::Writer {
2680 index,
2681 shape,
2682 element_size,
2683 chunk_index,
2684 is_null,
2685 } => {
2686 if *is_null {
2687 return Err(Hdf5Error::InvalidState(
2688 "cannot write to a NULL dataspace dataset".into(),
2689 ));
2690 }
2691 let total_elements: usize = shape.iter().product();
2692 if data.len() != total_elements {
2693 return Err(Hdf5Error::InvalidState(format!(
2694 "data length {} does not match dataset size {}",
2695 data.len(),
2696 total_elements,
2697 )));
2698 }
2699
2700 if T::element_size() != *element_size {
2702 return Err(Hdf5Error::TypeMismatch(format!(
2703 "write type has element size {} but dataset expects {}",
2704 T::element_size(),
2705 element_size,
2706 )));
2707 }
2708
2709 let byte_len = data.len() * T::element_size();
2713 let host =
2714 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) };
2715 let datatype = {
2716 let inner = borrow_inner(&self.file_inner);
2717 match &*inner {
2718 H5FileInner::Writer(writer) => writer.dataset_datatype(*index),
2719 _ => {
2720 return Err(Hdf5Error::InvalidState(
2721 "file is no longer in write mode".into(),
2722 ))
2723 }
2724 }
2725 };
2726 let stored = to_stored_byte_order(host, &datatype, T::element_size())?;
2727
2728 if let Some(kind) = *chunk_index {
2729 return self.write_full_image_chunked(*index, kind, &stored, *element_size);
2733 }
2734
2735 let inner = borrow_inner(&self.file_inner);
2736 match &*inner {
2737 H5FileInner::Writer(writer) => {
2738 writer.write_dataset_raw(*index, &stored)?;
2739 Ok(())
2740 }
2741 _ => Err(Hdf5Error::InvalidState(
2742 "file is no longer in write mode".into(),
2743 )),
2744 }
2745 }
2746 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
2747 "cannot write to a dataset opened in read mode".into(),
2748 )),
2749 }
2750 }
2751
2752 pub fn write_raw_bytes(&self, bytes: &[u8]) -> Result<()> {
2790 match &self.info {
2791 DatasetInfo::Writer {
2792 index,
2793 shape,
2794 element_size,
2795 chunk_index,
2796 is_null,
2797 } => {
2798 if *is_null {
2799 return Err(Hdf5Error::InvalidState(
2800 "cannot write to a NULL dataspace dataset".into(),
2801 ));
2802 }
2803 let expected: usize = shape.iter().product::<usize>() * *element_size;
2804 if bytes.len() != expected {
2805 return Err(Hdf5Error::InvalidState(format!(
2806 "raw byte length {} does not match dataset size {} \
2807 (product(shape) * element_size {})",
2808 bytes.len(),
2809 expected,
2810 element_size,
2811 )));
2812 }
2813 if let Some(kind) = *chunk_index {
2814 return self.write_full_image_chunked(*index, kind, bytes, *element_size);
2817 }
2818 let inner = borrow_inner(&self.file_inner);
2819 match &*inner {
2820 H5FileInner::Writer(writer) => {
2821 writer.write_dataset_raw(*index, bytes)?;
2822 Ok(())
2823 }
2824 _ => Err(Hdf5Error::InvalidState(
2825 "file is no longer in write mode".into(),
2826 )),
2827 }
2828 }
2829 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
2830 "cannot write to a dataset opened in read mode".into(),
2831 )),
2832 }
2833 }
2834
2835 fn write_full_image_chunked(
2845 &self,
2846 index: usize,
2847 kind: ChunkIndexKind,
2848 bytes: &[u8],
2849 element_size: usize,
2850 ) -> Result<()> {
2851 let inner = borrow_inner(&self.file_inner);
2852 let writer = match &*inner {
2853 H5FileInner::Writer(w) => w,
2854 _ => {
2855 return Err(Hdf5Error::InvalidState(
2856 "file is no longer in write mode".into(),
2857 ))
2858 }
2859 };
2860 let cell = writer.ds(index);
2864 let _op = cell.op.lock();
2865 writer.flush_append_buffer(index)?;
2868 let chunk_dims = writer
2869 .dataset_chunk_dims(index)
2870 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
2871 .to_vec();
2872 let dims = writer.dataset_dims(index).to_vec();
2873 let rank = dims.len();
2874
2875 let mut grid = vec![0u64; rank];
2877 for d in 0..rank {
2878 grid[d] = if chunk_dims[d] > 0 {
2879 dims[d].div_ceil(chunk_dims[d])
2880 } else {
2881 0
2882 };
2883 }
2884 let total_chunks: u64 = grid.iter().product();
2885
2886 let coords_of = |linear: u64| -> Vec<u64> {
2892 let mut rem = linear;
2893 let mut coords = vec![0u64; rank];
2894 for d in (0..rank).rev() {
2895 coords[d] = rem % grid[d];
2896 rem /= grid[d];
2897 }
2898 coords
2899 };
2900
2901 let batched = writer.dataset_is_filtered(index)
2907 && matches!(
2908 kind,
2909 ChunkIndexKind::ExtensibleArray | ChunkIndexKind::FixedArray
2910 );
2911 if !batched {
2912 let mut staging = Vec::new();
2918 for linear in 0..total_chunks {
2919 let coords = coords_of(linear);
2920 let chunk =
2921 match Self::contiguous_chunk_span(&dims, &chunk_dims, &coords, element_size) {
2922 Some(span) => &bytes[span],
2923 None => {
2924 Self::gather_chunk_into(
2925 &mut staging,
2926 bytes,
2927 &dims,
2928 &chunk_dims,
2929 &coords,
2930 element_size,
2931 );
2932 &staging[..]
2933 }
2934 };
2935 writer.write_chunk_at_coords(index, &coords, chunk)?;
2936 }
2937 } else {
2938 const BATCH_WINDOW: u64 = 256;
2949 let mut start = 0u64;
2950 while start < total_chunks {
2951 let end = (start + BATCH_WINDOW).min(total_chunks);
2952 let items: Vec<(Vec<u64>, Cow<'_, [u8]>)> = (start..end)
2953 .map(|counter| {
2954 let coords = coords_of(counter);
2955 let data = match Self::contiguous_chunk_span(
2956 &dims,
2957 &chunk_dims,
2958 &coords,
2959 element_size,
2960 ) {
2961 Some(span) => Cow::Borrowed(&bytes[span]),
2962 None => {
2963 let mut buf = Vec::new();
2964 Self::gather_chunk_into(
2965 &mut buf,
2966 bytes,
2967 &dims,
2968 &chunk_dims,
2969 &coords,
2970 element_size,
2971 );
2972 Cow::Owned(buf)
2973 }
2974 };
2975 (coords, data)
2976 })
2977 .collect();
2978 if kind == ChunkIndexKind::FixedArray {
2979 let pairs: Vec<(&[u64], &[u8])> = items
2980 .iter()
2981 .map(|(c, d)| (c.as_slice(), d.as_ref()))
2982 .collect();
2983 writer.write_chunks_fixed_array_batch_inner(index, &pairs)?;
2984 } else {
2985 let mut pairs: Vec<(u64, &[u8])> = Vec::with_capacity(items.len());
2986 for (c, d) in &items {
2987 pairs.push((writer.chunk_slot(index, c)?, d.as_ref()));
2988 }
2989 writer.write_chunks_batch_inner(index, &pairs)?;
2990 }
2991 start = end;
2992 }
2993 }
2994 Ok(())
2995 }
2996
2997 fn contiguous_chunk_span(
3009 dims: &[u64],
3010 chunk_dims: &[u64],
3011 coords: &[u64],
3012 element_size: usize,
3013 ) -> Option<std::ops::Range<usize>> {
3014 let rank = dims.len();
3015 if rank == 0 || chunk_dims[1..] != dims[1..] {
3016 return None;
3017 }
3018 if (coords[0] + 1) * chunk_dims[0] > dims[0] {
3019 return None;
3020 }
3021 let plane: u64 = dims[1..].iter().product::<u64>() * element_size as u64;
3022 let start = usize::try_from(coords[0] * chunk_dims[0] * plane).ok()?;
3023 let len = usize::try_from(chunk_dims[0] * plane).ok()?;
3024 Some(start..start.checked_add(len)?)
3025 }
3026
3027 fn gather_chunk_into(
3035 out: &mut Vec<u8>,
3036 source: &[u8],
3037 dims: &[u64],
3038 chunk_dims: &[u64],
3039 coords: &[u64],
3040 element_size: usize,
3041 ) {
3042 let rank = dims.len();
3043 let chunk_elems: u64 = chunk_dims.iter().product();
3044 let chunk_bytes = chunk_elems as usize * element_size;
3045 if rank == 0 {
3046 out.clear();
3048 out.resize(chunk_bytes, 0);
3049 if source.len() >= element_size {
3050 out[..element_size].copy_from_slice(&source[..element_size]);
3051 }
3052 return;
3053 }
3054
3055 let mut extent = vec![0u64; rank];
3058 for d in 0..rank {
3059 let start = coords[d] * chunk_dims[d];
3060 let end = ((coords[d] + 1) * chunk_dims[d]).min(dims[d]);
3061 extent[d] = end.saturating_sub(start);
3062 }
3063 if out.len() != chunk_bytes {
3068 out.clear();
3069 out.resize(chunk_bytes, 0);
3070 } else if extent != chunk_dims {
3071 out.fill(0);
3072 }
3073 if extent.contains(&0) {
3074 return; }
3076
3077 let mut src_stride = vec![1u64; rank];
3080 let mut dst_stride = vec![1u64; rank];
3081 for d in (0..rank - 1).rev() {
3082 src_stride[d] = src_stride[d + 1] * dims[d + 1];
3083 dst_stride[d] = dst_stride[d + 1] * chunk_dims[d + 1];
3084 }
3085
3086 let last = rank - 1;
3088 let run = extent[last] as usize * element_size;
3089 let outer: u64 = extent[..last].iter().product::<u64>().max(1);
3090 let mut idx = vec![0u64; rank]; for _ in 0..outer {
3092 let mut src_off = 0u64;
3093 let mut dst_off = 0u64;
3094 for d in 0..rank {
3095 let global = coords[d] * chunk_dims[d] + idx[d];
3096 src_off += global * src_stride[d];
3097 dst_off += idx[d] * dst_stride[d];
3098 }
3099 let s = src_off as usize * element_size;
3100 let dpos = dst_off as usize * element_size;
3101 out[dpos..dpos + run].copy_from_slice(&source[s..s + run]);
3102
3103 let mut d = last;
3106 while d > 0 {
3107 d -= 1;
3108 idx[d] += 1;
3109 if idx[d] < extent[d] {
3110 break;
3111 }
3112 idx[d] = 0;
3113 }
3114 }
3115 }
3116
3117 pub fn write_chunk(&self, chunk_idx: usize, data: &[u8]) -> Result<()> {
3125 match &self.info {
3126 DatasetInfo::Writer {
3127 index, chunk_index, ..
3128 } => {
3129 let Some(kind) = *chunk_index else {
3130 return Err(Hdf5Error::InvalidState(
3131 "write_chunk is only for chunked datasets".into(),
3132 ));
3133 };
3134 if kind == ChunkIndexKind::BtreeV2 {
3135 return Err(Hdf5Error::InvalidState(
3136 "this dataset uses a v2 B-tree chunk index; use write_chunk_at \
3137 with the chunk's grid coordinates"
3138 .into(),
3139 ));
3140 }
3141
3142 let inner = borrow_inner(&self.file_inner);
3143 match &*inner {
3144 H5FileInner::Writer(writer) => {
3145 let cell = writer.ds(*index);
3148 let _op = cell.op.lock();
3149 match kind {
3150 ChunkIndexKind::FixedArray
3154 | ChunkIndexKind::Implicit
3155 | ChunkIndexKind::SingleChunk
3156 | ChunkIndexKind::BtreeV1 => {
3157 let coords =
3158 writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3159 writer.write_chunk_at_coords(*index, &coords, data)?;
3160 }
3161 _ => writer.write_chunk_inner(*index, chunk_idx as u64, data)?,
3162 }
3163 Ok(())
3164 }
3165 _ => Err(Hdf5Error::InvalidState(
3166 "file is no longer in write mode".into(),
3167 )),
3168 }
3169 }
3170 DatasetInfo::Reader { .. } => {
3171 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3172 }
3173 }
3174 }
3175
3176 pub fn write_chunk_raw(&self, chunk_idx: usize, data: &[u8], filter_mask: u32) -> Result<()> {
3203 match &self.info {
3204 DatasetInfo::Writer {
3205 index, chunk_index, ..
3206 } => {
3207 let Some(kind) = *chunk_index else {
3208 return Err(Hdf5Error::InvalidState(
3209 "write_chunk_raw is only for chunked datasets".into(),
3210 ));
3211 };
3212 if kind == ChunkIndexKind::BtreeV2 {
3213 return Err(Hdf5Error::InvalidState(
3214 "this dataset uses a v2 B-tree chunk index; use \
3215 write_chunk_raw_at with the chunk's grid coordinates"
3216 .into(),
3217 ));
3218 }
3219 if kind == ChunkIndexKind::Implicit {
3220 return Err(Hdf5Error::InvalidState(
3221 "this dataset uses the implicit chunk index, which stores \
3222 every chunk at its full unfiltered size and has nowhere to \
3223 record a stored size or a filter mask"
3224 .into(),
3225 ));
3226 }
3227
3228 let inner = borrow_inner(&self.file_inner);
3229 match &*inner {
3230 H5FileInner::Writer(writer) => {
3231 let cell = writer.ds(*index);
3234 let _op = cell.op.lock();
3235 if kind == ChunkIndexKind::FixedArray {
3236 let coords = writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3239 writer.write_compressed_chunk_fixed_array_inner(
3240 *index,
3241 &coords,
3242 data,
3243 filter_mask,
3244 )?;
3245 } else if kind == ChunkIndexKind::BtreeV1 {
3246 let coords = writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3249 writer.write_compressed_chunk_btree_v1_inner(
3250 *index,
3251 &coords,
3252 data,
3253 filter_mask,
3254 )?;
3255 } else if kind == ChunkIndexKind::SingleChunk {
3256 let coords = writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3260 writer.write_compressed_chunk_single_chunk_inner(
3261 *index,
3262 &coords,
3263 data,
3264 filter_mask,
3265 )?;
3266 } else {
3267 writer.write_compressed_chunk_inner(
3268 *index,
3269 chunk_idx as u64,
3270 data,
3271 filter_mask,
3272 )?;
3273 }
3274 Ok(())
3275 }
3276 _ => Err(Hdf5Error::InvalidState(
3277 "file is no longer in write mode".into(),
3278 )),
3279 }
3280 }
3281 DatasetInfo::Reader { .. } => {
3282 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3283 }
3284 }
3285 }
3286
3287 pub fn write_chunk_at(&self, chunk_coords: &[usize], data: &[u8]) -> Result<()> {
3308 self.write_chunk_at_inner(chunk_coords, ChunkBytes::Unfiltered(data), "write_chunk_at")
3309 }
3310
3311 pub fn write_chunk_raw_at(
3329 &self,
3330 chunk_coords: &[usize],
3331 data: &[u8],
3332 filter_mask: u32,
3333 ) -> Result<()> {
3334 self.write_chunk_at_inner(
3335 chunk_coords,
3336 ChunkBytes::Prefiltered { data, filter_mask },
3337 "write_chunk_raw_at",
3338 )
3339 }
3340
3341 fn write_chunk_at_inner(
3347 &self,
3348 chunk_coords: &[usize],
3349 bytes: ChunkBytes<'_>,
3350 what: &str,
3351 ) -> Result<()> {
3352 match &self.info {
3353 DatasetInfo::Writer {
3354 index, chunk_index, ..
3355 } => {
3356 let Some(kind) = *chunk_index else {
3357 return Err(Hdf5Error::InvalidState(format!(
3358 "{what} is only for chunked datasets"
3359 )));
3360 };
3361 let coords: Vec<u64> = chunk_coords.iter().map(|&c| c as u64).collect();
3362 let inner = borrow_inner(&self.file_inner);
3363 let writer = match &*inner {
3364 H5FileInner::Writer(w) => w,
3365 _ => {
3366 return Err(Hdf5Error::InvalidState(
3367 "file is no longer in write mode".into(),
3368 ))
3369 }
3370 };
3371 let cell = writer.ds(*index);
3375 let _op = cell.op.lock();
3376 let chunk_dims = writer
3377 .dataset_chunk_dims(*index)
3378 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
3379 .to_vec();
3380 let dims = writer.dataset_dims(*index).to_vec();
3381 if coords.len() != dims.len() {
3382 return Err(Hdf5Error::InvalidState(format!(
3383 "chunk_coords has {} entries but the dataset has {} dimensions",
3384 coords.len(),
3385 dims.len()
3386 )));
3387 }
3388 if chunk_dims.len() != dims.len() {
3389 return Err(Hdf5Error::InvalidState(format!(
3390 "dataset chunk shape has {} dimensions but the dataspace has {}",
3391 chunk_dims.len(),
3392 dims.len()
3393 )));
3394 }
3395
3396 if kind == ChunkIndexKind::FixedArray {
3397 match bytes {
3399 ChunkBytes::Unfiltered(data) => {
3400 writer.write_chunk_fixed_array_inner(*index, &coords, data)?
3401 }
3402 ChunkBytes::Prefiltered { data, filter_mask } => writer
3403 .write_compressed_chunk_fixed_array_inner(
3404 *index,
3405 &coords,
3406 data,
3407 filter_mask,
3408 )?,
3409 }
3410 return Ok(());
3411 }
3412
3413 if kind == ChunkIndexKind::Implicit {
3414 match bytes {
3417 ChunkBytes::Unfiltered(data) => {
3418 writer.write_chunk_implicit_inner(*index, &coords, data)?
3419 }
3420 ChunkBytes::Prefiltered { .. } => {
3421 return Err(Hdf5Error::InvalidState(
3422 "this dataset uses the implicit chunk index, which stores \
3423 every chunk at its full unfiltered size and has nowhere \
3424 to record a stored size or a filter mask"
3425 .into(),
3426 ))
3427 }
3428 }
3429 return Ok(());
3430 }
3431
3432 if kind == ChunkIndexKind::SingleChunk {
3433 match bytes {
3436 ChunkBytes::Unfiltered(data) => {
3437 writer.write_chunk_single_chunk_inner(*index, &coords, data)?
3438 }
3439 ChunkBytes::Prefiltered { data, filter_mask } => writer
3440 .write_compressed_chunk_single_chunk_inner(
3441 *index,
3442 &coords,
3443 data,
3444 filter_mask,
3445 )?,
3446 }
3447 return Ok(());
3448 }
3449
3450 let max_dims = writer.dataset_max_dims(*index);
3462 let mut new_dims = dims.clone();
3463 for d in 0..dims.len() {
3464 let needed = coords[d]
3465 .checked_add(1)
3466 .and_then(|c| c.checked_mul(chunk_dims[d]))
3467 .ok_or_else(|| {
3468 Hdf5Error::InvalidState(format!(
3469 "chunk coordinate {} in dimension {} is too large",
3470 coords[d], d
3471 ))
3472 })?;
3473 let needed = needed.min(max_dims[d]);
3474 if needed > new_dims[d] {
3475 new_dims[d] = needed;
3476 }
3477 }
3478
3479 if kind == ChunkIndexKind::BtreeV2 {
3480 match bytes {
3481 ChunkBytes::Unfiltered(data) => {
3482 writer.write_chunk_btree_v2_inner(*index, &coords, data)?
3483 }
3484 ChunkBytes::Prefiltered { data, filter_mask } => writer
3485 .write_compressed_chunk_btree_v2_inner(
3486 *index,
3487 &coords,
3488 data,
3489 filter_mask,
3490 )?,
3491 }
3492 } else if kind == ChunkIndexKind::BtreeV1 {
3493 match bytes {
3497 ChunkBytes::Unfiltered(data) => {
3498 writer.write_chunk_btree_v1_inner(*index, &coords, data)?
3499 }
3500 ChunkBytes::Prefiltered { data, filter_mask } => writer
3501 .write_compressed_chunk_btree_v1_inner(
3502 *index,
3503 &coords,
3504 data,
3505 filter_mask,
3506 )?,
3507 }
3508 } else {
3509 let linear = writer.chunk_slot(*index, &coords)?;
3512 match bytes {
3513 ChunkBytes::Unfiltered(data) => {
3514 writer.write_chunk_inner(*index, linear, data)?
3515 }
3516 ChunkBytes::Prefiltered { data, filter_mask } => writer
3517 .write_compressed_chunk_inner(*index, linear, data, filter_mask)?,
3518 }
3519 }
3520
3521 if new_dims != dims {
3522 writer.extend_dataset_inner(*index, &new_dims)?;
3523 }
3524 Ok(())
3525 }
3526 DatasetInfo::Reader { .. } => {
3527 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3528 }
3529 }
3530 }
3531
3532 pub fn write_chunks_batch(&self, chunks: &[(usize, &[u8])]) -> Result<()> {
3538 match &self.info {
3539 DatasetInfo::Writer {
3540 index, chunk_index, ..
3541 } => {
3542 if chunk_index.is_none() {
3543 return Err(Hdf5Error::InvalidState(
3544 "write_chunks_batch is only for chunked datasets".into(),
3545 ));
3546 }
3547 let pairs: Vec<(u64, &[u8])> = chunks
3548 .iter()
3549 .map(|(idx, data)| (*idx as u64, *data))
3550 .collect();
3551 let inner = borrow_inner(&self.file_inner);
3552 match &*inner {
3553 H5FileInner::Writer(writer) => {
3554 writer.write_chunks_batch(*index, &pairs)?;
3555 Ok(())
3556 }
3557 _ => Err(Hdf5Error::InvalidState(
3558 "file is no longer in write mode".into(),
3559 )),
3560 }
3561 }
3562 DatasetInfo::Reader { .. } => {
3563 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3564 }
3565 }
3566 }
3567
3568 pub fn append<T: H5Type>(&self, data: &[T]) -> Result<()> {
3591 match &self.info {
3592 DatasetInfo::Writer {
3593 index,
3594 element_size,
3595 chunk_index,
3596 ..
3597 } => {
3598 if chunk_index.is_none() {
3599 return Err(Hdf5Error::InvalidState(
3600 "append is only for chunked datasets".into(),
3601 ));
3602 }
3603 if T::element_size() != *element_size {
3604 return Err(Hdf5Error::TypeMismatch(format!(
3605 "append type has element size {} but dataset expects {}",
3606 T::element_size(),
3607 element_size,
3608 )));
3609 }
3610
3611 let ds_index = *index;
3612 let es = *element_size;
3613
3614 let inner = borrow_inner(&self.file_inner);
3615 let writer = match &*inner {
3616 H5FileInner::Writer(w) => w,
3617 _ => {
3618 return Err(Hdf5Error::InvalidState(
3619 "file is no longer in write mode".into(),
3620 ))
3621 }
3622 };
3623
3624 let cell = writer.ds(ds_index);
3629 let _op = cell.op.lock();
3630
3631 let chunk_dims = writer
3632 .dataset_chunk_dims(ds_index)
3633 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
3634 .to_vec();
3635 let dims = writer.dataset_dims(ds_index).to_vec();
3636
3637 let frame_elems: usize = if dims.len() > 1 {
3639 dims[1..].iter().map(|&d| d as usize).product()
3640 } else {
3641 1
3642 };
3643
3644 if frame_elems == 0 {
3645 return Err(Hdf5Error::InvalidState(
3646 "cannot append to dataset with zero-size trailing dimensions".into(),
3647 ));
3648 }
3649
3650 if !data.len().is_multiple_of(frame_elems) {
3651 return Err(Hdf5Error::InvalidState(format!(
3652 "data length {} is not a multiple of frame size {}",
3653 data.len(),
3654 frame_elems,
3655 )));
3656 }
3657
3658 let n_new_frames = data.len() / frame_elems;
3659 let current_dim0 = dims[0] as usize;
3660
3661 let chunk_dim0 = chunk_dims[0] as usize;
3663 let frame_bytes = frame_elems * es;
3664
3665 let host = unsafe {
3666 std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * es)
3667 };
3668 let datatype = writer.dataset_datatype(ds_index);
3669 let raw = to_stored_byte_order(host, &datatype, es)?;
3670
3671 let taken = { writer.ds(ds_index).lock().append.take() };
3676 let (base_dim0, buffered_frames, mut combined) = match taken {
3677 Some(b) if b.base + b.frames == current_dim0 as u64 => {
3678 (b.base as usize, b.frames as usize, b.bytes)
3679 }
3680 Some(b) => {
3681 writer.write_append_frames(ds_index, b.base, b.frames, &b.bytes)?;
3682 (current_dim0, 0, Vec::new())
3683 }
3684 None => (current_dim0, 0, Vec::new()),
3685 };
3686 combined.extend_from_slice(&raw);
3687
3688 let total_frames = buffered_frames + n_new_frames;
3689
3690 let last_boundary = ((base_dim0 + total_frames) / chunk_dim0) * chunk_dim0;
3697 let write_frames = last_boundary.saturating_sub(base_dim0);
3698 let tail_frames = total_frames - write_frames;
3699 if write_frames > 0 {
3700 writer.write_append_frames(
3701 ds_index,
3702 base_dim0 as u64,
3703 write_frames as u64,
3704 &combined[..write_frames * frame_bytes],
3705 )?;
3706 }
3707 if tail_frames > 0 {
3708 let ds = writer.ds(ds_index);
3709 let mut m = ds.lock();
3710 m.append = Some(crate::io::writer::AppendBuffer {
3711 base: (base_dim0 + write_frames) as u64,
3712 frames: tail_frames as u64,
3713 bytes: combined[write_frames * frame_bytes..].to_vec(),
3714 });
3715 }
3716
3717 let logical_dim0 = base_dim0 + total_frames;
3719 let mut new_dims: Vec<u64> = dims;
3720 new_dims[0] = logical_dim0 as u64;
3721 writer.extend_dataset_inner(ds_index, &new_dims)?;
3722
3723 Ok(())
3724 }
3725 DatasetInfo::Reader { .. } => {
3726 Err(Hdf5Error::InvalidState("cannot append in read mode".into()))
3727 }
3728 }
3729 }
3730
3731 pub fn extend(&self, new_dims: &[usize]) -> Result<()> {
3733 match &self.info {
3734 DatasetInfo::Writer {
3735 index, chunk_index, ..
3736 } => {
3737 if chunk_index.is_none() {
3738 return Err(Hdf5Error::InvalidState(
3739 "extend is only for chunked datasets".into(),
3740 ));
3741 }
3742
3743 let dims_u64: Vec<u64> = new_dims.iter().map(|&d| d as u64).collect();
3744 let inner = borrow_inner(&self.file_inner);
3745 match &*inner {
3746 H5FileInner::Writer(writer) => {
3747 writer.extend_dataset(*index, &dims_u64)?;
3748 Ok(())
3749 }
3750 _ => Err(Hdf5Error::InvalidState(
3751 "file is no longer in write mode".into(),
3752 )),
3753 }
3754 }
3755 DatasetInfo::Reader { .. } => {
3756 Err(Hdf5Error::InvalidState("cannot extend in read mode".into()))
3757 }
3758 }
3759 }
3760
3761 pub fn set_extent(&self, new_dims: &[usize]) -> Result<()> {
3775 match &self.info {
3776 DatasetInfo::Writer { index, .. } => {
3777 let dims_u64: Vec<u64> = new_dims.iter().map(|&d| d as u64).collect();
3778 let inner = borrow_inner(&self.file_inner);
3779 match &*inner {
3780 H5FileInner::Writer(writer) => {
3781 writer.set_dataset_extent(*index, &dims_u64)?;
3782 Ok(())
3783 }
3784 _ => Err(Hdf5Error::InvalidState(
3785 "file is no longer in write mode".into(),
3786 )),
3787 }
3788 }
3789 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
3790 "cannot set extent in read mode".into(),
3791 )),
3792 }
3793 }
3794
3795 pub fn flush(&self) -> Result<()> {
3797 match &self.info {
3798 DatasetInfo::Writer { index, .. } => {
3799 let inner = borrow_inner(&self.file_inner);
3800 match &*inner {
3801 H5FileInner::Writer(writer) => {
3802 writer.flush_dataset(*index)?;
3803 Ok(())
3804 }
3805 _ => Ok(()),
3806 }
3807 }
3808 DatasetInfo::Reader { .. } => Ok(()),
3809 }
3810 }
3811
3812 pub fn read_slice<T: H5Type>(&self, starts: &[usize], counts: &[usize]) -> Result<Vec<T>> {
3817 match &self.info {
3818 DatasetInfo::Reader {
3819 name,
3820 shape,
3821 element_size,
3822 } => {
3823 if T::element_size() != *element_size {
3824 return Err(Hdf5Error::TypeMismatch(format!(
3825 "read type has element size {} but dataset has element size {}",
3826 T::element_size(),
3827 element_size,
3828 )));
3829 }
3830 let datatype = self.datatype()?;
3831 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
3832 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
3833
3834 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
3838 check_hyperslab(&dims, &starts_u64, &counts_u64)?;
3839 let count = element_count(&counts_u64)?;
3840 let mut inner = borrow_inner_mut(&self.file_inner);
3841 let H5FileInner::Reader(reader) = &mut *inner else {
3842 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
3843 };
3844 read_image_into_new(count, |image| {
3848 reader.read_slice_into_dst(
3849 name,
3850 &starts_u64,
3851 &counts_u64,
3852 image,
3853 ReadDst::Fresh,
3854 )?;
3855 to_host_byte_order(image, &datatype, T::element_size())
3856 })
3857 }
3858 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
3859 "cannot read_slice from a dataset in write mode".into(),
3860 )),
3861 }
3862 }
3863
3864 pub fn read_hyperslab<T: H5Type>(
3883 &self,
3884 start: &[usize],
3885 stride: &[usize],
3886 count: &[usize],
3887 block: &[usize],
3888 ) -> Result<Vec<T>> {
3889 match &self.info {
3890 DatasetInfo::Reader {
3891 name, element_size, ..
3892 } => {
3893 if T::element_size() != *element_size {
3894 return Err(Hdf5Error::TypeMismatch(format!(
3895 "read type has element size {} but dataset has element size {}",
3896 T::element_size(),
3897 element_size,
3898 )));
3899 }
3900 let datatype = self.datatype()?;
3901 let start_u64: Vec<u64> = start.iter().map(|&s| s as u64).collect();
3902 let stride_u64: Vec<u64> = stride.iter().map(|&s| s as u64).collect();
3903 let count_u64: Vec<u64> = count.iter().map(|&c| c as u64).collect();
3904 let block_u64: Vec<u64> = block.iter().map(|&b| b as u64).collect();
3905
3906 let selected: Vec<u64> = count_u64
3907 .iter()
3908 .zip(&block_u64)
3909 .map(|(&c, &b)| c.saturating_mul(b))
3910 .collect();
3911 let n = element_count(&selected)?;
3912 let mut inner = borrow_inner_mut(&self.file_inner);
3913 let H5FileInner::Reader(reader) = &mut *inner else {
3914 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
3915 };
3916 read_image_into_new(n, |image| {
3917 reader.read_hyperslab_into(
3918 name,
3919 &start_u64,
3920 &stride_u64,
3921 &count_u64,
3922 &block_u64,
3923 image,
3924 )?;
3925 to_host_byte_order(image, &datatype, T::element_size())
3926 })
3927 }
3928 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
3929 "cannot read_hyperslab from a dataset in write mode".into(),
3930 )),
3931 }
3932 }
3933
3934 pub fn read_points<T: H5Type>(&self, points: &[Vec<usize>]) -> Result<Vec<T>> {
3949 match &self.info {
3950 DatasetInfo::Reader {
3951 name, element_size, ..
3952 } => {
3953 if T::element_size() != *element_size {
3954 return Err(Hdf5Error::TypeMismatch(format!(
3955 "read type has element size {} but dataset has element size {}",
3956 T::element_size(),
3957 element_size,
3958 )));
3959 }
3960 let datatype = self.datatype()?;
3961 let points_u64: Vec<Vec<u64>> = points
3962 .iter()
3963 .map(|p| p.iter().map(|&c| c as u64).collect())
3964 .collect();
3965
3966 let mut inner = borrow_inner_mut(&self.file_inner);
3967 let H5FileInner::Reader(reader) = &mut *inner else {
3968 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
3969 };
3970 read_image_into_new(points_u64.len(), |image| {
3971 reader.read_points_into(name, &points_u64, image, ReadDst::Fresh)?;
3972 to_host_byte_order(image, &datatype, T::element_size())
3973 })
3974 }
3975 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
3976 "cannot read_points from a dataset in write mode".into(),
3977 )),
3978 }
3979 }
3980
3981 pub fn read_chunk_raw_at(&self, chunk_coords: &[usize]) -> Result<(Vec<u8>, u32)> {
4003 match &self.info {
4004 DatasetInfo::Reader { name, .. } => {
4005 let coords_u64: Vec<u64> = chunk_coords.iter().map(|&c| c as u64).collect();
4006 let mut inner = borrow_inner_mut(&self.file_inner);
4007 match &mut *inner {
4008 H5FileInner::Reader(reader) => Ok(reader.read_chunk_raw_at(name, &coords_u64)?),
4009 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4010 }
4011 }
4012 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4013 "cannot read_chunk_raw_at from a dataset in write mode".into(),
4014 )),
4015 }
4016 }
4017
4018 pub fn write_slice<T: H5Type>(
4031 &self,
4032 starts: &[usize],
4033 counts: &[usize],
4034 data: &[T],
4035 ) -> Result<()> {
4036 match &self.info {
4037 DatasetInfo::Writer {
4038 index,
4039 element_size,
4040 ..
4041 } => {
4042 if T::element_size() != *element_size {
4043 return Err(Hdf5Error::TypeMismatch(format!(
4044 "write type has element size {} but dataset expects {}",
4045 T::element_size(),
4046 element_size,
4047 )));
4048 }
4049
4050 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
4051 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
4052
4053 let inner = borrow_inner(&self.file_inner);
4054 let H5FileInner::Writer(writer) = &*inner else {
4055 return Err(Hdf5Error::InvalidState(
4056 "file is no longer in write mode".into(),
4057 ));
4058 };
4059
4060 check_hyperslab(&writer.dataset_dims(*index), &starts_u64, &counts_u64)?;
4065 let expected = element_count(&counts_u64)?;
4066 if data.len() != expected {
4067 return Err(Hdf5Error::InvalidState(format!(
4068 "data length {} does not match slice size {}",
4069 data.len(),
4070 expected,
4071 )));
4072 }
4073
4074 let byte_len = data.len() * T::element_size();
4075 let host =
4076 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) };
4077
4078 let datatype = writer.dataset_datatype(*index);
4079 let stored = to_stored_byte_order(host, &datatype, T::element_size())?;
4080 writer.write_slice(*index, &starts_u64, &counts_u64, &stored)?;
4081 Ok(())
4082 }
4083 DatasetInfo::Reader { .. } => {
4084 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4085 }
4086 }
4087 }
4088
4089 pub fn write_vlen_strings_slice(&self, start: usize, strings: &[&str]) -> Result<()> {
4113 match &self.info {
4114 DatasetInfo::Writer { index, .. } => {
4115 let inner = borrow_inner(&self.file_inner);
4116 match &*inner {
4117 H5FileInner::Writer(writer) => {
4118 writer.write_vlen_strings_slice(*index, start as u64, strings)?;
4119 Ok(())
4120 }
4121 _ => Err(Hdf5Error::InvalidState(
4122 "file is no longer in write mode".into(),
4123 )),
4124 }
4125 }
4126 DatasetInfo::Reader { .. } => {
4127 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4128 }
4129 }
4130 }
4131
4132 pub fn read_vlen_strings(&self) -> Result<Vec<String>> {
4137 match &self.info {
4138 DatasetInfo::Reader { name, .. } => {
4139 let mut inner = borrow_inner_mut(&self.file_inner);
4140 match &mut *inner {
4141 H5FileInner::Reader(reader) => Ok(reader.read_vlen_strings(name)?),
4142 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4143 }
4144 }
4145 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4146 "cannot read vlen strings from a dataset in write mode".into(),
4147 )),
4148 }
4149 }
4150
4151 pub fn read_vlen_bytes(&self) -> Result<Vec<Vec<u8>>> {
4158 match &self.info {
4159 DatasetInfo::Reader { name, .. } => {
4160 let mut inner = borrow_inner_mut(&self.file_inner);
4161 match &mut *inner {
4162 H5FileInner::Reader(reader) => Ok(reader.read_vlen_bytes(name)?),
4163 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4164 }
4165 }
4166 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4167 "cannot read vlen bytes from a dataset in write mode".into(),
4168 )),
4169 }
4170 }
4171
4172 pub fn write_object_references(&self, paths: &[&str]) -> Result<()> {
4182 match &self.info {
4183 DatasetInfo::Writer { index, .. } => {
4184 let inner = borrow_inner(&self.file_inner);
4185 match &*inner {
4186 H5FileInner::Writer(writer) => {
4187 writer.write_object_references(*index, 0, paths)?;
4188 Ok(())
4189 }
4190 _ => Err(Hdf5Error::InvalidState(
4191 "file is no longer in write mode".into(),
4192 )),
4193 }
4194 }
4195 DatasetInfo::Reader { .. } => {
4196 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4197 }
4198 }
4199 }
4200
4201 pub fn set_scale(&self, name: Option<&str>) -> Result<()> {
4217 let index = self.writer_index("set_scale")?;
4218 let inner = borrow_inner(&self.file_inner);
4219 match &*inner {
4220 H5FileInner::Writer(writer) => Ok(writer.set_dimension_scale(index, name)?),
4221 _ => Err(Hdf5Error::InvalidState(
4222 "file is no longer in write mode".into(),
4223 )),
4224 }
4225 }
4226
4227 pub fn attach_scale(&self, axis: usize, scale: &H5Dataset) -> Result<()> {
4247 let did = self.writer_index("attach_scale")?;
4248 let dsid = scale.writer_index("attach_scale")?;
4249 if !same_inner(&self.file_inner, &scale.file_inner) {
4250 return Err(Hdf5Error::InvalidState(
4251 "the dimension scale belongs to another file".into(),
4252 ));
4253 }
4254 let inner = borrow_inner(&self.file_inner);
4255 match &*inner {
4256 H5FileInner::Writer(writer) => Ok(writer.attach_dimension_scale(did, dsid, axis)?),
4257 _ => Err(Hdf5Error::InvalidState(
4258 "file is no longer in write mode".into(),
4259 )),
4260 }
4261 }
4262
4263 fn writer_index(&self, what: &str) -> Result<usize> {
4266 match &self.info {
4267 DatasetInfo::Writer { index, .. } => Ok(*index),
4268 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(format!(
4269 "{what} is only available in write mode"
4270 ))),
4271 }
4272 }
4273
4274 pub fn write_region_references(&self, targets: &[(&str, Selection)]) -> Result<()> {
4302 match &self.info {
4303 DatasetInfo::Writer { index, .. } => {
4304 let inner = borrow_inner(&self.file_inner);
4305 match &*inner {
4306 H5FileInner::Writer(writer) => {
4307 writer.write_region_references(*index, 0, targets)?;
4308 Ok(())
4309 }
4310 _ => Err(Hdf5Error::InvalidState(
4311 "file is no longer in write mode".into(),
4312 )),
4313 }
4314 }
4315 DatasetInfo::Reader { .. } => {
4316 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4317 }
4318 }
4319 }
4320
4321 pub fn write_std_region_references(&self, targets: &[(&str, Selection)]) -> Result<()> {
4335 let targets: Vec<(&str, ReferenceTarget)> = targets
4336 .iter()
4337 .map(|(path, selection)| (*path, ReferenceTarget::Region(selection.clone())))
4338 .collect();
4339 self.write_revised_references(&targets)
4340 }
4341
4342 pub fn write_attribute_references(&self, targets: &[(&str, &str)]) -> Result<()> {
4353 let targets: Vec<(&str, ReferenceTarget)> = targets
4354 .iter()
4355 .map(|(path, name)| (*path, ReferenceTarget::Attribute((*name).to_string())))
4356 .collect();
4357 self.write_revised_references(&targets)
4358 }
4359
4360 fn write_revised_references(&self, targets: &[(&str, ReferenceTarget)]) -> Result<()> {
4363 match &self.info {
4364 DatasetInfo::Writer { index, .. } => {
4365 let inner = borrow_inner(&self.file_inner);
4366 match &*inner {
4367 H5FileInner::Writer(writer) => {
4368 writer.write_revised_references(*index, 0, targets)?;
4369 Ok(())
4370 }
4371 _ => Err(Hdf5Error::InvalidState(
4372 "file is no longer in write mode".into(),
4373 )),
4374 }
4375 }
4376 DatasetInfo::Reader { .. } => {
4377 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4378 }
4379 }
4380 }
4381
4382 pub fn read_references(&self) -> Result<Vec<Reference>> {
4409 match &self.info {
4410 DatasetInfo::Reader { name, .. } => {
4411 let mut inner = borrow_inner_mut(&self.file_inner);
4412 match &mut *inner {
4413 H5FileInner::Reader(reader) => Ok(reader.read_references(name)?),
4414 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4415 }
4416 }
4417 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4418 "cannot read references from a dataset in write mode".into(),
4419 )),
4420 }
4421 }
4422
4423 pub fn read_strings(&self) -> Result<Vec<String>> {
4442 self.read_strings_inner(false)
4443 }
4444
4445 pub fn read_strings_lossy(&self) -> Result<Vec<String>> {
4451 self.read_strings_inner(true)
4452 }
4453
4454 fn read_strings_inner(&self, lossy: bool) -> Result<Vec<String>> {
4458 if matches!(self.info, DatasetInfo::Writer { .. }) {
4459 return Err(Hdf5Error::InvalidState(
4460 "cannot read strings from a dataset in write mode".into(),
4461 ));
4462 }
4463 match self.datatype()? {
4464 DatatypeMessage::VarLenString { charset, .. } => self
4465 .read_vlen_bytes()?
4466 .iter()
4467 .enumerate()
4468 .map(|(i, bytes)| decode_string(bytes, charset, lossy, i))
4469 .collect(),
4470 DatatypeMessage::FixedString {
4471 size,
4472 padding,
4473 charset,
4474 } => {
4475 let width = size as usize;
4476 if width == 0 {
4477 return Err(Hdf5Error::InvalidState(
4479 "fixed-string datatype has zero width".into(),
4480 ));
4481 }
4482 let raw = self.read_raw_bytes()?;
4485 raw.chunks_exact(width)
4486 .enumerate()
4487 .map(|(i, elem)| {
4488 decode_string(trim_fixed_string(elem, padding, i)?, charset, lossy, i)
4489 })
4490 .collect()
4491 }
4492 other => Err(Hdf5Error::InvalidState(format!(
4493 "read_strings is only for string datasets, this one is {other:?}"
4494 ))),
4495 }
4496 }
4497
4498 pub fn read_raw<T: H5Type>(&self) -> Result<Vec<T>> {
4510 match &self.info {
4511 DatasetInfo::Reader {
4512 name, element_size, ..
4513 } => {
4514 if T::element_size() != *element_size {
4515 return Err(Hdf5Error::TypeMismatch(format!(
4516 "read type has element size {} but dataset has element size {}",
4517 T::element_size(),
4518 element_size,
4519 )));
4520 }
4521
4522 let datatype = self.datatype()?;
4523 let mut inner = borrow_inner_mut(&self.file_inner);
4524 let H5FileInner::Reader(reader) = &mut *inner else {
4525 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
4526 };
4527 let total = reader.dataset_raw_size(name)? as usize;
4528 if !total.is_multiple_of(T::element_size()) {
4529 return Err(Hdf5Error::TypeMismatch(format!(
4530 "raw data size {total} is not a multiple of element size {}",
4531 T::element_size(),
4532 )));
4533 }
4534
4535 read_image_into_new(total / T::element_size(), |image| {
4539 reader.read_dataset_raw_into_dst(name, image, ReadDst::Fresh)?;
4540 to_host_byte_order(image, &datatype, T::element_size())
4541 })
4542 }
4543 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4544 "cannot read from a dataset in write mode".into(),
4545 )),
4546 }
4547 }
4548
4549 #[cfg(feature = "mmap")]
4612 pub fn read_mapped<T: H5Type>(&self) -> Result<crate::mapped::MappedView<T>> {
4613 self.mapped_view(crate::mapped::ViewRange::Whole)
4614 }
4615
4616 #[cfg(feature = "mmap")]
4633 pub fn read_mapped_slice<T: H5Type>(
4634 &self,
4635 starts: &[usize],
4636 counts: &[usize],
4637 ) -> Result<crate::mapped::MappedView<T>> {
4638 self.mapped_view(crate::mapped::ViewRange::Slab { starts, counts })
4639 }
4640
4641 #[cfg(feature = "mmap")]
4646 fn mapped_view<T: H5Type>(
4647 &self,
4648 range: crate::mapped::ViewRange<'_>,
4649 ) -> Result<crate::mapped::MappedView<T>> {
4650 let DatasetInfo::Reader { name, .. } = &self.info else {
4651 return Err(Hdf5Error::InvalidState(
4652 "cannot read from a dataset in write mode".into(),
4653 ));
4654 };
4655 let mut inner = borrow_inner_mut(&self.file_inner);
4656 let H5FileInner::Reader(reader) = &mut *inner else {
4657 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
4658 };
4659 let src = reader.dataset_view_source(name)?;
4660 crate::mapped::view::<T>(&src, range).map_err(Hdf5Error::NotViewable)
4661 }
4662
4663 pub fn read_raw_bytes(&self) -> Result<Vec<u8>> {
4670 match &self.info {
4671 DatasetInfo::Reader { name, .. } => {
4672 let mut inner = borrow_inner_mut(&self.file_inner);
4673 match &mut *inner {
4674 H5FileInner::Reader(reader) => Ok(reader.read_dataset_raw(name)?),
4675 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4676 }
4677 }
4678 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4679 "cannot read from a dataset in write mode".into(),
4680 )),
4681 }
4682 }
4683
4684 pub fn read_numeric_as<T: ReadNumeric>(&self) -> Result<Vec<T>> {
4708 match &self.info {
4709 DatasetInfo::Reader { name, .. } => {
4710 let (kind, raw) = {
4711 let mut inner = borrow_inner_mut(&self.file_inner);
4712 match &mut *inner {
4713 H5FileInner::Reader(reader) => {
4714 let info = reader
4715 .dataset_info(name)
4716 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))?;
4717 let kind = numeric::classify(&info.datatype)?;
4718 (kind, reader.read_dataset_raw(name)?)
4719 }
4720 _ => {
4721 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4722 }
4723 }
4724 };
4725 numeric::convert(kind, &raw)
4726 }
4727 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4728 "cannot read from a dataset in write mode".into(),
4729 )),
4730 }
4731 }
4732
4733 pub fn read_numeric_slice_as<T: ReadNumeric>(
4740 &self,
4741 starts: &[usize],
4742 counts: &[usize],
4743 ) -> Result<Vec<T>> {
4744 match &self.info {
4745 DatasetInfo::Reader { name, .. } => {
4746 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
4747 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
4748 let (kind, raw) = {
4749 let mut inner = borrow_inner_mut(&self.file_inner);
4750 match &mut *inner {
4751 H5FileInner::Reader(reader) => {
4752 let info = reader
4753 .dataset_info(name)
4754 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))?;
4755 let kind = numeric::classify(&info.datatype)?;
4756 (kind, reader.read_slice(name, &starts_u64, &counts_u64)?)
4757 }
4758 _ => {
4759 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4760 }
4761 }
4762 };
4763 numeric::convert(kind, &raw)
4764 }
4765 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4766 "cannot read_slice from a dataset in write mode".into(),
4767 )),
4768 }
4769 }
4770
4771 pub fn read_raw_into<T: H5Type>(&self, out: &mut [T]) -> Result<()> {
4793 match &self.info {
4794 DatasetInfo::Reader {
4795 name, element_size, ..
4796 } => {
4797 if T::element_size() != *element_size {
4798 return Err(Hdf5Error::TypeMismatch(format!(
4799 "read type has element size {} but dataset has element size {}",
4800 T::element_size(),
4801 element_size,
4802 )));
4803 }
4804 let datatype = self.datatype()?;
4805 let byte_len = out.len() * T::element_size();
4811 let bytes = unsafe {
4812 std::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, byte_len)
4813 };
4814 {
4815 let mut inner = borrow_inner_mut(&self.file_inner);
4816 match &mut *inner {
4817 H5FileInner::Reader(reader) => reader.read_dataset_raw_into(name, bytes)?,
4818 _ => {
4819 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4820 }
4821 }
4822 }
4823 to_host_byte_order(bytes, &datatype, T::element_size())
4824 }
4825 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4826 "cannot read from a dataset in write mode".into(),
4827 )),
4828 }
4829 }
4830
4831 pub fn read_slice_into<T: H5Type>(
4850 &self,
4851 out: &mut [T],
4852 starts: &[usize],
4853 counts: &[usize],
4854 ) -> Result<()> {
4855 match &self.info {
4856 DatasetInfo::Reader {
4857 name, element_size, ..
4858 } => {
4859 if T::element_size() != *element_size {
4860 return Err(Hdf5Error::TypeMismatch(format!(
4861 "read type has element size {} but dataset has element size {}",
4862 T::element_size(),
4863 element_size,
4864 )));
4865 }
4866 let datatype = self.datatype()?;
4867 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
4868 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
4869 let byte_len = out.len() * T::element_size();
4872 let bytes = unsafe {
4873 std::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, byte_len)
4874 };
4875 {
4876 let mut inner = borrow_inner_mut(&self.file_inner);
4877 match &mut *inner {
4878 H5FileInner::Reader(reader) => {
4879 reader.read_slice_into(name, &starts_u64, &counts_u64, bytes)?
4880 }
4881 _ => {
4882 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4883 }
4884 }
4885 }
4886 to_host_byte_order(bytes, &datatype, T::element_size())
4887 }
4888 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4889 "cannot read from a dataset in write mode".into(),
4890 )),
4891 }
4892 }
4893}
4894
4895pub trait ReadNumeric: numeric::Sealed {}
4906impl<T: numeric::Sealed> ReadNumeric for T {}
4907
4908pub(crate) mod numeric {
4909 use crate::error::{Hdf5Error, Result};
4913 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage, IeeeFormat};
4914
4915 #[derive(Clone, Copy)]
4917 pub enum SourceKind {
4918 Int {
4919 size: usize,
4920 signed: bool,
4921 byte_order: ByteOrder,
4922 },
4923 F16(ByteOrder),
4924 F32(ByteOrder),
4925 F64(ByteOrder),
4926 }
4927
4928 impl SourceKind {
4929 fn element_size(self) -> usize {
4930 match self {
4931 SourceKind::Int { size, .. } => size,
4932 SourceKind::F16(_) => 2,
4933 SourceKind::F32(_) => 4,
4934 SourceKind::F64(_) => 8,
4935 }
4936 }
4937 }
4938
4939 fn f16_bits_to_f32(bits: u16) -> f32 {
4944 let sign = u32::from(bits >> 15);
4945 let exponent = u32::from((bits >> 10) & 0x1f);
4946 let mantissa = u32::from(bits & 0x03ff);
4947 if exponent == 0 {
4948 let magnitude = mantissa as f32 * (1.0 / 16_777_216.0);
4952 return if sign == 1 { -magnitude } else { magnitude };
4953 }
4954 let out = if exponent == 0x1f {
4955 (sign << 31) | 0x7f80_0000 | (mantissa << 13)
4958 } else {
4959 (sign << 31) | ((exponent + 112) << 23) | (mantissa << 13)
4962 };
4963 f32::from_bits(out)
4964 }
4965
4966 pub fn classify(dt: &DatatypeMessage) -> Result<SourceKind> {
4972 match *dt {
4973 DatatypeMessage::FixedPoint {
4974 size,
4975 byte_order,
4976 signed,
4977 bit_offset,
4978 bit_precision,
4979 } => {
4980 if !matches!(size, 1 | 2 | 4 | 8)
4981 || bit_offset != 0
4982 || u32::from(bit_precision) != size * 8
4983 {
4984 return Err(Hdf5Error::TypeMismatch(format!(
4985 "fixed-point datatype (size {size}, bit offset {bit_offset}, \
4986 precision {bit_precision}) is not a standard-width integer",
4987 )));
4988 }
4989 Ok(SourceKind::Int {
4990 size: size as usize,
4991 signed,
4992 byte_order,
4993 })
4994 }
4995 DatatypeMessage::BitField {
4996 size,
4997 byte_order,
4998 bit_offset,
4999 bit_precision,
5000 } => {
5001 if !matches!(size, 1 | 2 | 4 | 8)
5005 || bit_offset != 0
5006 || u32::from(bit_precision) != size * 8
5007 {
5008 return Err(Hdf5Error::TypeMismatch(format!(
5009 "bit-field datatype (size {size}, bit offset {bit_offset}, \
5010 precision {bit_precision}) is not a whole-width bit field",
5011 )));
5012 }
5013 Ok(SourceKind::Int {
5014 size: size as usize,
5015 signed: false,
5016 byte_order,
5017 })
5018 }
5019 DatatypeMessage::FloatingPoint {
5020 size,
5021 byte_order,
5022 exponent_size,
5023 mantissa_size,
5024 ..
5025 } => match dt.ieee_format() {
5026 Some(IeeeFormat::Binary16) => Ok(SourceKind::F16(byte_order)),
5027 Some(IeeeFormat::Binary32) => Ok(SourceKind::F32(byte_order)),
5028 Some(IeeeFormat::Binary64) => Ok(SourceKind::F64(byte_order)),
5029 None => Err(Hdf5Error::TypeMismatch(format!(
5030 "floating-point datatype (size {size}, exponent {exponent_size} bits, \
5031 mantissa {mantissa_size} bits) is not an IEEE 754 interchange format",
5032 ))),
5033 },
5034 ref other => Err(Hdf5Error::TypeMismatch(format!(
5035 "dataset datatype '{other}' is not numeric",
5036 ))),
5037 }
5038 }
5039
5040 pub fn convert<T: Sealed>(kind: SourceKind, raw: &[u8]) -> Result<Vec<T>> {
5048 let size = kind.element_size();
5049 if !raw.len().is_multiple_of(size) {
5050 return Err(Hdf5Error::TypeMismatch(format!(
5051 "raw data size {} is not a multiple of element size {size}",
5052 raw.len(),
5053 )));
5054 }
5055 let mut out = vec![T::default(); raw.len() / size];
5056 match kind {
5057 SourceKind::Int {
5058 size,
5059 signed,
5060 byte_order,
5061 } => {
5062 let le = byte_order == ByteOrder::LittleEndian;
5063 match (size, signed) {
5064 (1, true) => ints::<1, i64, T>(raw, le, &mut out),
5065 (1, false) => ints::<1, u64, T>(raw, le, &mut out),
5066 (2, true) => ints::<2, i64, T>(raw, le, &mut out),
5067 (2, false) => ints::<2, u64, T>(raw, le, &mut out),
5068 (4, true) => ints::<4, i64, T>(raw, le, &mut out),
5069 (4, false) => ints::<4, u64, T>(raw, le, &mut out),
5070 (8, true) => ints::<8, i64, T>(raw, le, &mut out),
5071 (8, false) => ints::<8, u64, T>(raw, le, &mut out),
5072 _ => unreachable!("classify admits 1-, 2-, 4- and 8-byte integers only"),
5073 }
5074 }
5075 SourceKind::F16(order) => floats::<2, T>(raw, order, &mut out, |b| {
5076 T::from_f32(f16_bits_to_f32(u16::from_le_bytes(b)))
5077 }),
5078 SourceKind::F32(order) => {
5079 floats::<4, T>(raw, order, &mut out, |b| T::from_f32(f32::from_le_bytes(b)))
5080 }
5081 SourceKind::F64(order) => {
5082 floats::<8, T>(raw, order, &mut out, |b| T::from_f64(f64::from_le_bytes(b)))
5083 }
5084 }?;
5085 Ok(out)
5086 }
5087
5088 fn ints<const N: usize, L: IntLane, T: Sealed>(
5090 raw: &[u8],
5091 le: bool,
5092 out: &mut [T],
5093 ) -> Result<()> {
5094 let (chunks, _) = raw.as_chunks::<N>();
5095 for (index, (o, &chunk)) in out.iter_mut().zip(chunks).enumerate() {
5096 let mut bytes = chunk;
5097 if !le {
5098 bytes.reverse();
5099 }
5100 *o = L::load(bytes).into_target(index)?;
5101 }
5102 Ok(())
5103 }
5104
5105 fn floats<const N: usize, T: Sealed>(
5108 raw: &[u8],
5109 order: ByteOrder,
5110 out: &mut [T],
5111 decode: impl Fn([u8; N]) -> Result<T>,
5112 ) -> Result<()> {
5113 let (chunks, _) = raw.as_chunks::<N>();
5114 for (o, &chunk) in out.iter_mut().zip(chunks) {
5115 let mut bytes = chunk;
5116 if order == ByteOrder::BigEndian {
5117 bytes.reverse();
5118 }
5119 *o = decode(bytes)?;
5120 }
5121 Ok(())
5122 }
5123
5124 pub trait IntLane: Copy {
5128 fn load<const N: usize>(bytes: [u8; N]) -> Self;
5130 fn into_target<T: Sealed>(self, index: usize) -> Result<T>;
5131 }
5132
5133 impl IntLane for u64 {
5134 fn load<const N: usize>(bytes: [u8; N]) -> Self {
5135 let mut padded = [0u8; 8];
5136 padded[..N].copy_from_slice(&bytes);
5137 u64::from_le_bytes(padded)
5138 }
5139 fn into_target<T: Sealed>(self, index: usize) -> Result<T> {
5140 T::from_u64(self, index)
5141 }
5142 }
5143
5144 impl IntLane for i64 {
5145 fn load<const N: usize>(bytes: [u8; N]) -> Self {
5146 let shift = 64 - 8 * N as u32;
5148 ((u64::load(bytes) << shift) as i64) >> shift
5149 }
5150 fn into_target<T: Sealed>(self, index: usize) -> Result<T> {
5151 T::from_i64(self, index)
5152 }
5153 }
5154
5155 pub trait Sealed: Copy + Default {
5159 fn from_i64(v: i64, index: usize) -> Result<Self>;
5160 fn from_u64(v: u64, index: usize) -> Result<Self>;
5161 fn from_f32(v: f32) -> Result<Self>;
5162 fn from_f64(v: f64) -> Result<Self>;
5163 }
5164
5165 macro_rules! int_targets {
5166 ($($t:ty),* $(,)?) => {$(
5167 impl Sealed for $t {
5168 fn from_i64(v: i64, index: usize) -> Result<Self> {
5169 <$t>::try_from(v).map_err(|_| int_overflow::<$t>(v, index))
5170 }
5171 fn from_u64(v: u64, index: usize) -> Result<Self> {
5172 <$t>::try_from(v).map_err(|_| int_overflow::<$t>(v, index))
5173 }
5174 fn from_f32(_: f32) -> Result<Self> {
5175 Err(float_as_int::<$t>())
5176 }
5177 fn from_f64(_: f64) -> Result<Self> {
5178 Err(float_as_int::<$t>())
5179 }
5180 }
5181 )*};
5182 }
5183 int_targets!(i8, i16, i32, i64, u8, u16, u32, u64);
5184
5185 impl Sealed for i128 {
5188 fn from_i64(v: i64, _index: usize) -> Result<Self> {
5189 Ok(Self::from(v))
5190 }
5191 fn from_u64(v: u64, _index: usize) -> Result<Self> {
5192 Ok(Self::from(v))
5193 }
5194 fn from_f32(_: f32) -> Result<Self> {
5195 Err(float_as_int::<i128>())
5196 }
5197 fn from_f64(_: f64) -> Result<Self> {
5198 Err(float_as_int::<i128>())
5199 }
5200 }
5201
5202 impl Sealed for u128 {
5203 fn from_i64(v: i64, index: usize) -> Result<Self> {
5204 Self::try_from(v).map_err(|_| int_overflow::<u128>(v, index))
5205 }
5206 fn from_u64(v: u64, _index: usize) -> Result<Self> {
5207 Ok(Self::from(v))
5208 }
5209 fn from_f32(_: f32) -> Result<Self> {
5210 Err(float_as_int::<u128>())
5211 }
5212 fn from_f64(_: f64) -> Result<Self> {
5213 Err(float_as_int::<u128>())
5214 }
5215 }
5216
5217 fn int_overflow<T>(v: impl std::fmt::Display, index: usize) -> Hdf5Error {
5218 Hdf5Error::TypeMismatch(format!(
5219 "value {v} at element {index} does not fit in {}",
5220 std::any::type_name::<T>()
5221 ))
5222 }
5223
5224 fn float_as_int<T>() -> Hdf5Error {
5225 Hdf5Error::TypeMismatch(format!(
5226 "cannot read a floating-point dataset as {}; read as f64 and convert explicitly",
5227 std::any::type_name::<T>()
5228 ))
5229 }
5230
5231 impl Sealed for f32 {
5232 fn from_i64(_: i64, _index: usize) -> Result<Self> {
5233 Err(int_as_f32())
5234 }
5235 fn from_u64(_: u64, _index: usize) -> Result<Self> {
5236 Err(int_as_f32())
5237 }
5238 fn from_f32(v: f32) -> Result<Self> {
5239 Ok(v)
5240 }
5241 fn from_f64(_: f64) -> Result<Self> {
5242 Err(Hdf5Error::TypeMismatch(
5243 "narrowing an f64 dataset to f32 loses precision; read as f64".into(),
5244 ))
5245 }
5246 }
5247
5248 fn int_as_f32() -> Hdf5Error {
5249 Hdf5Error::TypeMismatch(
5250 "cannot read an integer dataset as f32; read as an integer type and convert \
5251 explicitly"
5252 .into(),
5253 )
5254 }
5255
5256 impl Sealed for f64 {
5257 fn from_i64(_: i64, _index: usize) -> Result<Self> {
5258 Err(int_as_f64())
5259 }
5260 fn from_u64(_: u64, _index: usize) -> Result<Self> {
5261 Err(int_as_f64())
5262 }
5263 fn from_f32(v: f32) -> Result<Self> {
5265 Ok(f64::from(v))
5266 }
5267 fn from_f64(v: f64) -> Result<Self> {
5268 Ok(v)
5269 }
5270 }
5271
5272 fn int_as_f64() -> Hdf5Error {
5273 Hdf5Error::TypeMismatch(
5274 "cannot read an integer dataset as f64; integers above 2^53 lose precision — \
5275 read as an integer type and convert explicitly"
5276 .into(),
5277 )
5278 }
5279
5280 #[cfg(test)]
5281 mod tests {
5282 use super::*;
5283
5284 #[test]
5288 fn f16_widening_is_exact() {
5289 let cases: [(u16, f32); 10] = [
5290 (0x0000, 0.0),
5291 (0x3c00, 1.0),
5292 (0xc000, -2.0),
5293 (0x3555, 0.333_251_95), (0x0001, 5.960_464_5e-8), (0x03ff, 6.097_555e-5), (0x0400, 6.103_515_6e-5), (0x7bff, 65504.0), (0x7c00, f32::INFINITY),
5299 (0xfc00, f32::NEG_INFINITY),
5300 ];
5301 for (bits, expected) in cases {
5302 let got = f16_bits_to_f32(bits);
5303 assert_eq!(got, expected, "0x{bits:04x} widened to {got}");
5304 }
5305
5306 let neg_zero = f16_bits_to_f32(0x8000);
5307 assert_eq!(neg_zero, 0.0);
5308 assert!(neg_zero.is_sign_negative(), "-0.0 lost its sign");
5309
5310 let nan = f16_bits_to_f32(0x7e01);
5311 assert!(nan.is_nan());
5312 assert_eq!(nan.to_bits(), 0x7fc0_2000);
5314 }
5315
5316 #[test]
5317 fn f16_source_converts_and_honors_byte_order() {
5318 let kind = classify(&DatatypeMessage::f16_type()).unwrap();
5319 let raw = [0x00, 0x3c, 0x00, 0xc0, 0x55, 0x35, 0xff, 0x7b];
5321 assert_eq!(
5322 convert::<f32>(kind, &raw).unwrap(),
5323 vec![1.0, -2.0, 0.333_251_95, 65504.0]
5324 );
5325 assert_eq!(
5326 convert::<f64>(kind, &raw).unwrap(),
5327 vec![1.0, -2.0, 0.333_251_953_125, 65504.0]
5328 );
5329
5330 let DatatypeMessage::FloatingPoint { .. } = DatatypeMessage::f16_type() else {
5331 unreachable!()
5332 };
5333 let mut be = DatatypeMessage::f16_type();
5334 if let DatatypeMessage::FloatingPoint { byte_order, .. } = &mut be {
5335 *byte_order = ByteOrder::BigEndian;
5336 }
5337 let be_kind = classify(&be).unwrap();
5338 assert_eq!(convert::<f32>(be_kind, &[0x3c, 0x00]).unwrap(), vec![1.0]);
5339 }
5340
5341 #[test]
5344 fn non_ieee_float_is_refused() {
5345 let mut odd = DatatypeMessage::f32_type();
5346 if let DatatypeMessage::FloatingPoint { exponent_bias, .. } = &mut odd {
5347 *exponent_bias = 63;
5348 }
5349 assert!(odd.ieee_format().is_none());
5350 let err = classify(&odd).err().expect("non-IEEE float was accepted");
5351 assert!(
5352 err.to_string().contains("IEEE 754 interchange format"),
5353 "unexpected error: {err}"
5354 );
5355 }
5356 }
5357}
5358
5359#[cfg(test)]
5360mod tests {
5361 use crate::H5File;
5362 use std::path::PathBuf;
5363
5364 fn temp_path(name: &str) -> PathBuf {
5365 use std::sync::atomic::{AtomicU64, Ordering};
5369 static COUNTER: AtomicU64 = AtomicU64::new(0);
5370 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
5371 std::env::temp_dir().join(format!(
5372 "hdf5_dataset_test_{}_{}_{}.h5",
5373 name,
5374 std::process::id(),
5375 n
5376 ))
5377 }
5378
5379 #[test]
5380 fn runtime_compound_via_datatype_override_and_raw_bytes() {
5381 use crate::format::messages::datatype::DatatypeMessage;
5382 use crate::types::{CompoundType, H5Type};
5383
5384 let path = temp_path("compound_raw");
5385 let ct = CompoundType {
5389 members: vec![
5390 ("id".to_string(), i32::hdf5_type(), 0),
5391 ("val".to_string(), f64::hdf5_type(), 4),
5392 ],
5393 total_size: 12,
5394 };
5395 let recs: [(i32, f64); 3] = [(1, 2.5), (2, 3.5), (3, -4.0)];
5396 let mut bytes = Vec::new();
5397 for (id, val) in recs {
5398 bytes.extend_from_slice(&id.to_le_bytes());
5399 bytes.extend_from_slice(&val.to_le_bytes());
5400 }
5401
5402 {
5403 let file = H5File::create(&path).unwrap();
5404 let ds = file
5405 .new_dataset::<u8>()
5406 .datatype(ct.to_datatype())
5407 .shape([recs.len()])
5408 .create("records")
5409 .unwrap();
5410 ds.write_raw_bytes(&bytes).unwrap();
5411 file.close().unwrap();
5412 }
5413 {
5414 let file = H5File::open(&path).unwrap();
5415 let ds = file.dataset("records").unwrap();
5416 match ds.datatype().unwrap() {
5419 DatatypeMessage::Compound { size, members } => {
5420 assert_eq!(size, 12);
5421 assert_eq!(members.len(), 2);
5422 assert_eq!(members[0].name, "id");
5423 assert_eq!(members[0].offset, 0);
5424 assert_eq!(members[1].name, "val");
5425 assert_eq!(members[1].offset, 4);
5426 }
5427 other => panic!("expected compound datatype, got {other:?}"),
5428 }
5429 assert_eq!(ds.read_raw_bytes().unwrap(), bytes);
5430 }
5431 std::fs::remove_file(&path).ok();
5432 }
5433
5434 #[test]
5435 fn builder_requires_shape() {
5436 let path = temp_path("no_shape");
5437 let file = H5File::create(&path).unwrap();
5438 let result = file.new_dataset::<u8>().create("data");
5439 assert!(result.is_err());
5440 std::fs::remove_file(&path).ok();
5441 }
5442
5443 #[test]
5449 fn a_partial_edge_chunk_does_not_grow_past_the_declared_maximum() {
5450 let path = temp_path("edge_chunk_extent");
5451 let file = H5File::create(&path).unwrap();
5454 let ds = file
5455 .new_dataset::<i32>()
5456 .shape([10usize, 4])
5457 .max_shape(&[Some(10), None])
5458 .chunk(&[4, 4])
5459 .create("grid")
5460 .unwrap();
5461 let chunk: Vec<u8> = (0i32..16).flat_map(|v| v.to_le_bytes()).collect();
5462 ds.write_chunk_at(&[2, 0], &chunk).unwrap();
5464 assert_eq!(ds.shape(), vec![10, 4]);
5465 file.close().unwrap();
5466
5467 let file = H5File::open(&path).unwrap();
5468 let back = file.dataset("grid").unwrap().read_raw::<i32>().unwrap();
5469 assert_eq!(back.len(), 40);
5470 assert_eq!(&back[32..40], &[0, 1, 2, 3, 4, 5, 6, 7]);
5471 drop(file);
5472 std::fs::remove_file(&path).ok();
5473 }
5474
5475 #[test]
5484 fn the_edge_write_cap_holds_for_the_v2_btree_index() {
5485 let path = temp_path("edge_chunk_bt2");
5486 let file = H5File::create(&path).unwrap();
5487 let ds = file
5488 .new_dataset::<i32>()
5489 .shape([10usize, 4, 4])
5490 .max_shape(&[Some(10), None, None])
5491 .chunk(&[4, 4, 4])
5492 .create("cube")
5493 .unwrap();
5494 let chunk: Vec<u8> = (0i32..64).flat_map(|v| v.to_le_bytes()).collect();
5495 ds.write_chunk_at(&[2, 0, 0], &chunk).unwrap();
5497 assert_eq!(ds.shape(), vec![10, 4, 4]);
5498 file.close().unwrap();
5499
5500 let file = H5File::open(&path).unwrap();
5501 let back = file.dataset("cube").unwrap().read_raw::<i32>().unwrap();
5502 assert_eq!(back.len(), 160);
5503 assert_eq!(&back[128..160], &(0i32..32).collect::<Vec<_>>()[..]);
5505 drop(file);
5506 std::fs::remove_file(&path).ok();
5507 }
5508
5509 #[test]
5517 fn an_external_or_virtual_dataset_never_reaches_the_edge_write_cap() {
5518 use crate::Selection;
5519 let dir = std::env::temp_dir().join(format!(
5520 "rust_hdf5_edge_cap_{}_{}",
5521 std::process::id(),
5522 temp_path("x").file_name().unwrap().to_string_lossy()
5523 ));
5524 std::fs::create_dir_all(&dir).unwrap();
5525 let path = dir.join("edge_cap.h5");
5526 let file = H5File::create(&path).unwrap();
5527 let payload = dir.join("payload.raw");
5528
5529 for (which, res) in [
5531 (
5532 "external",
5533 file.new_dataset::<i32>()
5534 .shape([10usize])
5535 .external(&[(payload.to_str().unwrap(), 0, 40)])
5536 .chunk(&[4])
5537 .create("a"),
5538 ),
5539 (
5540 "virtual",
5541 file.new_dataset::<i32>()
5542 .shape([10usize])
5543 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
5544 .chunk(&[4])
5545 .create("b"),
5546 ),
5547 ] {
5548 match res {
5549 Ok(_) => panic!("a {which} dataset cannot also be chunked"),
5550 Err(e) => assert!(e.to_string().contains("chunked"), "{which}: {e}"),
5551 }
5552 }
5553
5554 let ext = file
5557 .new_dataset::<i32>()
5558 .shape([10usize])
5559 .external(&[(payload.to_str().unwrap(), 0, 40)])
5560 .create("outside")
5561 .unwrap();
5562 let vds = file
5563 .new_dataset::<i32>()
5564 .shape([10usize])
5565 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
5566 .create("elsewhere")
5567 .unwrap();
5568 let chunk: Vec<u8> = (0i32..4).flat_map(|v| v.to_le_bytes()).collect();
5569 for (which, ds) in [("external", &ext), ("virtual", &vds)] {
5570 let err = ds.write_chunk_at(&[2], &chunk).unwrap_err().to_string();
5571 assert!(err.contains("only for chunked datasets"), "{which}: {err}");
5572 let err = ds
5573 .write_chunk_raw_at(&[2], &chunk, 0)
5574 .unwrap_err()
5575 .to_string();
5576 assert!(err.contains("only for chunked datasets"), "{which}: {err}");
5577 assert_eq!(ds.shape(), vec![10], "{which}");
5578 }
5579 file.close().unwrap();
5580 std::fs::remove_dir_all(&dir).ok();
5581 }
5582
5583 #[test]
5584 fn write_raw_size_mismatch() {
5585 let path = temp_path("size_mismatch");
5586 let file = H5File::create(&path).unwrap();
5587 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
5588 let result = ds.write_raw(&[1u8, 2, 3]);
5590 assert!(result.is_err());
5591 std::fs::remove_file(&path).ok();
5592 }
5593
5594 #[cfg(feature = "deflate")]
5598 #[test]
5599 fn filter_without_chunk_autochunks_and_roundtrips() {
5600 let path = temp_path("autochunk_filter");
5601 let data: Vec<i32> = (0..8).collect();
5602 {
5603 let file = H5File::create(&path).unwrap();
5604 let ds = file
5605 .new_dataset::<i32>()
5606 .deflate(6)
5607 .shape([8])
5608 .create("seq")
5609 .unwrap();
5610 ds.write_raw(&data).unwrap();
5611 file.close().unwrap();
5612 }
5613 {
5614 let file = H5File::open(&path).unwrap();
5615 let ds = file.dataset("seq").unwrap();
5616 assert!(
5618 ds.is_chunked(),
5619 "auto-chunk did not produce chunked storage"
5620 );
5621 assert_eq!(ds.chunk_dims(), Some(vec![8]));
5622 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5623 }
5624 std::fs::remove_file(&path).ok();
5625 }
5626
5627 #[cfg(feature = "deflate")]
5631 #[test]
5632 fn an_edge_chunk_pads_with_zero_not_the_chunk_written_before_it() {
5633 let path = temp_path("edge_chunk_padding");
5639 let data: Vec<i32> = (1..=20).collect(); {
5641 let file = H5File::create(&path).unwrap();
5642 let ds = file
5643 .new_dataset::<i32>()
5644 .shape([4, 5])
5645 .chunk(&[2, 3])
5646 .create("grid")
5647 .unwrap();
5648 ds.write_raw(&data).unwrap();
5649 file.close().unwrap();
5650 }
5651 {
5652 let file = H5File::open(&path).unwrap();
5653 let ds = file.dataset("grid").unwrap();
5654 let as_i32 = |bytes: Vec<u8>| -> Vec<i32> {
5655 bytes
5656 .as_chunks::<4>()
5657 .0
5658 .iter()
5659 .map(|b| i32::from_le_bytes(*b))
5660 .collect()
5661 };
5662 assert_eq!(
5665 as_i32(ds.read_chunk_raw_at(&[0, 0]).unwrap().0),
5666 [1, 2, 3, 6, 7, 8]
5667 );
5668 assert_eq!(
5669 as_i32(ds.read_chunk_raw_at(&[0, 1]).unwrap().0),
5670 [4, 5, 0, 9, 10, 0]
5671 );
5672 assert_eq!(
5673 as_i32(ds.read_chunk_raw_at(&[1, 1]).unwrap().0),
5674 [14, 15, 0, 19, 20, 0]
5675 );
5676 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5677 }
5678 std::fs::remove_file(&path).ok();
5679 }
5680
5681 #[test]
5682 #[cfg(feature = "deflate")]
5683 fn write_raw_multichunk_edge_roundtrips() {
5684 let path = temp_path("multichunk_edge");
5685 let data: Vec<i32> = (0..35).collect(); {
5687 let file = H5File::create(&path).unwrap();
5688 let ds = file
5689 .new_dataset::<i32>()
5690 .shape([7, 5])
5691 .chunk(&[3, 2])
5692 .deflate(4)
5693 .create("grid")
5694 .unwrap();
5695 ds.write_raw(&data).unwrap();
5696 file.close().unwrap();
5697 }
5698 {
5699 let file = H5File::open(&path).unwrap();
5700 let ds = file.dataset("grid").unwrap();
5701 assert_eq!(ds.shape(), vec![7, 5]);
5702 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
5703 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5704 }
5705 std::fs::remove_file(&path).ok();
5706 }
5707
5708 #[test]
5711 fn write_raw_unfiltered_chunked_roundtrips() {
5712 let path = temp_path("chunked_unfiltered");
5713 let data: Vec<f64> = (0..12).map(|i| i as f64 * 1.5).collect(); {
5715 let file = H5File::create(&path).unwrap();
5716 let ds = file
5717 .new_dataset::<f64>()
5718 .shape([4, 3])
5719 .chunk(&[2, 2])
5720 .create("m")
5721 .unwrap();
5722 ds.write_raw(&data).unwrap();
5723 file.close().unwrap();
5724 }
5725 {
5726 let file = H5File::open(&path).unwrap();
5727 let ds = file.dataset("m").unwrap();
5728 assert_eq!(ds.chunk_dims(), Some(vec![2, 2]));
5729 assert_eq!(ds.read_raw::<f64>().unwrap(), data);
5730 }
5731 std::fs::remove_file(&path).ok();
5732 }
5733
5734 #[cfg(feature = "deflate")]
5739 #[test]
5740 fn write_raw_ea_compressed_roundtrips() {
5741 let path = temp_path("write_raw_ea_deflate");
5742 let data: Vec<i32> = (0..20).collect(); {
5744 let file = H5File::create(&path).unwrap();
5745 let ds = file
5746 .new_dataset::<i32>()
5747 .shape([5, 4])
5748 .chunk(&[2, 4])
5749 .max_shape(&[None, Some(4)]) .deflate(5)
5751 .create("stream")
5752 .unwrap();
5753 ds.write_raw(&data).unwrap();
5754 file.close().unwrap();
5755 }
5756 {
5757 let file = H5File::open(&path).unwrap();
5758 let ds = file.dataset("stream").unwrap();
5759 assert_eq!(ds.shape(), vec![5, 4]);
5760 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5761 }
5762 std::fs::remove_file(&path).ok();
5763 }
5764
5765 #[cfg(feature = "deflate")]
5770 #[test]
5771 fn read_full_3d_chunked_edge_roundtrips() {
5772 let path = temp_path("full_3d_chunked_edge");
5773 let total: usize = 5 * 4 * 3;
5776 let data: Vec<i32> = (0..total as i32).collect();
5777 {
5778 let file = H5File::create(&path).unwrap();
5779 let ds = file
5780 .new_dataset::<i32>()
5781 .shape([5, 4, 3])
5782 .chunk(&[2, 3, 2])
5783 .deflate(4)
5784 .create("vol")
5785 .unwrap();
5786 ds.write_raw(&data).unwrap();
5787 file.close().unwrap();
5788 }
5789 {
5790 let file = H5File::open(&path).unwrap();
5791 let ds = file.dataset("vol").unwrap();
5792 assert_eq!(ds.shape(), vec![5, 4, 3]);
5793 assert_eq!(ds.chunk_dims(), Some(vec![2, 3, 2]));
5794 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5795 }
5796 std::fs::remove_file(&path).ok();
5797 }
5798
5799 #[test]
5800 fn roundtrip_u8_1d() {
5801 let path = temp_path("rt_u8_1d");
5802 let data: Vec<u8> = (0..10).collect();
5803
5804 {
5805 let file = H5File::create(&path).unwrap();
5806 let ds = file.new_dataset::<u8>().shape([10]).create("seq").unwrap();
5807 ds.write_raw(&data).unwrap();
5808 file.close().unwrap();
5809 }
5810
5811 {
5812 let file = H5File::open(&path).unwrap();
5813 let ds = file.dataset("seq").unwrap();
5814 assert_eq!(ds.shape(), vec![10]);
5815 let readback = ds.read_raw::<u8>().unwrap();
5816 assert_eq!(readback, data);
5817 }
5818
5819 std::fs::remove_file(&path).ok();
5820 }
5821
5822 #[test]
5823 fn roundtrip_i32_2d() {
5824 let path = temp_path("rt_i32_2d");
5825 let data: Vec<i32> = vec![-1, 0, 1, 2, 3, 4];
5826
5827 {
5828 let file = H5File::create(&path).unwrap();
5829 let ds = file
5830 .new_dataset::<i32>()
5831 .shape([2, 3])
5832 .create("matrix")
5833 .unwrap();
5834 ds.write_raw(&data).unwrap();
5835 file.close().unwrap();
5836 }
5837
5838 {
5839 let file = H5File::open(&path).unwrap();
5840 let ds = file.dataset("matrix").unwrap();
5841 assert_eq!(ds.shape(), vec![2, 3]);
5842 let readback = ds.read_raw::<i32>().unwrap();
5843 assert_eq!(readback, data);
5844 }
5845
5846 std::fs::remove_file(&path).ok();
5847 }
5848
5849 #[test]
5850 fn roundtrip_f64_3d() {
5851 let path = temp_path("rt_f64_3d");
5852 let data: Vec<f64> = (0..24).map(|i| i as f64 * 0.5).collect();
5853
5854 {
5855 let file = H5File::create(&path).unwrap();
5856 let ds = file
5857 .new_dataset::<f64>()
5858 .shape([2, 3, 4])
5859 .create("cube")
5860 .unwrap();
5861 ds.write_raw(&data).unwrap();
5862 file.close().unwrap();
5863 }
5864
5865 {
5866 let file = H5File::open(&path).unwrap();
5867 let ds = file.dataset("cube").unwrap();
5868 assert_eq!(ds.shape(), vec![2, 3, 4]);
5869 let readback = ds.read_raw::<f64>().unwrap();
5870 assert_eq!(readback, data);
5871 }
5872
5873 std::fs::remove_file(&path).ok();
5874 }
5875
5876 #[test]
5877 fn cannot_read_in_write_mode() {
5878 let path = temp_path("no_read_write");
5879 let file = H5File::create(&path).unwrap();
5880 let ds = file.new_dataset::<u8>().shape([4]).create("x").unwrap();
5881 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
5882 let result = ds.read_raw::<u8>();
5883 assert!(result.is_err());
5884 std::fs::remove_file(&path).ok();
5885 }
5886
5887 #[test]
5888 fn cannot_write_in_read_mode() {
5889 let path = temp_path("no_write_read");
5890
5891 {
5892 let file = H5File::create(&path).unwrap();
5893 let ds = file.new_dataset::<u8>().shape([4]).create("x").unwrap();
5894 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
5895 file.close().unwrap();
5896 }
5897
5898 {
5899 let file = H5File::open(&path).unwrap();
5900 let ds = file.dataset("x").unwrap();
5901 let result = ds.write_raw(&[5u8, 6, 7, 8]);
5902 assert!(result.is_err());
5903 }
5904
5905 std::fs::remove_file(&path).ok();
5906 }
5907
5908 #[test]
5909 fn numeric_attr_roundtrip() {
5910 let path = temp_path("num_attr");
5911 {
5912 let file = H5File::create(&path).unwrap();
5913 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
5914 ds.write_raw(&[1.0f32; 4]).unwrap();
5915
5916 let a1 = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
5917 a1.write_numeric(&1.2345f64).unwrap();
5918
5919 let a2 = ds.new_attr::<i32>().shape(()).create("count").unwrap();
5920 a2.write_numeric(&42i32).unwrap();
5921
5922 file.close().unwrap();
5923 }
5924 {
5925 let file = H5File::open(&path).unwrap();
5926 let ds = file.dataset("data").unwrap();
5927
5928 let scale = ds.attr("scale").unwrap();
5929 let val: f64 = scale.read_numeric().unwrap();
5930 assert!((val - 1.2345).abs() < 1e-10);
5931
5932 let count = ds.attr("count").unwrap();
5933 let val: i32 = count.read_numeric().unwrap();
5934 assert_eq!(val, 42);
5935 }
5936 std::fs::remove_file(&path).ok();
5937 }
5938
5939 #[test]
5940 fn array_attr_roundtrip() {
5941 let path = temp_path("array_attr");
5942 let offsets = [10i32, -20, 30];
5943 {
5944 let file = H5File::create(&path).unwrap();
5945 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
5946 ds.write_raw(&[1.0f32; 4]).unwrap();
5947
5948 let a = ds
5950 .new_attr::<i32>()
5951 .shape([3])
5952 .create("dim_offset")
5953 .unwrap();
5954 a.write_array(&offsets).unwrap();
5955
5956 let bad = ds.new_attr::<i32>().shape([3]).create("bad").unwrap();
5958 assert!(bad.write_array(&[1i32, 2]).is_err());
5959
5960 file.close().unwrap();
5961 }
5962 {
5963 let file = H5File::open(&path).unwrap();
5964 let ds = file.dataset("data").unwrap();
5965 let a = ds.attr("dim_offset").unwrap();
5966 let raw = a.read_raw().unwrap();
5967 assert_eq!(raw.len(), 3 * 4);
5968 let got: Vec<i32> = raw
5969 .as_chunks::<4>()
5970 .0
5971 .iter()
5972 .map(|b| i32::from_le_bytes(*b))
5973 .collect();
5974 assert_eq!(got, offsets);
5975 }
5976 std::fs::remove_file(&path).ok();
5977 }
5978
5979 #[test]
5980 fn attr_datatype_exposes_class_and_sign() {
5981 use crate::format::messages::datatype::DatatypeMessage;
5985
5986 let path = temp_path("attr_datatype");
5987 {
5988 let file = H5File::create(&path).unwrap();
5989 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
5990 ds.new_attr::<f64>()
5991 .shape(())
5992 .create("scale")
5993 .unwrap()
5994 .write_numeric(&1.5f64)
5995 .unwrap();
5996 ds.new_attr::<i32>()
5997 .shape(())
5998 .create("count")
5999 .unwrap()
6000 .write_numeric(&7i32)
6001 .unwrap();
6002 file.close().unwrap();
6003 }
6004 {
6005 let file = H5File::open(&path).unwrap();
6006 let ds = file.dataset("data").unwrap();
6007
6008 match ds.attr("scale").unwrap().datatype().unwrap() {
6009 DatatypeMessage::FloatingPoint { size, .. } => assert_eq!(size, 8),
6010 other => panic!("expected FloatingPoint for f64 attr, got {other:?}"),
6011 }
6012
6013 match ds.attr("count").unwrap().datatype().unwrap() {
6014 DatatypeMessage::FixedPoint { size, signed, .. } => {
6015 assert_eq!(size, 4);
6016 assert!(signed, "i32 attr must be signed");
6017 }
6018 other => panic!("expected FixedPoint for i32 attr, got {other:?}"),
6019 }
6020 }
6021 std::fs::remove_file(&path).ok();
6022 }
6023
6024 #[test]
6025 fn attr_datatype_in_write_mode_errors() {
6026 let path = temp_path("attr_datatype_write_mode");
6027 let file = H5File::create(&path).unwrap();
6028 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
6029 let attr = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
6030 assert!(attr.datatype().is_err());
6031 std::fs::remove_file(&path).ok();
6032 }
6033
6034 #[test]
6035 fn cannot_create_dataset_in_read_mode() {
6036 let path = temp_path("no_create_read");
6037
6038 {
6039 let _file = H5File::create(&path).unwrap();
6040 }
6041
6042 {
6043 let file = H5File::open(&path).unwrap();
6044 let result = file.new_dataset::<u8>().shape([4]).create("x");
6045 assert!(result.is_err());
6046 }
6047
6048 std::fs::remove_file(&path).ok();
6049 }
6050
6051 #[test]
6052 fn shape_accessor() {
6053 let path = temp_path("shape_acc");
6054
6055 let file = H5File::create(&path).unwrap();
6056 let ds = file
6057 .new_dataset::<f32>()
6058 .shape([5, 10, 3])
6059 .create("tensor")
6060 .unwrap();
6061 assert_eq!(ds.shape(), vec![5, 10, 3]);
6062
6063 std::fs::remove_file(&path).ok();
6064 }
6065
6066 #[test]
6067 fn slice_roundtrip_2d() {
6068 let path = temp_path("slice_2d");
6069
6070 let data: Vec<i32> = (0..20).collect();
6072 {
6073 let file = H5File::create(&path).unwrap();
6074 let ds = file
6075 .new_dataset::<i32>()
6076 .shape([4, 5])
6077 .create("mat")
6078 .unwrap();
6079 ds.write_raw(&data).unwrap();
6080 file.close().unwrap();
6081 }
6082 {
6083 let file = H5File::open(&path).unwrap();
6084 let ds = file.dataset("mat").unwrap();
6085 let slice = ds.read_slice::<i32>(&[1, 2], &[2, 2]).unwrap();
6087 assert_eq!(slice, vec![7, 8, 12, 13]);
6090 }
6091
6092 std::fs::remove_file(&path).ok();
6093 }
6094
6095 fn assert_into_matches<T>(ds: &super::H5Dataset, starts: &[usize], counts: &[usize])
6100 where
6101 T: crate::types::H5Type + Copy + std::fmt::Debug + PartialEq + Default,
6102 {
6103 let n: usize = ds.shape().iter().product();
6104 let want_full = ds.read_raw::<T>().unwrap();
6105 let mut got_full = vec![T::default(); n];
6106 ds.read_raw_into::<T>(&mut got_full).unwrap();
6107 assert_eq!(got_full, want_full, "read_raw_into != read_raw");
6108
6109 let want_slice = ds.read_slice::<T>(starts, counts).unwrap();
6110 let sn: usize = counts.iter().product();
6111 let mut got_slice = vec![T::default(); sn];
6112 ds.read_slice_into::<T>(&mut got_slice, starts, counts)
6113 .unwrap();
6114 assert_eq!(got_slice, want_slice, "read_slice_into != read_slice");
6115 }
6116
6117 #[test]
6118 fn read_into_matches_vec_contiguous() {
6119 let path = temp_path("into_contig");
6120 let data: Vec<i32> = (0..20).collect(); {
6122 let file = H5File::create(&path).unwrap();
6123 let ds = file
6124 .new_dataset::<i32>()
6125 .shape([4, 5])
6126 .create("mat")
6127 .unwrap();
6128 ds.write_raw(&data).unwrap();
6129 file.close().unwrap();
6130 }
6131 {
6132 let file = H5File::open(&path).unwrap();
6133 let ds = file.dataset("mat").unwrap();
6134 assert_eq!(ds.chunk_dims(), None);
6135 assert_into_matches::<i32>(&ds, &[1, 2], &[2, 2]);
6136 }
6137 std::fs::remove_file(&path).ok();
6138 }
6139
6140 #[test]
6141 fn read_into_matches_vec_chunked_unfiltered() {
6142 let path = temp_path("into_chunk");
6143 let data: Vec<f64> = (0..35).map(|i| i as f64 * 1.5).collect(); {
6145 let file = H5File::create(&path).unwrap();
6146 let ds = file
6147 .new_dataset::<f64>()
6148 .shape([7, 5])
6149 .chunk(&[3, 2]) .create("grid")
6151 .unwrap();
6152 ds.write_raw(&data).unwrap();
6153 file.close().unwrap();
6154 }
6155 {
6156 let file = H5File::open(&path).unwrap();
6157 let ds = file.dataset("grid").unwrap();
6158 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
6159 assert_into_matches::<f64>(&ds, &[2, 1], &[3, 3]);
6161 }
6162 std::fs::remove_file(&path).ok();
6163 }
6164
6165 #[test]
6166 fn read_into_matches_vec_single_chunk() {
6167 let path = temp_path("into_single_chunk");
6168 let data: Vec<i32> = (0..12).collect(); {
6170 let file = H5File::create(&path).unwrap();
6171 let ds = file
6172 .new_dataset::<i32>()
6173 .shape([3, 4])
6174 .chunk(&[3, 4]) .create("g")
6176 .unwrap();
6177 ds.write_raw(&data).unwrap();
6178 file.close().unwrap();
6179 }
6180 {
6181 let file = H5File::open(&path).unwrap();
6182 let ds = file.dataset("g").unwrap();
6183 assert_eq!(ds.chunk_dims(), Some(vec![3, 4]));
6184 assert_into_matches::<i32>(&ds, &[1, 1], &[2, 2]);
6185 }
6186 std::fs::remove_file(&path).ok();
6187 }
6188
6189 #[cfg(feature = "deflate")]
6190 #[test]
6191 fn read_into_matches_vec_chunked_deflate() {
6192 let path = temp_path("into_chunk_deflate");
6193 let data: Vec<i32> = (0..35).collect(); {
6195 let file = H5File::create(&path).unwrap();
6196 let ds = file
6197 .new_dataset::<i32>()
6198 .shape([7, 5])
6199 .chunk(&[3, 2])
6200 .deflate(4)
6201 .create("grid")
6202 .unwrap();
6203 ds.write_raw(&data).unwrap();
6204 file.close().unwrap();
6205 }
6206 {
6207 let file = H5File::open(&path).unwrap();
6208 let ds = file.dataset("grid").unwrap();
6209 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
6210 assert_into_matches::<i32>(&ds, &[2, 1], &[3, 3]);
6211 }
6212 std::fs::remove_file(&path).ok();
6213 }
6214
6215 #[test]
6216 fn read_into_wrong_buffer_size_rejected() {
6217 let path = temp_path("into_badlen");
6218 let data: Vec<i32> = (0..20).collect(); {
6220 let file = H5File::create(&path).unwrap();
6221 let ds = file
6222 .new_dataset::<i32>()
6223 .shape([4, 5])
6224 .create("mat")
6225 .unwrap();
6226 ds.write_raw(&data).unwrap();
6227 file.close().unwrap();
6228 }
6229 {
6230 let file = H5File::open(&path).unwrap();
6231 let ds = file.dataset("mat").unwrap();
6232
6233 let mut small = vec![0i32; 19];
6235 assert!(ds.read_raw_into::<i32>(&mut small).is_err());
6236 let mut large = vec![0i32; 21];
6237 assert!(ds.read_raw_into::<i32>(&mut large).is_err());
6238
6239 let mut bad_slice = vec![0i32; 3];
6241 assert!(ds
6242 .read_slice_into::<i32>(&mut bad_slice, &[1, 2], &[2, 2])
6243 .is_err());
6244 let mut ok_slice = vec![0i32; 4];
6246 assert!(ds
6247 .read_slice_into::<i32>(&mut ok_slice, &[1, 2], &[2, 2])
6248 .is_ok());
6249 }
6250 std::fs::remove_file(&path).ok();
6251 }
6252
6253 #[test]
6254 fn read_into_wrong_element_size_rejected() {
6255 let path = temp_path("into_badtype");
6256 let data: Vec<i32> = (0..20).collect(); {
6258 let file = H5File::create(&path).unwrap();
6259 let ds = file
6260 .new_dataset::<i32>()
6261 .shape([4, 5])
6262 .create("mat")
6263 .unwrap();
6264 ds.write_raw(&data).unwrap();
6265 file.close().unwrap();
6266 }
6267 {
6268 let file = H5File::open(&path).unwrap();
6269 let ds = file.dataset("mat").unwrap();
6270 let mut as_u8 = vec![0u8; 20];
6273 assert!(matches!(
6274 ds.read_raw_into::<u8>(&mut as_u8),
6275 Err(crate::Hdf5Error::TypeMismatch(_))
6276 ));
6277 let mut as_i64 = vec![0i64; 20];
6278 assert!(matches!(
6279 ds.read_slice_into::<i64>(&mut as_i64, &[0, 0], &[4, 5]),
6280 Err(crate::Hdf5Error::TypeMismatch(_))
6281 ));
6282 }
6283 std::fs::remove_file(&path).ok();
6284 }
6285
6286 #[test]
6287 fn write_slice_2d() {
6288 let path = temp_path("write_slice_2d");
6289
6290 {
6291 let file = H5File::create(&path).unwrap();
6292 let ds = file
6293 .new_dataset::<f32>()
6294 .shape([3, 4])
6295 .create("data")
6296 .unwrap();
6297 ds.write_raw(&[0.0f32; 12]).unwrap();
6298 ds.write_slice(&[1, 1], &[2, 2], &[10.0f32, 20.0, 30.0, 40.0])
6300 .unwrap();
6301 file.close().unwrap();
6302 }
6303 {
6304 let file = H5File::open(&path).unwrap();
6305 let ds = file.dataset("data").unwrap();
6306 let full = ds.read_raw::<f32>().unwrap();
6307 assert_eq!(
6311 full,
6312 vec![0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 20.0, 0.0, 0.0, 30.0, 40.0, 0.0,]
6313 );
6314 }
6315
6316 std::fs::remove_file(&path).ok();
6317 }
6318
6319 fn chunk_of(v: i32) -> Vec<u8> {
6321 (0..8).flat_map(|_| v.to_le_bytes()).collect()
6322 }
6323
6324 fn rewrite_chunk(
6328 tag: &str,
6329 rewrites: i32,
6330 build: impl Fn(&H5File) -> crate::H5Dataset,
6331 ) -> (u64, i32) {
6332 let path = temp_path(tag);
6333 {
6334 let file = H5File::create(&path).unwrap();
6335 let ds = build(&file);
6336 for v in 1..=rewrites {
6337 ds.write_chunk_at(&[0, 0], &chunk_of(v)).unwrap();
6338 }
6339 file.close().unwrap();
6340 }
6341 let size = std::fs::metadata(&path).unwrap().len();
6342 let first = {
6343 let file = H5File::open(&path).unwrap();
6344 file.dataset("d").unwrap().read_raw::<i32>().unwrap()[0]
6345 };
6346 std::fs::remove_file(&path).ok();
6347 (size, first)
6348 }
6349
6350 #[test]
6356 fn rewriting_an_unfiltered_extensible_array_chunk_stays_in_place() {
6357 let build = |f: &H5File| {
6358 f.new_dataset::<i32>()
6359 .shape([2, 4])
6360 .chunk(&[2, 4])
6361 .max_shape(&[None, Some(4)])
6362 .create("d")
6363 .unwrap()
6364 };
6365 let (once, _) = rewrite_chunk("rewrite_ea_1", 1, build);
6366 let (many, last) = rewrite_chunk("rewrite_ea_8", 8, build);
6367 assert_eq!(many, once, "8 rewrites grew the file past a single write");
6368 assert_eq!(last, 8, "the last write must be the one that survives");
6369 }
6370
6371 #[test]
6372 fn rewriting_an_unfiltered_fixed_array_chunk_stays_in_place() {
6373 let build = |f: &H5File| {
6374 f.new_dataset::<i32>()
6375 .shape([2, 4])
6376 .chunk(&[2, 4])
6377 .create("d")
6378 .unwrap()
6379 };
6380 let (once, _) = rewrite_chunk("rewrite_fa_1", 1, build);
6381 let (many, last) = rewrite_chunk("rewrite_fa_8", 8, build);
6382 assert_eq!(many, once, "8 rewrites grew the file past a single write");
6383 assert_eq!(last, 8);
6384 }
6385
6386 #[test]
6387 fn rewriting_an_unfiltered_btree_v2_chunk_stays_in_place() {
6388 let build = |f: &H5File| {
6389 f.new_dataset::<i32>()
6390 .shape([2, 4])
6391 .chunk(&[2, 4])
6392 .max_shape(&[None, None])
6393 .create("d")
6394 .unwrap()
6395 };
6396 let (once, _) = rewrite_chunk("rewrite_bt2_1", 1, build);
6397 let (many, last) = rewrite_chunk("rewrite_bt2_8", 8, build);
6398 assert_eq!(many, once, "8 rewrites grew the file past a single write");
6399 assert_eq!(last, 8);
6400 }
6401
6402 #[test]
6407 fn repeated_flushes_do_not_grow_a_btree_v2_index() {
6408 let flush_n = |label: &str, flushes: usize| -> u64 {
6409 let path = temp_path(label);
6410 {
6411 let file = H5File::create(&path).unwrap();
6412 let ds = file
6413 .new_dataset::<i32>()
6414 .shape([2, 4])
6415 .chunk(&[2, 4])
6416 .max_shape(&[None, None])
6417 .create("d")
6418 .unwrap();
6419 let bytes: Vec<u8> = (0..8i32).flat_map(|v| v.to_le_bytes()).collect();
6420 ds.write_chunk_at(&[0, 0], &bytes).unwrap();
6421 for _ in 0..flushes {
6422 ds.flush().unwrap();
6423 }
6424 file.close().unwrap();
6425 }
6426 let size = std::fs::metadata(&path).unwrap().len();
6427 {
6429 let file = H5File::open(&path).unwrap();
6430 let ds = file.dataset("d").unwrap();
6431 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..8).collect::<Vec<i32>>());
6432 }
6433 std::fs::remove_file(&path).ok();
6434 size
6435 };
6436 assert_eq!(
6437 flush_n("bt2_flush_8", 8),
6438 flush_n("bt2_flush_1", 1),
6439 "8 index flushes grew the file past a single one"
6440 );
6441 }
6442
6443 #[cfg(feature = "deflate")]
6449 #[test]
6450 fn rewriting_a_filtered_chunk_recycles_the_released_block() {
6451 let flat: Vec<u8> = (0..8).flat_map(|_| 7i32.to_le_bytes()).collect();
6454 let varied: Vec<u8> = (0..8i32)
6455 .flat_map(|i| i.wrapping_mul(0x5bd1_e995).to_le_bytes())
6456 .collect();
6457
6458 let sizes: Vec<u64> = [1usize, 8]
6459 .iter()
6460 .map(|&rounds| {
6461 let path = temp_path(&format!("rewrite_filtered_{rounds}"));
6462 {
6463 let file = H5File::create(&path).unwrap();
6464 let ds = file
6465 .new_dataset::<i32>()
6466 .shape([2, 4])
6467 .chunk(&[2, 4])
6468 .max_shape(&[None, Some(4)])
6469 .deflate(6)
6470 .create("d")
6471 .unwrap();
6472 for _ in 0..rounds {
6473 ds.write_chunk_at(&[0, 0], &flat).unwrap();
6474 ds.write_chunk_at(&[0, 0], &varied).unwrap();
6475 }
6476 file.close().unwrap();
6477 }
6478 let size = std::fs::metadata(&path).unwrap().len();
6479 {
6480 let file = H5File::open(&path).unwrap();
6481 let got = file.dataset("d").unwrap().read_raw::<i32>().unwrap();
6482 let want: Vec<i32> = (0..8i32).map(|i| i.wrapping_mul(0x5bd1_e995)).collect();
6483 assert_eq!(got, want, "the last write must survive the round trip");
6484 }
6485 std::fs::remove_file(&path).ok();
6486 size
6487 })
6488 .collect();
6489
6490 assert_eq!(
6491 sizes[1], sizes[0],
6492 "8 alternating rewrites grew the file past a single pair"
6493 );
6494 }
6495
6496 #[test]
6497 fn write_slice_out_of_bounds_rejected() {
6498 let path = temp_path("write_slice_oob");
6499 let file = H5File::create(&path).unwrap();
6500 let ds = file.new_dataset::<i32>().shape([4]).create("d").unwrap();
6501 ds.write_raw(&[0i32; 4]).unwrap();
6502 assert!(ds.write_slice(&[2], &[6], &[9i32; 6]).is_err());
6504 assert!(ds.write_slice(&[1], &[2], &[7i32, 8]).is_ok());
6506 std::fs::remove_file(&path).ok();
6507 }
6508
6509 #[test]
6510 fn duplicate_dataset_name_rejected() {
6511 let path = temp_path("dup_name");
6512 let file = H5File::create(&path).unwrap();
6513 let _ = file.new_dataset::<i32>().shape([2]).create("d").unwrap();
6514 assert!(file.new_dataset::<i32>().shape([2]).create("d").is_err());
6515 std::fs::remove_file(&path).ok();
6516 }
6517
6518 #[test]
6519 fn extend_cannot_shrink() {
6520 let path = temp_path("extend_shrink");
6521 let file = H5File::create(&path).unwrap();
6522 let ds = file
6523 .new_dataset::<i32>()
6524 .shape([0])
6525 .chunk(&[2])
6526 .max_shape(&[None])
6527 .create("d")
6528 .unwrap();
6529 ds.append(&[1i32, 2, 3, 4]).unwrap();
6530 assert!(ds.extend(&[2]).is_err());
6532 assert!(ds.extend(&[6]).is_ok());
6534 std::fs::remove_file(&path).ok();
6535 }
6536
6537 #[test]
6538 fn attr_read_roundtrip() {
6539 use crate::types::VarLenUnicode;
6540 let path = temp_path("attr_read");
6541
6542 {
6543 let file = H5File::create(&path).unwrap();
6544 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
6545 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
6546 let a1 = ds
6547 .new_attr::<VarLenUnicode>()
6548 .shape(())
6549 .create("units")
6550 .unwrap();
6551 a1.write_string("meters").unwrap();
6552 let a2 = ds
6553 .new_attr::<VarLenUnicode>()
6554 .shape(())
6555 .create("desc")
6556 .unwrap();
6557 a2.write_string("test data").unwrap();
6558 file.close().unwrap();
6559 }
6560 {
6561 let file = H5File::open(&path).unwrap();
6562 let ds = file.dataset("data").unwrap();
6563
6564 let names = ds.attr_names().unwrap();
6565 assert!(names.contains(&"units".to_string()));
6566 assert!(names.contains(&"desc".to_string()));
6567
6568 let units = ds.attr("units").unwrap();
6569 assert_eq!(units.read_string().unwrap(), "meters");
6570
6571 let desc = ds.attr("desc").unwrap();
6572 assert_eq!(desc.read_string().unwrap(), "test data");
6573 }
6574
6575 std::fs::remove_file(&path).ok();
6576 }
6577
6578 #[test]
6579 fn type_mismatch_element_size() {
6580 let path = temp_path("type_mismatch");
6581
6582 {
6583 let file = H5File::create(&path).unwrap();
6584 let ds = file.new_dataset::<f64>().shape([4]).create("data").unwrap();
6585 ds.write_raw(&[1.0f64, 2.0, 3.0, 4.0]).unwrap();
6586 file.close().unwrap();
6587 }
6588
6589 {
6590 let file = H5File::open(&path).unwrap();
6591 let ds = file.dataset("data").unwrap();
6592 let result = ds.read_raw::<u8>();
6594 assert!(result.is_err());
6595 }
6596
6597 std::fs::remove_file(&path).ok();
6598 }
6599
6600 #[test]
6601 fn dataset_survives_file_move() {
6602 let path = temp_path("ds_survives");
6603
6604 let ds = {
6605 let file = H5File::create(&path).unwrap();
6606 file.new_dataset::<u8>().shape([4]).create("x").unwrap()
6607 };
6608 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
6610 std::fs::remove_file(&path).ok();
6613 }
6614
6615 #[test]
6616 fn new_attr_scalar_string() {
6617 use crate::types::VarLenUnicode;
6618
6619 let path = temp_path("attr_scalar_string");
6620 {
6621 let file = H5File::create(&path).unwrap();
6622 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
6623 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
6624
6625 let attr = ds
6626 .new_attr::<VarLenUnicode>()
6627 .shape(())
6628 .create("name")
6629 .unwrap();
6630 attr.write_scalar(&VarLenUnicode("test_value".to_string()))
6631 .unwrap();
6632
6633 file.close().unwrap();
6634 }
6635
6636 {
6638 use crate::format::messages::datatype::DatatypeMessage;
6639 let file = H5File::open(&path).unwrap();
6640 let ds = file.dataset("data").unwrap();
6641 assert_eq!(ds.shape(), vec![4]);
6642 let readback = ds.read_raw::<u8>().unwrap();
6643 assert_eq!(readback, vec![1u8, 2, 3, 4]);
6644
6645 let attr = ds.attr("name").unwrap();
6648 assert!(
6649 matches!(
6650 attr.datatype().unwrap(),
6651 DatatypeMessage::VarLenString { .. }
6652 ),
6653 "string attribute should have a variable-length string datatype"
6654 );
6655 assert_eq!(attr.read_string().unwrap(), "test_value");
6656 }
6657
6658 std::fs::remove_file(&path).ok();
6659 }
6660
6661 #[test]
6662 fn all_numeric_types_roundtrip() {
6663 let path = temp_path("all_types");
6664
6665 {
6666 let file = H5File::create(&path).unwrap();
6667
6668 let ds = file.new_dataset::<u8>().shape([2]).create("u8").unwrap();
6669 ds.write_raw(&[1u8, 2]).unwrap();
6670
6671 let ds = file.new_dataset::<i8>().shape([2]).create("i8").unwrap();
6672 ds.write_raw(&[-1i8, 1]).unwrap();
6673
6674 let ds = file.new_dataset::<u16>().shape([2]).create("u16").unwrap();
6675 ds.write_raw(&[100u16, 200]).unwrap();
6676
6677 let ds = file.new_dataset::<i16>().shape([2]).create("i16").unwrap();
6678 ds.write_raw(&[-100i16, 100]).unwrap();
6679
6680 let ds = file.new_dataset::<u32>().shape([2]).create("u32").unwrap();
6681 ds.write_raw(&[1000u32, 2000]).unwrap();
6682
6683 let ds = file.new_dataset::<i32>().shape([2]).create("i32").unwrap();
6684 ds.write_raw(&[-1000i32, 1000]).unwrap();
6685
6686 let ds = file.new_dataset::<u64>().shape([2]).create("u64").unwrap();
6687 ds.write_raw(&[10000u64, 20000]).unwrap();
6688
6689 let ds = file.new_dataset::<i64>().shape([2]).create("i64").unwrap();
6690 ds.write_raw(&[-10000i64, 10000]).unwrap();
6691
6692 let ds = file.new_dataset::<f32>().shape([2]).create("f32").unwrap();
6693 ds.write_raw(&[1.5f32, 2.5]).unwrap();
6694
6695 let ds = file.new_dataset::<f64>().shape([2]).create("f64").unwrap();
6696 ds.write_raw(&[1.23456f64, 7.89012]).unwrap();
6697
6698 file.close().unwrap();
6699 }
6700
6701 {
6702 let file = H5File::open(&path).unwrap();
6703
6704 assert_eq!(
6705 file.dataset("u8").unwrap().read_raw::<u8>().unwrap(),
6706 vec![1u8, 2]
6707 );
6708 assert_eq!(
6709 file.dataset("i8").unwrap().read_raw::<i8>().unwrap(),
6710 vec![-1i8, 1]
6711 );
6712 assert_eq!(
6713 file.dataset("u16").unwrap().read_raw::<u16>().unwrap(),
6714 vec![100u16, 200]
6715 );
6716 assert_eq!(
6717 file.dataset("i16").unwrap().read_raw::<i16>().unwrap(),
6718 vec![-100i16, 100]
6719 );
6720 assert_eq!(
6721 file.dataset("u32").unwrap().read_raw::<u32>().unwrap(),
6722 vec![1000u32, 2000]
6723 );
6724 assert_eq!(
6725 file.dataset("i32").unwrap().read_raw::<i32>().unwrap(),
6726 vec![-1000i32, 1000]
6727 );
6728 assert_eq!(
6729 file.dataset("u64").unwrap().read_raw::<u64>().unwrap(),
6730 vec![10000u64, 20000]
6731 );
6732 assert_eq!(
6733 file.dataset("i64").unwrap().read_raw::<i64>().unwrap(),
6734 vec![-10000i64, 10000]
6735 );
6736 assert_eq!(
6737 file.dataset("f32").unwrap().read_raw::<f32>().unwrap(),
6738 vec![1.5f32, 2.5]
6739 );
6740 assert_eq!(
6741 file.dataset("f64").unwrap().read_raw::<f64>().unwrap(),
6742 vec![1.23456f64, 7.89012]
6743 );
6744 }
6745
6746 std::fs::remove_file(&path).ok();
6747 }
6748
6749 #[test]
6750 fn append_chunked_roundtrip() {
6751 let path = temp_path("append_chunked");
6752
6753 {
6754 let file = H5File::create(&path).unwrap();
6755 let ds = file
6756 .new_dataset::<f64>()
6757 .shape([0, 3])
6758 .chunk(&[1, 3])
6759 .max_shape(&[None, Some(3)])
6760 .create("data")
6761 .unwrap();
6762
6763 ds.append(&[1.0f64, 2.0, 3.0]).unwrap();
6765 ds.append(&[4.0f64, 5.0, 6.0, 7.0, 8.0, 9.0]).unwrap();
6767
6768 file.close().unwrap();
6769 }
6770
6771 {
6772 let file = H5File::open(&path).unwrap();
6773 let ds = file.dataset("data").unwrap();
6774 assert_eq!(ds.shape(), vec![3, 3]);
6775 let all = ds.read_raw::<f64>().unwrap();
6776 assert_eq!(all, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
6777 }
6778
6779 std::fs::remove_file(&path).ok();
6780 }
6781
6782 #[test]
6783 fn append_1d_chunked() {
6784 let path = temp_path("append_1d");
6785
6786 {
6787 let file = H5File::create(&path).unwrap();
6788 let ds = file
6789 .new_dataset::<i32>()
6790 .shape([0])
6791 .chunk(&[4])
6792 .max_shape(&[None])
6793 .create("values")
6794 .unwrap();
6795
6796 ds.append(&[10i32, 20, 30]).unwrap(); ds.append(&[40i32]).unwrap(); ds.append(&[50i32, 60, 70, 80]).unwrap(); file.close().unwrap();
6801 }
6802
6803 {
6804 let file = H5File::open(&path).unwrap();
6805 let ds = file.dataset("values").unwrap();
6806 assert_eq!(ds.shape(), vec![8]);
6807 let all = ds.read_raw::<i32>().unwrap();
6808 assert_eq!(all, vec![10, 20, 30, 40, 50, 60, 70, 80]);
6809 }
6810
6811 std::fs::remove_file(&path).ok();
6812 }
6813
6814 #[test]
6815 fn append_partial_chunk_flushed_on_close() {
6816 let path = temp_path("append_partial_close");
6817
6818 {
6819 let file = H5File::create(&path).unwrap();
6820 let ds = file
6821 .new_dataset::<f64>()
6822 .shape([0])
6823 .chunk(&[4])
6824 .max_shape(&[None])
6825 .create("vals")
6826 .unwrap();
6827
6828 ds.append(&[1.0f64, 2.0, 3.0, 4.0, 5.0]).unwrap();
6830 file.close().unwrap();
6831 }
6832
6833 {
6834 let file = H5File::open(&path).unwrap();
6835 let ds = file.dataset("vals").unwrap();
6836 assert_eq!(ds.shape(), vec![5]);
6837 let all = ds.read_raw::<f64>().unwrap();
6838 assert_eq!(all.len(), 5);
6841 assert_eq!(all, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
6842 }
6843
6844 std::fs::remove_file(&path).ok();
6845 }
6846
6847 #[test]
6855 fn append_after_reopen_keeps_the_partial_chunk_it_lands_in() {
6856 let path = temp_path("append_reopen_partial");
6857
6858 {
6859 let file = H5File::create(&path).unwrap();
6860 let ds = file
6861 .new_dataset::<i32>()
6862 .shape([0, 3])
6863 .chunk(&[4, 3])
6864 .max_shape(&[None, Some(3)])
6865 .create("values")
6866 .unwrap();
6867 ds.append(&[1, 2, 3]).unwrap();
6868 file.close().unwrap();
6869 }
6870 for rows in [
6871 vec![4, 5, 6],
6872 vec![7, 8, 9, 10, 11, 12, 13, 14, 15],
6873 vec![16, 17, 18],
6874 ] {
6875 let file = H5File::open_rw(&path).unwrap();
6876 file.dataset_writer("values")
6877 .unwrap()
6878 .append(&rows)
6879 .unwrap();
6880 file.close().unwrap();
6881 }
6882
6883 let file = H5File::open(&path).unwrap();
6884 let ds = file.dataset("values").unwrap();
6885 assert_eq!(ds.shape(), vec![6, 3]);
6886 assert_eq!(
6887 ds.read_raw::<i32>().unwrap(),
6888 (1..=18).collect::<Vec<i32>>()
6889 );
6890 std::fs::remove_file(&path).ok();
6891 }
6892
6893 #[cfg(feature = "deflate")]
6894 #[test]
6895 fn vlen_append_after_reopen_filtered() {
6896 let path = temp_path("vlen_reopen_filtered");
6900 {
6901 let file = H5File::create(&path).unwrap();
6902 file.create_appendable_vlen_dataset(
6903 "strs",
6904 4,
6905 Some(crate::format::messages::filter::FilterPipeline::deflate(6)),
6906 )
6907 .unwrap();
6908 file.append_vlen_strings("strs", &["alpha", "beta", "gamma"])
6909 .unwrap();
6910 file.close().unwrap();
6911 }
6912 {
6913 let file = H5File::open_rw(&path).unwrap();
6914 file.append_vlen_strings("strs", &["delta"]).unwrap();
6915 file.close().unwrap();
6916 }
6917 {
6918 let file = H5File::open(&path).unwrap();
6919 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
6920 assert_eq!(
6921 got.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
6922 vec!["alpha", "beta", "gamma", "delta"]
6923 );
6924 }
6925 std::fs::remove_file(&path).ok();
6926 }
6927
6928 #[test]
6929 fn vlen_append_after_reopen_data_block() {
6930 let path = temp_path("vlen_reopen_datablk");
6934 let labels: Vec<String> = (0..9).map(|i| format!("s{i}")).collect();
6935 {
6936 let file = H5File::create(&path).unwrap();
6937 file.create_appendable_vlen_dataset("strs", 2, None)
6938 .unwrap();
6939 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
6940 file.append_vlen_strings("strs", &refs).unwrap();
6941 file.close().unwrap();
6942 }
6943 {
6944 let file = H5File::open_rw(&path).unwrap();
6945 file.append_vlen_strings("strs", &["s9"]).unwrap();
6946 file.close().unwrap();
6947 }
6948 {
6949 let file = H5File::open(&path).unwrap();
6950 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
6951 let want: Vec<String> = (0..10).map(|i| format!("s{i}")).collect();
6952 assert_eq!(got, want);
6953 }
6954 std::fs::remove_file(&path).ok();
6955 }
6956
6957 #[test]
6958 fn vlen_append_after_reopen_super_block() {
6959 let path = temp_path("vlen_reopen_super");
6967 let labels: Vec<String> = (0..489).map(|i| format!("v{i}")).collect();
6970 {
6971 let file = H5File::create(&path).unwrap();
6972 file.create_appendable_vlen_dataset("strs", 2, None)
6973 .unwrap();
6974 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
6975 file.append_vlen_strings("strs", &refs).unwrap();
6976 file.close().unwrap();
6977 }
6978 {
6979 let file = H5File::open_rw(&path).unwrap();
6980 file.append_vlen_strings("strs", &["v489"]).unwrap();
6981 file.close().unwrap();
6982 }
6983 {
6984 let file = H5File::open(&path).unwrap();
6985 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
6986 let want: Vec<String> = (0..490).map(|i| format!("v{i}")).collect();
6987 assert_eq!(got, want);
6988 }
6989 std::fs::remove_file(&path).ok();
6990 }
6991
6992 #[cfg(feature = "deflate")]
6993 #[test]
6994 fn vlen_append_after_reopen_filtered_data_block() {
6995 let path = temp_path("vlen_reopen_filt_datablk");
6998 let labels: Vec<String> = (0..9).map(|i| format!("item{i:02}")).collect();
6999 {
7000 let file = H5File::create(&path).unwrap();
7001 file.create_appendable_vlen_dataset(
7002 "strs",
7003 2,
7004 Some(crate::format::messages::filter::FilterPipeline::deflate(6)),
7005 )
7006 .unwrap();
7007 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
7008 file.append_vlen_strings("strs", &refs).unwrap();
7009 file.close().unwrap();
7010 }
7011 {
7012 let file = H5File::open_rw(&path).unwrap();
7013 file.append_vlen_strings("strs", &["item09"]).unwrap();
7014 file.close().unwrap();
7015 }
7016 {
7017 let file = H5File::open(&path).unwrap();
7018 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
7019 let want: Vec<String> = (0..10).map(|i| format!("item{i:02}")).collect();
7020 assert_eq!(got, want);
7021 }
7022 std::fs::remove_file(&path).ok();
7023 }
7024
7025 #[test]
7026 fn group_nx_class_attribute_roundtrip() {
7027 let path = temp_path("group_nx_class");
7030 {
7031 let file = H5File::create(&path).unwrap();
7032 let entry = file.create_group("entry").unwrap();
7033 entry.set_attr_string("NX_class", "NXentry").unwrap();
7034 let det = entry.create_group("detector").unwrap();
7035 det.set_attr_string("NX_class", "NXdetector").unwrap();
7036 det.set_attr_numeric("frame_count", &7i32).unwrap();
7037 det.new_dataset::<f32>()
7038 .shape([4])
7039 .create("data")
7040 .unwrap()
7041 .write_raw(&[1.0f32; 4])
7042 .unwrap();
7043 file.close().unwrap();
7044 }
7045 {
7046 let file = H5File::open(&path).unwrap();
7047 let entry = file.root_group().group("entry").unwrap();
7048 assert_eq!(entry.attr_string("NX_class").unwrap(), "NXentry");
7049 let det = entry.group("detector").unwrap();
7050 assert_eq!(det.attr_string("NX_class").unwrap(), "NXdetector");
7051 let names = det.attr_names().unwrap();
7052 assert!(names.contains(&"NX_class".to_string()));
7053 assert!(names.contains(&"frame_count".to_string()));
7054 }
7055 std::fs::remove_file(&path).ok();
7056 }
7057
7058 #[test]
7059 fn ea_super_block_roundtrip() {
7060 let path = temp_path("ea_super_rt");
7063 {
7064 let file = H5File::create(&path).unwrap();
7065 let ds = file
7066 .new_dataset::<i32>()
7067 .shape([0])
7068 .chunk(&[1])
7069 .max_shape(&[None])
7070 .create("v")
7071 .unwrap();
7072 ds.append(&(0..2000).collect::<Vec<i32>>()).unwrap();
7073 file.close().unwrap();
7074 }
7075 {
7076 let file = H5File::open(&path).unwrap();
7077 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7078 assert_eq!(v.len(), 2000);
7079 assert!(v.iter().enumerate().all(|(i, &x)| x == i as i32));
7080 }
7081 std::fs::remove_file(&path).ok();
7082 }
7083
7084 #[cfg(feature = "deflate")]
7085 #[test]
7086 fn ea_filtered_super_block_roundtrip() {
7087 let path = temp_path("ea_filt_super");
7089 {
7090 let file = H5File::create(&path).unwrap();
7091 let ds = file
7092 .new_dataset::<i32>()
7093 .shape([0])
7094 .chunk(&[1])
7095 .max_shape(&[None])
7096 .deflate(4)
7097 .create("v")
7098 .unwrap();
7099 ds.append(&(0..600).collect::<Vec<i32>>()).unwrap();
7100 file.close().unwrap();
7101 }
7102 {
7103 let file = H5File::open(&path).unwrap();
7104 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7105 assert_eq!(v, (0..600).collect::<Vec<i32>>());
7106 }
7107 std::fs::remove_file(&path).ok();
7108 }
7109
7110 #[test]
7111 fn ea_super_block_open_append() {
7112 let path = temp_path("ea_super_append");
7114 {
7115 let file = H5File::create(&path).unwrap();
7116 let ds = file
7117 .new_dataset::<i32>()
7118 .shape([0])
7119 .chunk(&[1])
7120 .max_shape(&[None])
7121 .create("v")
7122 .unwrap();
7123 ds.append(&(0..300).collect::<Vec<i32>>()).unwrap();
7124 file.close().unwrap();
7125 }
7126 {
7127 let w = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7128 let idx = w.dataset_index("v").unwrap();
7129 for c in 300..900u64 {
7130 w.write_chunk(idx, c, &(c as i32).to_le_bytes()).unwrap();
7131 }
7132 w.extend_dataset(idx, &[900]).unwrap();
7133 w.close().unwrap();
7134 }
7135 {
7136 let file = H5File::open(&path).unwrap();
7137 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7138 assert_eq!(v.len(), 900);
7139 assert!(v.iter().enumerate().all(|(i, &x)| x == i as i32));
7140 }
7141 std::fs::remove_file(&path).ok();
7142 }
7143
7144 #[cfg(feature = "deflate")]
7150 #[test]
7151 fn compressed_multi_unlimited_dataset_roundtrips() {
7152 let path = temp_path("bt2_filtered");
7153 {
7154 let file = H5File::create(&path).unwrap();
7155 let ds = file
7156 .new_dataset::<i32>()
7157 .shape([6, 8])
7158 .chunk(&[2, 4])
7159 .max_shape(&[None, None])
7160 .deflate(6)
7161 .create("d")
7162 .unwrap();
7163 ds.write_slice(&[0, 0], &[6, 8], &[7i32; 48]).unwrap();
7164 ds.write_slice(&[1, 1], &[2, 2], &[1i32, 2, 3, 4]).unwrap();
7167 file.close().unwrap();
7168 }
7169 {
7170 let file = H5File::open(&path).unwrap();
7171 let ds = file.dataset("d").unwrap();
7172 assert_eq!(ds.shape(), vec![6, 8]);
7173 let mut want = vec![7i32; 48];
7174 want[9] = 1;
7175 want[10] = 2;
7176 want[17] = 3;
7177 want[18] = 4;
7178 assert_eq!(ds.read_raw::<i32>().unwrap(), want);
7179 }
7180 std::fs::remove_file(&path).ok();
7181 }
7182
7183 #[test]
7184 fn btree_v2_multi_unlimited_roundtrip() {
7185 let path = temp_path("bt2_multi");
7188 {
7189 let file = H5File::create(&path).unwrap();
7190 let ds = file
7191 .new_dataset::<i32>()
7192 .shape([0, 0])
7193 .chunk(&[2, 2])
7194 .max_shape(&[None, None])
7195 .create("grid")
7196 .unwrap();
7197 assert!(ds.is_chunked());
7198 for cr in 0..2usize {
7200 for cc in 0..2usize {
7201 let mut bytes = Vec::new();
7202 for i in 0..2usize {
7203 for j in 0..2usize {
7204 let v = ((cr * 2 + i) * 4 + (cc * 2 + j)) as i32;
7205 bytes.extend_from_slice(&v.to_le_bytes());
7206 }
7207 }
7208 ds.write_chunk_at(&[cr, cc], &bytes).unwrap();
7209 }
7210 }
7211 file.close().unwrap();
7212 }
7213 {
7214 let file = H5File::open(&path).unwrap();
7215 let ds = file.dataset("grid").unwrap();
7216 assert_eq!(ds.shape(), vec![4, 4]);
7217 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..16).collect::<Vec<i32>>());
7218 }
7219 std::fs::remove_file(&path).ok();
7220 }
7221
7222 #[test]
7223 fn subframe_chunking_roundtrip() {
7224 let path = temp_path("subframe");
7228 {
7229 let file = H5File::create(&path).unwrap();
7230 let ds = file
7231 .new_dataset::<i32>()
7232 .shape([0, 8, 8])
7233 .chunk(&[1, 4, 4])
7234 .max_shape(&[None, Some(8), Some(8)])
7235 .create("v")
7236 .unwrap();
7237 for f in 0..3usize {
7238 for cr in 0..2usize {
7239 for cc in 0..2usize {
7240 let mut bytes = Vec::new();
7241 for i in 0..4usize {
7242 for j in 0..4usize {
7243 let v = (f * 64 + (cr * 4 + i) * 8 + (cc * 4 + j)) as i32;
7244 bytes.extend_from_slice(&v.to_le_bytes());
7245 }
7246 }
7247 ds.write_chunk_at(&[f, cr, cc], &bytes).unwrap();
7248 }
7249 }
7250 }
7251 file.close().unwrap();
7252 }
7253 {
7254 let file = H5File::open(&path).unwrap();
7255 let ds = file.dataset("v").unwrap();
7256 assert_eq!(ds.shape(), vec![3, 8, 8]);
7257 assert_eq!(
7258 ds.read_raw::<i32>().unwrap(),
7259 (0..192).collect::<Vec<i32>>()
7260 );
7261 }
7262 std::fs::remove_file(&path).ok();
7263 }
7264
7265 #[test]
7266 fn fill_value_contiguous_roundtrip() {
7267 let path = temp_path("fill_value_contig");
7268 {
7269 let file = H5File::create(&path).unwrap();
7270 let ds = file
7271 .new_dataset::<f32>()
7272 .shape([4])
7273 .fill_value(2.5f32)
7274 .create("data")
7275 .unwrap();
7276 ds.write_raw(&[1.0f32, 2.0, 3.0, 4.0]).unwrap();
7277 file.close().unwrap();
7278 }
7279 {
7281 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7282 let idx = writer.dataset_index("data").unwrap();
7283 assert_eq!(
7284 writer.ds(idx).lock().fill_value,
7285 Some(2.5f32.to_le_bytes().to_vec())
7286 );
7287 }
7288 {
7290 let file = H5File::open(&path).unwrap();
7291 let ds = file.dataset("data").unwrap();
7292 assert_eq!(ds.read_raw::<f32>().unwrap(), vec![1.0, 2.0, 3.0, 4.0]);
7293 }
7294 std::fs::remove_file(&path).ok();
7295 }
7296
7297 #[test]
7302 fn early_allocation_writes_the_implicit_index() {
7303 let path = temp_path("implicit_index");
7304 {
7305 let file = H5File::create(&path).unwrap();
7306 let ds = file
7307 .new_dataset::<i32>()
7308 .shape([16])
7309 .chunk(&[4])
7310 .early_allocation()
7311 .create("data")
7312 .unwrap();
7313 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap();
7314 file.close().unwrap();
7315 }
7316 let bytes = std::fs::read(&path).unwrap();
7317 for magic in [b"EAHD", b"EAIB", b"FAHD", b"FADB", b"BTHD", b"TREE"] {
7318 assert!(
7319 !bytes.windows(4).any(|w| w == magic),
7320 "{} appears in a file whose chunk index is supposed to be no \
7321 structure at all",
7322 String::from_utf8_lossy(magic)
7323 );
7324 }
7325 {
7326 let file = H5File::open(&path).unwrap();
7327 let ds = file.dataset("data").unwrap();
7328 assert_eq!(
7329 ds.read_raw::<i32>().unwrap(),
7330 (0..16i32).collect::<Vec<_>>()
7331 );
7332 }
7333 std::fs::remove_file(&path).ok();
7334 }
7335
7336 #[test]
7342 #[cfg(feature = "deflate")]
7343 fn early_allocation_only_picks_implicit_where_libhdf5_does() {
7344 for (which, magic) in [("unlimited", b"EAHD"), ("filtered", b"FAHD")] {
7347 let path = temp_path("implicit_not");
7348 {
7349 let file = H5File::create(&path).unwrap();
7350 let builder = file.new_dataset::<i32>().shape([16]);
7351 let builder = match which {
7352 "unlimited" => builder.chunk(&[4]).max_shape(&[None]),
7353 _ => builder.chunk(&[4]).deflate(6),
7354 };
7355 let ds = builder.early_allocation().create("data").unwrap();
7356 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap();
7357 file.close().unwrap();
7358 }
7359 let bytes = std::fs::read(&path).unwrap();
7360 assert!(
7361 bytes.windows(4).any(|w| w == magic),
7362 "{which}: expected a {} index",
7363 String::from_utf8_lossy(magic)
7364 );
7365 let file = H5File::open(&path).unwrap();
7366 assert_eq!(
7367 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
7368 (0..16i32).collect::<Vec<_>>(),
7369 "{which}"
7370 );
7371 std::fs::remove_file(&path).ok();
7372 }
7373 }
7374
7375 #[test]
7383 fn one_whole_dataset_chunk_writes_the_single_chunk_index() {
7384 for early in [false, true] {
7385 let path = temp_path("single_chunk_index");
7386 {
7387 let file = H5File::create(&path).unwrap();
7388 let builder = file.new_dataset::<i32>().shape([16]).chunk(&[16]);
7389 let builder = if early {
7390 builder.early_allocation()
7391 } else {
7392 builder
7393 };
7394 let ds = builder.create("data").unwrap();
7395 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap();
7396 file.close().unwrap();
7397 }
7398 let bytes = std::fs::read(&path).unwrap();
7399 for magic in [b"EAHD", b"EAIB", b"FAHD", b"FADB", b"BTHD", b"TREE"] {
7400 assert!(
7401 !bytes.windows(4).any(|w| w == magic),
7402 "early={early}: {} appears in a file whose chunk index is \
7403 supposed to be no structure at all",
7404 String::from_utf8_lossy(magic)
7405 );
7406 }
7407 let file = H5File::open(&path).unwrap();
7408 assert_eq!(
7409 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
7410 (0..16i32).collect::<Vec<_>>(),
7411 "early={early}"
7412 );
7413 std::fs::remove_file(&path).ok();
7414 }
7415 }
7416
7417 #[test]
7421 fn implicit_index_fills_every_chunk_at_create() {
7422 let path = temp_path("implicit_fill");
7423 {
7424 let file = H5File::create(&path).unwrap();
7425 let ds = file
7426 .new_dataset::<i32>()
7427 .shape([8])
7428 .chunk(&[4])
7429 .early_allocation()
7430 .fill_value(-3i32)
7431 .create("data")
7432 .unwrap();
7433 let chunk: Vec<u8> = [1i32, 2, 3, 4]
7435 .iter()
7436 .flat_map(|v| v.to_le_bytes())
7437 .collect();
7438 ds.write_chunk(0, &chunk).unwrap();
7439 file.close().unwrap();
7440 }
7441 let file = H5File::open(&path).unwrap();
7442 let ds = file.dataset("data").unwrap();
7443 assert_eq!(
7444 ds.read_raw::<i32>().unwrap(),
7445 vec![1, 2, 3, 4, -3, -3, -3, -3]
7446 );
7447 std::fs::remove_file(&path).ok();
7448 }
7449
7450 #[test]
7455 fn implicit_index_survives_a_reopen() {
7456 let path = temp_path("implicit_reopen");
7457 {
7458 let file = H5File::create(&path).unwrap();
7459 file.new_dataset::<i32>()
7460 .shape([8])
7461 .chunk(&[4])
7462 .early_allocation()
7463 .create("data")
7464 .unwrap()
7465 .write_raw(&[0i32, 1, 2, 3, 4, 5, 6, 7])
7466 .unwrap();
7467 file.close().unwrap();
7468 }
7469 {
7470 let file = H5File::open_rw(&path).unwrap();
7471 let ds = file.dataset_writer("data").unwrap();
7472 let chunk: Vec<u8> = [10i32, 11, 12, 13]
7473 .iter()
7474 .flat_map(|v| v.to_le_bytes())
7475 .collect();
7476 ds.write_chunk(1, &chunk).unwrap();
7477 file.close().unwrap();
7478 }
7479 let file = H5File::open(&path).unwrap();
7480 assert_eq!(
7481 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
7482 vec![0, 1, 2, 3, 10, 11, 12, 13]
7483 );
7484 std::fs::remove_file(&path).ok();
7485 }
7486
7487 #[test]
7488 fn fill_value_chunked_roundtrip() {
7489 let path = temp_path("fill_value_chunked");
7490 {
7491 let file = H5File::create(&path).unwrap();
7492 let ds = file
7493 .new_dataset::<i32>()
7494 .shape([0])
7495 .chunk(&[4])
7496 .max_shape(&[None])
7497 .fill_value(-7i32)
7498 .create("vals")
7499 .unwrap();
7500 ds.append(&[1i32, 2, 3, 4]).unwrap();
7501 file.close().unwrap();
7502 }
7503 {
7504 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7505 let idx = writer.dataset_index("vals").unwrap();
7506 assert_eq!(
7507 writer.ds(idx).lock().fill_value,
7508 Some((-7i32).to_le_bytes().to_vec())
7509 );
7510 }
7511 std::fs::remove_file(&path).ok();
7512 }
7513
7514 #[test]
7515 fn fill_value_bytes_takes_the_stored_type_in_its_declared_order() {
7516 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
7517 let be12 = DatatypeMessage::FixedPoint {
7518 size: 2,
7519 byte_order: ByteOrder::BigEndian,
7520 signed: false,
7521 bit_offset: 0,
7522 bit_precision: 12,
7523 };
7524 let path = temp_path("fill_value_bytes");
7525 {
7526 let file = H5File::create(&path).unwrap();
7527 file.new_dataset::<u16>()
7528 .shape([0])
7529 .chunk(&[4])
7530 .max_shape(&[None])
7531 .datatype(be12.clone())
7532 .fill_value_bytes(&0x0abcu16.to_ne_bytes())
7533 .create("vals")
7534 .unwrap();
7535 let err = file
7536 .new_dataset::<u16>()
7537 .shape([4])
7538 .datatype(be12)
7539 .fill_value_bytes(&[1, 2, 3])
7540 .create("short");
7541 assert!(matches!(err, Err(crate::Hdf5Error::TypeMismatch(_))));
7542 file.close().unwrap();
7543 }
7544 {
7545 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7546 let idx = writer.dataset_index("vals").unwrap();
7547 assert_eq!(writer.ds(idx).lock().fill_value, Some(vec![0x0a, 0xbc]));
7548 assert!(writer.dataset_index("short").is_none());
7549 }
7550 std::fs::remove_file(&path).ok();
7551 }
7552
7553 #[test]
7554 fn fill_value_read_missing_chunks() {
7555 fn i32_bytes(vals: &[i32]) -> Vec<u8> {
7558 vals.iter().flat_map(|v| v.to_le_bytes()).collect()
7559 }
7560 let path = temp_path("fill_value_read_missing");
7561 {
7562 let file = H5File::create(&path).unwrap();
7563 let ds = file
7564 .new_dataset::<i32>()
7565 .shape([0])
7566 .chunk(&[2])
7567 .max_shape(&[None])
7568 .fill_value(-1i32)
7569 .create("vals")
7570 .unwrap();
7571 ds.write_chunk(0, &i32_bytes(&[10, 20])).unwrap();
7573 ds.write_chunk(2, &i32_bytes(&[50, 60])).unwrap();
7574 ds.extend(&[6]).unwrap();
7575 file.close().unwrap();
7576 }
7577 {
7578 let file = H5File::open(&path).unwrap();
7579 let ds = file.dataset("vals").unwrap();
7580 let all = ds.read_raw::<i32>().unwrap();
7581 assert_eq!(all, vec![10, 20, -1, -1, 50, 60]);
7582 }
7583 std::fs::remove_file(&path).ok();
7584 }
7585
7586 #[test]
7587 fn fill_value_partial_chunk_padded_with_fill() {
7588 let path = temp_path("fill_value_partial_pad");
7592 {
7593 let file = H5File::create(&path).unwrap();
7594 let ds = file
7595 .new_dataset::<i32>()
7596 .shape([0])
7597 .chunk(&[4])
7598 .max_shape(&[None])
7599 .fill_value(-9i32)
7600 .create("vals")
7601 .unwrap();
7602 ds.append(&[1i32, 2, 3]).unwrap();
7604 file.close().unwrap();
7605 }
7606 let bytes = std::fs::read(&path).unwrap();
7607 let needle: Vec<u8> = [1i32, 2, 3].iter().flat_map(|v| v.to_le_bytes()).collect();
7609 let pos = bytes
7610 .windows(needle.len())
7611 .position(|w| w == needle)
7612 .expect("chunk data [1,2,3] not found in file");
7613 let pad = &bytes[pos + needle.len()..pos + needle.len() + 4];
7614 assert_eq!(
7615 pad,
7616 &(-9i32).to_le_bytes(),
7617 "partial chunk tail must be padded with fill value -9, got {:?}",
7618 pad
7619 );
7620 std::fs::remove_file(&path).ok();
7621 }
7622
7623 #[test]
7624 fn vlen_append_after_reopen_preserves_existing() {
7625 let path = temp_path("vlen_append_reopen");
7628 {
7629 let file = H5File::create(&path).unwrap();
7630 file.create_appendable_vlen_dataset("strs", 4, None)
7631 .unwrap();
7632 file.append_vlen_strings("strs", &["a", "b", "c"]).unwrap();
7634 file.close().unwrap();
7635 }
7636 {
7637 let file = H5File::open_rw(&path).unwrap();
7639 file.append_vlen_strings("strs", &["d"]).unwrap();
7640 file.close().unwrap();
7641 }
7642 {
7643 let file = H5File::open(&path).unwrap();
7644 let ds = file.dataset("strs").unwrap();
7645 let got = ds.read_vlen_strings().unwrap();
7646 assert_eq!(
7647 got.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
7648 vec!["a", "b", "c", "d"]
7649 );
7650 }
7651 std::fs::remove_file(&path).ok();
7652 }
7653
7654 #[test]
7655 fn fill_value_size_mismatch_errors() {
7656 let path = temp_path("fill_value_mismatch");
7657 let writer = crate::io::writer::Hdf5Writer::create(&path).unwrap();
7658 let dt = <f64 as crate::types::H5Type>::hdf5_type();
7659 let idx = writer.create_dataset("d", dt, &[4u64]).unwrap();
7660 assert!(writer.set_dataset_fill_value(idx, vec![0u8; 4]).is_err());
7662 writer.set_dataset_fill_value(idx, vec![0u8; 8]).unwrap();
7664 writer.close().unwrap();
7665 std::fs::remove_file(&path).ok();
7666 }
7667
7668 #[test]
7669 fn datatype_exposes_class_sign_and_byteorder() {
7670 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
7674
7675 let path = temp_path("datatype_accessor");
7676 {
7677 let file = H5File::create(&path).unwrap();
7678 file.new_dataset::<u8>().shape([3]).create("u8d").unwrap();
7679 file.new_dataset::<i8>().shape([3]).create("i8d").unwrap();
7680 file.new_dataset::<i32>().shape([3]).create("i32d").unwrap();
7681 file.new_dataset::<f32>().shape([3]).create("f32d").unwrap();
7682 file.close().unwrap();
7683 }
7684
7685 let file = H5File::open(&path).unwrap();
7686
7687 match file.dataset("u8d").unwrap().datatype().unwrap() {
7688 DatatypeMessage::FixedPoint {
7689 size,
7690 signed,
7691 byte_order,
7692 ..
7693 } => {
7694 assert_eq!(size, 1);
7695 assert!(!signed, "u8 must be unsigned");
7696 assert_eq!(byte_order, ByteOrder::LittleEndian);
7697 }
7698 other => panic!("expected FixedPoint for u8, got {other:?}"),
7699 }
7700
7701 match file.dataset("i8d").unwrap().datatype().unwrap() {
7702 DatatypeMessage::FixedPoint { size, signed, .. } => {
7703 assert_eq!(size, 1);
7704 assert!(signed, "i8 must be signed");
7705 }
7706 other => panic!("expected FixedPoint for i8, got {other:?}"),
7707 }
7708
7709 match file.dataset("i32d").unwrap().datatype().unwrap() {
7710 DatatypeMessage::FixedPoint { size, signed, .. } => {
7711 assert_eq!(size, 4);
7712 assert!(signed, "i32 must be signed");
7713 }
7714 other => panic!("expected FixedPoint for i32, got {other:?}"),
7715 }
7716
7717 match file.dataset("f32d").unwrap().datatype().unwrap() {
7718 DatatypeMessage::FloatingPoint { size, .. } => assert_eq!(size, 4),
7719 other => panic!("expected FloatingPoint for f32, got {other:?}"),
7720 }
7721
7722 std::fs::remove_file(&path).ok();
7723 }
7724
7725 #[test]
7726 fn datatype_in_write_mode_errors() {
7727 let path = temp_path("datatype_write_mode");
7728 let file = H5File::create(&path).unwrap();
7729 let ds = file.new_dataset::<f32>().shape([4]).create("d").unwrap();
7730 assert!(ds.datatype().is_err());
7731 std::fs::remove_file(&path).ok();
7732 }
7733
7734 #[cfg(feature = "deflate")]
7740 #[test]
7741 fn write_chunk_raw_ea_roundtrip_mask0() {
7742 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7743 let path = temp_path("wcr_ea_mask0");
7744 let original: Vec<i32> = (0..12).collect();
7745 {
7746 let file = H5File::create(&path).unwrap();
7747 let ds = file
7748 .new_dataset::<i32>()
7749 .shape([0])
7750 .chunk(&[4])
7751 .max_shape(&[None])
7752 .deflate(4)
7753 .create("v")
7754 .unwrap();
7755 assert!(ds.is_chunked());
7756 let pipeline = FilterPipeline::deflate(4);
7757 for c in 0..3usize {
7758 let raw: Vec<u8> = original[c * 4..c * 4 + 4]
7759 .iter()
7760 .flat_map(|v| v.to_le_bytes())
7761 .collect();
7762 let compressed = apply_filters(&pipeline, &raw).unwrap();
7763 ds.write_chunk_raw(c, &compressed, 0).unwrap();
7764 }
7765 ds.set_extent(&[12]).unwrap();
7766 file.close().unwrap();
7767 }
7768 {
7769 let file = H5File::open(&path).unwrap();
7770 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7771 assert_eq!(v, original);
7772 }
7773 std::fs::remove_file(&path).ok();
7774 }
7775
7776 #[cfg(feature = "deflate")]
7779 #[test]
7780 fn write_chunk_raw_fixed_array_roundtrip_mask0() {
7781 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7782 let path = temp_path("wcr_fa_mask0");
7783 let original: Vec<i32> = (0..12).collect();
7784 {
7785 let file = H5File::create(&path).unwrap();
7786 let ds = file
7787 .new_dataset::<i32>()
7788 .shape([12])
7789 .chunk(&[4])
7790 .deflate(4)
7791 .create("v")
7792 .unwrap();
7793 assert!(ds.is_chunked());
7794 let pipeline = FilterPipeline::deflate(4);
7795 for c in 0..3usize {
7796 let raw: Vec<u8> = original[c * 4..c * 4 + 4]
7797 .iter()
7798 .flat_map(|v| v.to_le_bytes())
7799 .collect();
7800 let compressed = apply_filters(&pipeline, &raw).unwrap();
7801 ds.write_chunk_raw(c, &compressed, 0).unwrap();
7802 }
7803 file.close().unwrap();
7804 }
7805 {
7806 let file = H5File::open(&path).unwrap();
7807 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7808 assert_eq!(v, original);
7809 }
7810 std::fs::remove_file(&path).ok();
7811 }
7812
7813 #[cfg(feature = "deflate")]
7819 #[test]
7820 fn write_chunk_raw_records_filter_mask() {
7821 let path = temp_path("wcr_records_mask");
7822 let raw: Vec<u8> = [10i32, 20, 30, 40]
7823 .iter()
7824 .flat_map(|v| v.to_le_bytes())
7825 .collect();
7826 assert_eq!(raw.len(), 16);
7827 {
7828 let file = H5File::create(&path).unwrap();
7829 let ds = file
7830 .new_dataset::<i32>()
7831 .shape([0])
7832 .chunk(&[4])
7833 .max_shape(&[None])
7834 .deflate(4)
7835 .create("v")
7836 .unwrap();
7837 ds.write_chunk_raw(0, &raw, 1).unwrap();
7840 ds.set_extent(&[4]).unwrap();
7841 file.close().unwrap();
7842 }
7843 {
7846 let w = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7847 let idx = w.dataset_index("v").unwrap();
7848 let ds = w.ds(idx);
7849 let m = ds.lock();
7850 let entry = &m
7851 .chunked
7852 .as_ref()
7853 .unwrap()
7854 .filt_iblk
7855 .as_ref()
7856 .unwrap()
7857 .elements[0];
7858 assert_eq!(entry.filter_mask, 1, "filter_mask must round-trip to disk");
7859 assert_eq!(entry.nbytes, 16, "uncompressed chunk stored verbatim");
7860 }
7861 std::fs::remove_file(&path).ok();
7862 }
7863
7864 #[cfg(feature = "deflate")]
7875 #[test]
7876 fn a_chunk_that_decodes_short_of_its_image_reads_as_fill() {
7877 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7878 let path = temp_path("short_chunk_image_is_fill");
7879 let pipeline = FilterPipeline::deflate(4);
7880 {
7881 let file = H5File::create(&path).unwrap();
7882 let ds = file
7883 .new_dataset::<i32>()
7884 .shape([0])
7885 .chunk(&[4])
7886 .max_shape(&[None])
7887 .fill_value(-1i32)
7888 .deflate(4)
7889 .create("v")
7890 .unwrap();
7891 let short: Vec<u8> = [7i32, 8].iter().flat_map(|v| v.to_le_bytes()).collect();
7893 ds.write_chunk_raw(0, &apply_filters(&pipeline, &short).unwrap(), 0)
7894 .unwrap();
7895 let whole: Vec<u8> = [9i32, 10, 11, 12]
7897 .iter()
7898 .flat_map(|v| v.to_le_bytes())
7899 .collect();
7900 ds.write_chunk_raw(2, &apply_filters(&pipeline, &whole).unwrap(), 0)
7901 .unwrap();
7902 ds.set_extent(&[12]).unwrap();
7903 file.close().unwrap();
7904 }
7905 {
7906 let file = H5File::open(&path).unwrap();
7907 let ds = file.dataset("v").unwrap();
7908 assert_eq!(
7909 ds.read_raw::<i32>().unwrap(),
7910 vec![-1, -1, -1, -1, -1, -1, -1, -1, 9, 10, 11, 12]
7911 );
7912 assert_eq!(ds.read_slice::<i32>(&[0], &[4]).unwrap(), vec![-1; 4]);
7915 assert_eq!(
7916 ds.read_slice::<i32>(&[8], &[4]).unwrap(),
7917 vec![9, 10, 11, 12]
7918 );
7919 }
7920 std::fs::remove_file(&path).ok();
7921 }
7922
7923 #[cfg(feature = "deflate")]
7929 #[test]
7930 fn a_staged_chunk_carries_only_what_its_stream_decoded() {
7931 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7932 let path = temp_path("short_chunk_image_staged");
7933 let pipeline = FilterPipeline::deflate(4);
7934 {
7935 let file = H5File::create(&path).unwrap();
7936 let ds = file
7937 .new_dataset::<i32>()
7938 .shape([0])
7939 .chunk(&[4])
7940 .max_shape(&[None])
7941 .fill_value(-1i32)
7942 .deflate(4)
7943 .create("v")
7944 .unwrap();
7945 let short: Vec<u8> = [7i32, 8].iter().flat_map(|v| v.to_le_bytes()).collect();
7946 ds.write_chunk_raw(0, &apply_filters(&pipeline, &short).unwrap(), 0)
7947 .unwrap();
7948 ds.set_extent(&[4]).unwrap();
7949 file.close().unwrap();
7950 }
7951 {
7952 let file = H5File::open(&path).unwrap();
7953 let ds = file.dataset("v").unwrap();
7954 assert_eq!(ds.read_slice::<i32>(&[0], &[2]).unwrap(), vec![7, 8]);
7956 assert_eq!(ds.read_slice::<i32>(&[1], &[2]).unwrap(), vec![-1, -1]);
7958 }
7959 std::fs::remove_file(&path).ok();
7960 }
7961
7962 #[cfg(feature = "deflate")]
7963 #[test]
7964 fn write_chunk_raw_ea_per_chunk_mask_roundtrip() {
7965 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7966 let path = temp_path("wcr_ea_per_chunk_mask");
7967 let original: Vec<i32> = (0..8).collect();
7968 let pipeline = FilterPipeline::deflate(4);
7969 {
7970 let file = H5File::create(&path).unwrap();
7971 let ds = file
7972 .new_dataset::<i32>()
7973 .shape([0])
7974 .chunk(&[4])
7975 .max_shape(&[None])
7976 .deflate(4)
7977 .create("v")
7978 .unwrap();
7979 let raw0: Vec<u8> = original[0..4]
7980 .iter()
7981 .flat_map(|v| v.to_le_bytes())
7982 .collect();
7983 ds.write_chunk_raw(0, &apply_filters(&pipeline, &raw0).unwrap(), 0)
7985 .unwrap();
7986 let raw1: Vec<u8> = original[4..8]
7987 .iter()
7988 .flat_map(|v| v.to_le_bytes())
7989 .collect();
7990 ds.write_chunk_raw(1, &raw1, 1).unwrap();
7992 ds.set_extent(&[8]).unwrap();
7993 file.close().unwrap();
7994 }
7995 {
7996 let file = H5File::open(&path).unwrap();
7997 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7998 assert_eq!(v, original);
7999 }
8000 std::fs::remove_file(&path).ok();
8001 }
8002
8003 #[cfg(feature = "deflate")]
8006 #[test]
8007 fn write_chunk_raw_fixed_array_per_chunk_mask_roundtrip() {
8008 use crate::format::messages::filter::{apply_filters, FilterPipeline};
8009 let path = temp_path("wcr_fa_per_chunk_mask");
8010 let original: Vec<i32> = (0..8).collect();
8011 let pipeline = FilterPipeline::deflate(4);
8012 {
8013 let file = H5File::create(&path).unwrap();
8014 let ds = file
8015 .new_dataset::<i32>()
8016 .shape([8])
8017 .chunk(&[4])
8018 .deflate(4)
8019 .create("v")
8020 .unwrap();
8021 let raw0: Vec<u8> = original[0..4]
8022 .iter()
8023 .flat_map(|v| v.to_le_bytes())
8024 .collect();
8025 ds.write_chunk_raw(0, &apply_filters(&pipeline, &raw0).unwrap(), 0)
8026 .unwrap();
8027 let raw1: Vec<u8> = original[4..8]
8028 .iter()
8029 .flat_map(|v| v.to_le_bytes())
8030 .collect();
8031 ds.write_chunk_raw(1, &raw1, 1).unwrap();
8032 file.close().unwrap();
8033 }
8034 {
8035 let file = H5File::open(&path).unwrap();
8036 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
8037 assert_eq!(v, original);
8038 }
8039 std::fs::remove_file(&path).ok();
8040 }
8041
8042 #[test]
8045 fn write_chunk_raw_rejects_unfiltered() {
8046 let path = temp_path("wcr_unfiltered");
8047 let file = H5File::create(&path).unwrap();
8048 let ds = file
8049 .new_dataset::<i32>()
8050 .shape([0])
8051 .chunk(&[4])
8052 .max_shape(&[None])
8053 .create("v")
8054 .unwrap();
8055 let err = ds.write_chunk_raw(0, &[0u8; 16], 0).unwrap_err();
8056 assert!(
8057 err.to_string().contains("filtered dataset"),
8058 "expected a filtered-dataset error, got: {err}"
8059 );
8060 std::fs::remove_file(&path).ok();
8061 }
8062
8063 #[test]
8067 fn write_chunk_raw_sends_btree_v2_to_the_coordinate_form() {
8068 let path = temp_path("wcr_btree2");
8069 let file = H5File::create(&path).unwrap();
8070 let ds = file
8071 .new_dataset::<i32>()
8072 .shape([0, 0])
8073 .chunk(&[2, 2])
8074 .max_shape(&[None, None])
8075 .create("grid")
8076 .unwrap();
8077 let err = ds.write_chunk_raw(0, &[0u8; 16], 0).unwrap_err();
8078 assert!(
8079 err.to_string().contains("write_chunk_raw_at"),
8080 "expected a pointer to the coordinate form, got: {err}"
8081 );
8082 std::fs::remove_file(&path).ok();
8083 }
8084
8085 #[cfg(feature = "deflate")]
8090 #[test]
8091 fn write_chunk_raw_at_round_trips_on_btree_v2() {
8092 use crate::format::messages::filter::{apply_filters, FilterPipeline};
8093
8094 let path = temp_path("wcr_at_btree2");
8095 let raw0: Vec<u8> = (0..4i32).flat_map(|v| v.to_le_bytes()).collect();
8096 let raw1: Vec<u8> = (100..104i32).flat_map(|v| v.to_le_bytes()).collect();
8097 {
8098 let file = H5File::create(&path).unwrap();
8099 let ds = file
8100 .new_dataset::<i32>()
8101 .shape([0, 0])
8102 .chunk(&[2, 2])
8103 .max_shape(&[None, None])
8104 .deflate(6)
8105 .create("grid")
8106 .unwrap();
8107 let pipeline = FilterPipeline::deflate(6);
8108 ds.write_chunk_raw_at(&[0, 0], &apply_filters(&pipeline, &raw0).unwrap(), 0)
8110 .unwrap();
8111 ds.write_chunk_raw_at(&[1, 1], &raw1, 1).unwrap();
8113 file.close().unwrap();
8114 }
8115 let file = H5File::open(&path).unwrap();
8116 let ds = file.dataset("grid").unwrap();
8117 assert_eq!(ds.shape(), vec![4, 4]);
8118 let all = ds.read_raw::<i32>().unwrap();
8119 assert_eq!([all[0], all[1], all[4], all[5]], [0, 1, 2, 3]);
8121 assert_eq!([all[10], all[11], all[14], all[15]], [100, 101, 102, 103]);
8123 drop(file);
8124 std::fs::remove_file(&path).ok();
8125 }
8126
8127 #[cfg(feature = "deflate")]
8130 #[test]
8131 fn write_chunk_raw_at_round_trips_on_the_array_indexes() {
8132 for (label, max_shape) in [
8133 ("wcr_at_ea", Some(vec![None, Some(4usize)])),
8134 ("wcr_at_fa", None),
8135 ] {
8136 let path = temp_path(label);
8137 let raw: Vec<u8> = (0..8i32).flat_map(|v| v.to_le_bytes()).collect();
8138 {
8139 let file = H5File::create(&path).unwrap();
8140 let mut b = file
8141 .new_dataset::<i32>()
8142 .shape([4usize, 4])
8143 .chunk(&[2, 4])
8144 .deflate(6);
8145 if let Some(ref ms) = max_shape {
8146 b = b.max_shape(ms);
8147 }
8148 let ds = b.create("grid").unwrap();
8149 ds.write_chunk_raw_at(&[1, 0], &raw, 1).unwrap();
8151 file.close().unwrap();
8152 }
8153 let file = H5File::open(&path).unwrap();
8154 let ds = file.dataset("grid").unwrap();
8155 let all = ds.read_raw::<i32>().unwrap();
8156 assert_eq!(&all[8..16], &(0..8).collect::<Vec<i32>>()[..], "{label}");
8157 drop(file);
8158 std::fs::remove_file(&path).ok();
8159 }
8160 }
8161
8162 #[cfg(feature = "deflate")]
8166 #[test]
8167 fn write_chunk_raw_at_rejects_an_oversized_btree_v2_chunk() {
8168 let path = temp_path("wcr_at_oversized");
8169 let file = H5File::create(&path).unwrap();
8170 let ds = file
8171 .new_dataset::<i32>()
8172 .shape([0, 0])
8173 .chunk(&[1, 1])
8174 .max_shape(&[None, None])
8175 .deflate(4)
8176 .create("grid")
8177 .unwrap();
8178 let err = ds
8179 .write_chunk_raw_at(&[0, 0], &vec![0u8; 70000], 0)
8180 .unwrap_err();
8181 assert!(
8182 err.to_string().contains("does not fit"),
8183 "expected a chunk-size-field overflow error, got: {err}"
8184 );
8185 std::fs::remove_file(&path).ok();
8186 }
8187
8188 #[test]
8191 fn write_chunk_raw_at_rejects_an_unfiltered_btree_v2() {
8192 let path = temp_path("wcr_at_unfiltered");
8193 let file = H5File::create(&path).unwrap();
8194 let ds = file
8195 .new_dataset::<i32>()
8196 .shape([0, 0])
8197 .chunk(&[2, 2])
8198 .max_shape(&[None, None])
8199 .create("grid")
8200 .unwrap();
8201 let err = ds.write_chunk_raw_at(&[0, 0], &[0u8; 16], 0).unwrap_err();
8202 assert!(
8203 err.to_string().contains("filtered dataset"),
8204 "expected a filtered-dataset error, got: {err}"
8205 );
8206 std::fs::remove_file(&path).ok();
8207 }
8208
8209 #[cfg(feature = "deflate")]
8214 #[test]
8215 fn write_chunk_raw_rejects_oversized_chunk() {
8216 let path = temp_path("wcr_oversized");
8217 let file = H5File::create(&path).unwrap();
8218 let ds = file
8219 .new_dataset::<i32>()
8220 .shape([0])
8221 .chunk(&[1])
8222 .max_shape(&[None])
8223 .deflate(4)
8224 .create("v")
8225 .unwrap();
8226 let err = ds.write_chunk_raw(0, &vec![0u8; 70000], 0).unwrap_err();
8227 assert!(
8228 err.to_string().contains("does not fit"),
8229 "expected a chunk-size-field overflow error, got: {err}"
8230 );
8231 std::fs::remove_file(&path).ok();
8232 }
8233
8234 use crate::format::messages::datatype::{CompoundMember, DatatypeMessage};
8237
8238 fn write_fixed_string_dataset(
8241 path: &std::path::Path,
8242 dt: DatatypeMessage,
8243 width: usize,
8244 elems: &[&[u8]],
8245 compressed: bool,
8246 ) {
8247 let mut raw = Vec::with_capacity(elems.len() * width);
8248 for e in elems {
8249 assert!(e.len() <= width);
8250 raw.extend_from_slice(e);
8251 raw.resize(raw.len() + (width - e.len()), 0);
8252 }
8253 let file = H5File::create(path).unwrap();
8254 let mut b = file.new_dataset::<u8>().datatype(dt).shape([elems.len()]);
8255 if compressed {
8256 b = b.chunk(&[2]).deflate(6);
8257 }
8258 let ds = b.create("labels").unwrap();
8259 ds.write_raw_bytes(&raw).unwrap();
8260 file.close().unwrap();
8261 }
8262
8263 #[test]
8266 fn read_strings_handles_any_fixed_width() {
8267 for width in [4usize, 24, 100] {
8268 let path = temp_path(&format!("fixed_str_{width}"));
8269 write_fixed_string_dataset(
8270 &path,
8271 DatatypeMessage::fixed_string(width as u32),
8272 width,
8273 &[b"ab", b"cde", b""],
8274 false,
8275 );
8276 let file = H5File::open(&path).unwrap();
8277 let got = file.dataset("labels").unwrap().read_strings().unwrap();
8278 assert_eq!(got, vec!["ab", "cde", ""], "width {width}");
8279 std::fs::remove_file(&path).ok();
8280 }
8281 }
8282
8283 #[test]
8293 fn read_strings_honors_every_padding_rule() {
8294 let elem: &[u8] = b"ab\0X\0\0";
8296 for (padding, want) in [(0u8, "ab"), (1, "ab")] {
8297 let path = temp_path(&format!("fixed_pad_{padding}"));
8298 write_fixed_string_dataset(
8299 &path,
8300 DatatypeMessage::FixedString {
8301 size: 6,
8302 padding,
8303 charset: 0,
8304 },
8305 6,
8306 &[elem],
8307 false,
8308 );
8309 let file = H5File::open(&path).unwrap();
8310 let got = file.dataset("labels").unwrap().read_strings().unwrap();
8311 assert_eq!(got, vec![want.to_string()], "padding {padding}");
8312 std::fs::remove_file(&path).ok();
8313 }
8314 let path = temp_path("fixed_pad_2");
8316 write_fixed_string_dataset(
8317 &path,
8318 DatatypeMessage::FixedString {
8319 size: 8,
8320 padding: 2,
8321 charset: 0,
8322 },
8323 8,
8324 &[b"a b "],
8325 false,
8326 );
8327 let file = H5File::open(&path).unwrap();
8328 assert_eq!(
8329 file.dataset("labels").unwrap().read_strings().unwrap(),
8330 vec!["a b".to_string()]
8331 );
8332 std::fs::remove_file(&path).ok();
8333
8334 let path = temp_path("fixed_pad_2_nul");
8336 write_fixed_string_dataset(
8337 &path,
8338 DatatypeMessage::FixedString {
8339 size: 8,
8340 padding: 2,
8341 charset: 0,
8342 },
8343 8,
8344 &[b"a\0b "],
8345 false,
8346 );
8347 let file = H5File::open(&path).unwrap();
8348 assert_eq!(
8349 file.dataset("labels").unwrap().read_strings().unwrap(),
8350 vec!["a\0b".to_string()]
8351 );
8352 std::fs::remove_file(&path).ok();
8353 }
8354
8355 #[test]
8358 fn read_strings_rejects_reserved_datatype_codes() {
8359 for (padding, charset, want) in [(3u8, 0u8, "padding rule 3"), (0, 7, "character set 7")] {
8360 let path = temp_path(&format!("fixed_reserved_{padding}_{charset}"));
8361 write_fixed_string_dataset(
8362 &path,
8363 DatatypeMessage::FixedString {
8364 size: 4,
8365 padding,
8366 charset,
8367 },
8368 4,
8369 &[b"ab"],
8370 false,
8371 );
8372 let file = H5File::open(&path).unwrap();
8373 let err = file
8374 .dataset("labels")
8375 .unwrap()
8376 .read_strings()
8377 .unwrap_err()
8378 .to_string();
8379 assert!(err.contains(want), "got: {err}");
8380 std::fs::remove_file(&path).ok();
8381 }
8382 }
8383
8384 #[test]
8388 fn to_host_byte_order_converts_scalars_and_refuses_composites() {
8389 use crate::dataset::{to_host_byte_order, HOST_BYTE_ORDER};
8390 use crate::format::messages::datatype::ByteOrder;
8391
8392 let foreign = match HOST_BYTE_ORDER {
8393 ByteOrder::LittleEndian => ByteOrder::BigEndian,
8394 ByteOrder::BigEndian => ByteOrder::LittleEndian,
8395 };
8396 let int = |order, size| DatatypeMessage::FixedPoint {
8397 size,
8398 byte_order: order,
8399 signed: false,
8400 bit_offset: 0,
8401 bit_precision: (size * 8) as u16,
8402 };
8403
8404 let mut buf = [1u8, 2, 3, 4, 5, 6, 7, 8];
8406 to_host_byte_order(&mut buf, &int(foreign, 4), 4).unwrap();
8407 assert_eq!(buf, [4, 3, 2, 1, 8, 7, 6, 5]);
8408
8409 let mut buf = [1u8, 2, 3, 4];
8411 to_host_byte_order(&mut buf, &int(HOST_BYTE_ORDER, 4), 4).unwrap();
8412 assert_eq!(buf, [1, 2, 3, 4]);
8413
8414 let mut buf = [1u8, 2, 3, 4];
8416 to_host_byte_order(&mut buf, &int(foreign, 1), 1).unwrap();
8417 assert_eq!(buf, [1, 2, 3, 4]);
8418
8419 let mut buf = [1u8, 2];
8421 let enumeration = DatatypeMessage::Enum {
8422 base: Box::new(int(foreign, 2)),
8423 members: Vec::new(),
8424 };
8425 to_host_byte_order(&mut buf, &enumeration, 2).unwrap();
8426 assert_eq!(buf, [2, 1]);
8427
8428 let mut buf = *b"abcd";
8430 to_host_byte_order(&mut buf, &DatatypeMessage::fixed_string(4), 4).unwrap();
8431 assert_eq!(&buf, b"abcd");
8432
8433 let compound = |order| DatatypeMessage::Compound {
8435 size: 4,
8436 members: vec![CompoundMember {
8437 name: "x".into(),
8438 offset: 0,
8439 datatype: int(order, 4),
8440 }],
8441 };
8442 let mut buf = [1u8, 2, 3, 4];
8443 to_host_byte_order(&mut buf, &compound(HOST_BYTE_ORDER), 4).unwrap();
8444 assert_eq!(buf, [1, 2, 3, 4]);
8445
8446 let mut buf = [1u8, 2, 3, 4];
8448 let err = to_host_byte_order(&mut buf, &compound(foreign), 4)
8449 .expect_err("a foreign-order compound was reinterpreted")
8450 .to_string();
8451 assert!(err.contains("read_raw_bytes"), "got: {err}");
8452 assert_eq!(buf, [1, 2, 3, 4], "the refused image is left alone");
8453 }
8454
8455 #[test]
8459 fn to_stored_byte_order_converts_scalars_and_refuses_composites() {
8460 use crate::dataset::{to_stored_byte_order, FOREIGN_BYTE_ORDER, HOST_BYTE_ORDER};
8461 use std::borrow::Cow;
8462
8463 let int = |order, size| DatatypeMessage::FixedPoint {
8464 size,
8465 byte_order: order,
8466 signed: false,
8467 bit_offset: 0,
8468 bit_precision: (size * 8) as u16,
8469 };
8470
8471 let host = [1u8, 2, 3, 4, 5, 6, 7, 8];
8473 let stored = to_stored_byte_order(&host, &int(FOREIGN_BYTE_ORDER, 4), 4).unwrap();
8474 assert_eq!(&*stored, &[4, 3, 2, 1, 8, 7, 6, 5]);
8475 assert!(matches!(stored, Cow::Owned(_)), "a swap needs its own copy");
8476
8477 let stored = to_stored_byte_order(&host, &int(HOST_BYTE_ORDER, 4), 4).unwrap();
8479 assert!(matches!(stored, Cow::Borrowed(_)), "no copy without a swap");
8480 assert_eq!(&*stored, &host);
8481
8482 let stored = to_stored_byte_order(&host, &int(FOREIGN_BYTE_ORDER, 1), 1).unwrap();
8484 assert_eq!(&*stored, &host);
8485
8486 let enumeration = DatatypeMessage::Enum {
8488 base: Box::new(int(FOREIGN_BYTE_ORDER, 2)),
8489 members: Vec::new(),
8490 };
8491 let stored = to_stored_byte_order(&[1u8, 2], &enumeration, 2).unwrap();
8492 assert_eq!(&*stored, &[2, 1]);
8493
8494 let compound = |order| DatatypeMessage::Compound {
8497 size: 4,
8498 members: vec![CompoundMember {
8499 name: "x".into(),
8500 offset: 0,
8501 datatype: int(order, 4),
8502 }],
8503 };
8504 let stored = to_stored_byte_order(&[1u8, 2, 3, 4], &compound(HOST_BYTE_ORDER), 4).unwrap();
8505 assert_eq!(&*stored, &[1, 2, 3, 4]);
8506 let err = to_stored_byte_order(&[1u8, 2, 3, 4], &compound(FOREIGN_BYTE_ORDER), 4)
8507 .expect_err("a foreign-order compound was written from host bytes")
8508 .to_string();
8509 assert!(err.contains("write_raw_bytes"), "got: {err}");
8510 }
8511
8512 #[test]
8516 fn read_strings_enforces_the_character_set_and_lossy_does_not() {
8517 for (charset, bytes, want) in [
8520 (0u8, b"caf\xe9".as_slice(), "ASCII character set"),
8521 (1, b"a\xff".as_slice(), "not valid UTF-8"),
8522 ] {
8523 let path = temp_path(&format!("fixed_charset_{charset}"));
8524 write_fixed_string_dataset(
8525 &path,
8526 DatatypeMessage::FixedString {
8527 size: 6,
8528 padding: 1,
8529 charset,
8530 },
8531 6,
8532 &[b"ok", bytes],
8533 false,
8534 );
8535 let file = H5File::open(&path).unwrap();
8536 let ds = file.dataset("labels").unwrap();
8537 let err = ds.read_strings().unwrap_err().to_string();
8538 assert!(err.contains(want) && err.contains("string 1"), "got: {err}");
8539 let lossy = ds.read_strings_lossy().unwrap();
8540 assert_eq!(lossy[0], "ok");
8541 assert_eq!(
8542 lossy[1].chars().next().unwrap(),
8543 if charset == 0 { 'c' } else { 'a' }
8544 );
8545 std::fs::remove_file(&path).ok();
8546 }
8547 }
8548
8549 #[test]
8552 fn read_strings_reads_utf8_fixed_strings() {
8553 let path = temp_path("fixed_utf8");
8554 write_fixed_string_dataset(
8555 &path,
8556 DatatypeMessage::fixed_string_utf8(12),
8557 12,
8558 &["héllo".as_bytes(), "안녕".as_bytes()],
8559 false,
8560 );
8561 let file = H5File::open(&path).unwrap();
8562 assert_eq!(
8563 file.dataset("labels").unwrap().read_strings().unwrap(),
8564 vec!["héllo".to_string(), "안녕".to_string()]
8565 );
8566 std::fs::remove_file(&path).ok();
8567 }
8568
8569 #[cfg(feature = "deflate")]
8572 #[test]
8573 fn read_strings_reads_a_compressed_fixed_string_dataset() {
8574 let path = temp_path("fixed_str_deflate");
8575 write_fixed_string_dataset(
8576 &path,
8577 DatatypeMessage::fixed_string(16),
8578 16,
8579 &[b"alpha", b"beta", b"gamma", b"delta", b"epsilon"],
8580 true,
8581 );
8582 let file = H5File::open(&path).unwrap();
8583 assert_eq!(
8584 file.dataset("labels").unwrap().read_strings().unwrap(),
8585 vec!["alpha", "beta", "gamma", "delta", "epsilon"]
8586 );
8587 std::fs::remove_file(&path).ok();
8588 }
8589
8590 #[test]
8593 fn read_strings_also_reads_variable_length_strings() {
8594 let path = temp_path("read_strings_vlen");
8595 {
8596 let file = H5File::create(&path).unwrap();
8597 file.write_vlen_strings("names", &["alpha", "", "안녕"])
8598 .unwrap();
8599 file.close().unwrap();
8600 }
8601 let file = H5File::open(&path).unwrap();
8602 assert_eq!(
8603 file.dataset("names").unwrap().read_strings().unwrap(),
8604 vec!["alpha".to_string(), String::new(), "안녕".to_string()]
8605 );
8606 std::fs::remove_file(&path).ok();
8607 }
8608
8609 #[test]
8614 fn read_strings_rejects_a_zero_width_fixed_string_dataset() {
8615 use crate::format::checksum::checksum_metadata;
8616 use crate::format::object_header::OHDR_SIGNATURE;
8617
8618 let path = temp_path("fixed_str_zero_width");
8619 write_fixed_string_dataset(
8620 &path,
8621 DatatypeMessage::fixed_string(37),
8622 37,
8623 &[b"ab", b"cd"],
8624 false,
8625 );
8626
8627 let mut bytes = std::fs::read(&path).unwrap();
8631 let needle = [0x13u8, 0, 0, 0, 37, 0, 0, 0];
8632 let at = bytes
8633 .windows(needle.len())
8634 .position(|w| w == needle)
8635 .expect("encoded fixed-string datatype message");
8636 assert!(
8637 !bytes[at + 1..].windows(needle.len()).any(|w| w == needle),
8638 "the datatype message pattern is not unique in the file"
8639 );
8640
8641 let ohdr = bytes[..at]
8645 .windows(4)
8646 .rposition(|w| w == OHDR_SIGNATURE)
8647 .expect("enclosing object header");
8648 let cksum_at = (at + needle.len()..bytes.len() - 4)
8649 .find(|&e| {
8650 u32::from_le_bytes(bytes[e..e + 4].try_into().unwrap())
8651 == checksum_metadata(&bytes[ohdr..e])
8652 })
8653 .expect("object header checksum");
8654
8655 bytes[at + 4..at + 8].copy_from_slice(&0u32.to_le_bytes());
8656 let fixed = checksum_metadata(&bytes[ohdr..cksum_at]);
8657 bytes[cksum_at..cksum_at + 4].copy_from_slice(&fixed.to_le_bytes());
8658 std::fs::write(&path, &bytes).unwrap();
8659
8660 let file = H5File::open(&path).unwrap();
8661 let err = file
8662 .dataset("labels")
8663 .unwrap()
8664 .read_strings()
8665 .unwrap_err()
8666 .to_string();
8667 assert!(err.contains("zero width"), "got: {err}");
8668 std::fs::remove_file(&path).ok();
8669 }
8670
8671 #[test]
8673 fn read_strings_rejects_a_non_string_dataset() {
8674 let path = temp_path("read_strings_numeric");
8675 {
8676 let file = H5File::create(&path).unwrap();
8677 let ds = file.new_dataset::<i32>().shape([3]).create("nums").unwrap();
8678 ds.write_raw(&[1i32, 2, 3]).unwrap();
8679 file.close().unwrap();
8680 }
8681 let file = H5File::open(&path).unwrap();
8682 let err = file
8683 .dataset("nums")
8684 .unwrap()
8685 .read_strings()
8686 .unwrap_err()
8687 .to_string();
8688 assert!(err.contains("only for string datasets"), "got: {err}");
8689 std::fs::remove_file(&path).ok();
8690 }
8691
8692 #[test]
8697 fn write_vlen_strings_slice_replaces_one_element() {
8698 let path = temp_path("vlen_slice_contig");
8699 {
8700 let file = H5File::create(&path).unwrap();
8701 file.write_vlen_strings("notes", &["a", "b", "c", "d"])
8702 .unwrap();
8703 file.close().unwrap();
8704 }
8705 {
8706 let file = H5File::open_rw(&path).unwrap();
8707 file.dataset_writer("notes")
8708 .unwrap()
8709 .write_vlen_strings_slice(1, &["replacement"])
8710 .unwrap();
8711 file.close().unwrap();
8712 }
8713 let file = H5File::open(&path).unwrap();
8714 let ds = file.dataset("notes").unwrap();
8715 assert_eq!(ds.shape(), vec![4]);
8716 assert_eq!(
8717 ds.read_vlen_strings().unwrap(),
8718 vec!["a", "replacement", "c", "d"]
8719 );
8720 std::fs::remove_file(&path).ok();
8721 }
8722
8723 #[test]
8726 fn write_vlen_strings_slice_spans_chunks_after_reopen() {
8727 let path = temp_path("vlen_slice_chunked");
8728 {
8729 let file = H5File::create(&path).unwrap();
8730 file.create_appendable_vlen_dataset("notes", 2, None)
8731 .unwrap();
8732 let all: Vec<String> = (0..6).map(|i| format!("v{i}")).collect();
8733 let refs: Vec<&str> = all.iter().map(|s| s.as_str()).collect();
8734 file.append_vlen_strings("notes", &refs).unwrap();
8735 file.close().unwrap();
8736 }
8737 {
8738 let file = H5File::open_rw(&path).unwrap();
8740 file.dataset_writer("notes")
8741 .unwrap()
8742 .write_vlen_strings_slice(1, &["x", "y", "z"])
8743 .unwrap();
8744 file.close().unwrap();
8745 }
8746 let file = H5File::open(&path).unwrap();
8747 let ds = file.dataset("notes").unwrap();
8748 assert_eq!(ds.shape(), vec![6]);
8749 assert_eq!(
8750 ds.read_vlen_strings().unwrap(),
8751 vec!["v0", "x", "y", "z", "v4", "v5"]
8752 );
8753 std::fs::remove_file(&path).ok();
8754 }
8755
8756 #[test]
8760 fn write_vlen_strings_slice_updates_buffered_elements() {
8761 let path = temp_path("vlen_slice_buffered");
8762 {
8763 let file = H5File::create(&path).unwrap();
8764 file.create_appendable_vlen_dataset("notes", 4, None)
8765 .unwrap();
8766 file.append_vlen_strings("notes", &["a", "b", "c"]).unwrap();
8768 file.dataset_writer("notes")
8769 .unwrap()
8770 .write_vlen_strings_slice(1, &["patched"])
8771 .unwrap();
8772 file.close().unwrap();
8773 }
8774 let file = H5File::open(&path).unwrap();
8775 assert_eq!(
8776 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8777 vec!["a", "patched", "c"]
8778 );
8779 std::fs::remove_file(&path).ok();
8780 }
8781
8782 #[test]
8786 fn write_vlen_strings_slice_checks_its_range() {
8787 let build = |name: &str, empty_call: bool| {
8788 let path = temp_path(name);
8789 let file = H5File::create(&path).unwrap();
8790 file.write_vlen_strings("notes", &["a", "b"]).unwrap();
8791 let ds = file.dataset_writer("notes").unwrap();
8792 let err = ds
8793 .write_vlen_strings_slice(1, &["x", "y"])
8794 .unwrap_err()
8795 .to_string();
8796 assert!(
8797 err.contains("outside the dataset's 2 elements"),
8798 "got: {err}"
8799 );
8800 if empty_call {
8801 ds.write_vlen_strings_slice(0, &[]).unwrap();
8802 }
8803 file.close().unwrap();
8804 path
8805 };
8806
8807 let with_empty = build("vlen_slice_range", true);
8808 let control = build("vlen_slice_range_control", false);
8809 assert_eq!(
8810 std::fs::metadata(&with_empty).unwrap().len(),
8811 std::fs::metadata(&control).unwrap().len(),
8812 "the rejected and empty calls must leave the file untouched"
8813 );
8814
8815 let file = H5File::open(&with_empty).unwrap();
8816 assert_eq!(
8817 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8818 vec!["a", "b"]
8819 );
8820 std::fs::remove_file(&with_empty).ok();
8821 std::fs::remove_file(&control).ok();
8822 }
8823
8824 #[test]
8827 fn write_vlen_strings_slice_rejects_a_multidimensional_dataset() {
8828 let path = temp_path("vlen_slice_2d");
8829 let file = H5File::create(&path).unwrap();
8830 let ds = file
8831 .new_dataset::<u8>()
8832 .datatype(DatatypeMessage::vlen_string_utf8())
8833 .shape([2, 3])
8834 .create("grid")
8835 .unwrap();
8836 let err = ds
8837 .write_vlen_strings_slice(0, &["x"])
8838 .unwrap_err()
8839 .to_string();
8840 assert!(err.contains("1-dimension datasets"), "got: {err}");
8841 file.close().unwrap();
8842 std::fs::remove_file(&path).ok();
8843 }
8844
8845 #[test]
8848 fn write_vlen_strings_slice_enforces_the_ascii_character_set() {
8849 let path = temp_path("vlen_slice_ascii");
8850 let file = H5File::create(&path).unwrap();
8851 let ds = file
8852 .new_dataset::<u8>()
8853 .datatype(DatatypeMessage::vlen_string_ascii())
8854 .shape([3])
8855 .create("notes")
8856 .unwrap();
8857 let err = ds
8858 .write_vlen_strings_slice(0, &["ok", "안녕"])
8859 .unwrap_err()
8860 .to_string();
8861 assert!(
8862 err.contains("string 1") && err.contains("is not ASCII"),
8863 "got: {err}"
8864 );
8865 ds.write_vlen_strings_slice(0, &["ok", "fine"]).unwrap();
8866 file.close().unwrap();
8867 std::fs::remove_file(&path).ok();
8868 }
8869
8870 #[test]
8873 fn write_vlen_strings_slice_rejects_a_non_vlen_dataset() {
8874 let path = temp_path("vlen_slice_numeric");
8875 let file = H5File::create(&path).unwrap();
8876 let ds = file.new_dataset::<i32>().shape([3]).create("nums").unwrap();
8877 ds.write_raw(&[1i32, 2, 3]).unwrap();
8878 let err = ds
8879 .write_vlen_strings_slice(0, &["x"])
8880 .unwrap_err()
8881 .to_string();
8882 assert!(
8883 err.contains("only for variable-length string datasets"),
8884 "got: {err}"
8885 );
8886 file.close().unwrap();
8887 std::fs::remove_file(&path).ok();
8888 }
8889
8890 #[test]
8897 fn write_vlen_strings_slice_reuses_the_freed_heap_block() {
8898 let size_after = |updates: usize| {
8899 let path = temp_path(&format!("vlen_slice_heap_reuse_{updates}"));
8900 let file = H5File::create(&path).unwrap();
8901 file.write_vlen_strings("notes", &["a", "b"]).unwrap();
8902 let ds = file.dataset_writer("notes").unwrap();
8903 for i in 0..updates {
8904 ds.write_vlen_strings_slice(0, &[&format!("update {i}")])
8905 .unwrap();
8906 }
8907 file.close().unwrap();
8908 let n = std::fs::metadata(&path).unwrap().len();
8909 let read = H5File::open(&path).unwrap();
8910 assert_eq!(
8911 read.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8912 vec![format!("update {}", updates - 1), "b".to_string()]
8913 );
8914 drop(read);
8915 std::fs::remove_file(&path).ok();
8916 n
8917 };
8918
8919 let settled = size_after(3);
8922 assert_eq!(size_after(20), settled, "20 updates against 3");
8923 assert_eq!(size_after(50), settled, "50 updates against 3");
8924 }
8925
8926 #[test]
8930 fn write_vlen_strings_slice_frees_an_empty_strings_object() {
8931 let size_after = |updates: usize| {
8932 let path = temp_path(&format!("vlen_slice_empty_reuse_{updates}"));
8933 let file = H5File::create(&path).unwrap();
8934 file.write_vlen_strings("notes", &["", "b"]).unwrap();
8935 let ds = file.dataset_writer("notes").unwrap();
8936 for _ in 0..updates {
8937 ds.write_vlen_strings_slice(0, &[""]).unwrap();
8938 }
8939 file.close().unwrap();
8940 let n = std::fs::metadata(&path).unwrap().len();
8941 let read = H5File::open(&path).unwrap();
8942 assert_eq!(
8943 read.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8944 vec!["".to_string(), "b".to_string()]
8945 );
8946 drop(read);
8947 std::fs::remove_file(&path).ok();
8948 n
8949 };
8950
8951 let settled = size_after(3);
8952 assert_eq!(size_after(20), settled, "20 empty updates against 3");
8953 assert_eq!(size_after(50), settled, "50 empty updates against 3");
8954 }
8955
8956 #[test]
8959 fn write_vlen_strings_slice_keeps_the_untouched_strings_readable() {
8960 let path = temp_path("vlen_slice_heap_survivors");
8961 {
8962 let file = H5File::create(&path).unwrap();
8963 file.write_vlen_strings("notes", &["a", "b", "c", "d"])
8964 .unwrap();
8965 let ds = file.dataset_writer("notes").unwrap();
8966 ds.write_vlen_strings_slice(1, &["B"]).unwrap();
8969 ds.write_vlen_strings_slice(3, &["D"]).unwrap();
8970 file.close().unwrap();
8971 }
8972 let file = H5File::open(&path).unwrap();
8973 assert_eq!(
8974 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8975 vec!["a", "B", "c", "D"]
8976 );
8977 std::fs::remove_file(&path).ok();
8978 }
8979
8980 #[test]
8984 fn write_vlen_strings_slice_frees_an_emptied_collection() {
8985 let path = temp_path("vlen_slice_heap_emptied");
8986 {
8987 let file = H5File::create(&path).unwrap();
8988 file.create_appendable_vlen_dataset("notes", 2, None)
8989 .unwrap();
8990 file.append_vlen_strings("notes", &["p", "q", "r", "s"])
8991 .unwrap();
8992 file.close().unwrap();
8993 }
8994 {
8995 let file = H5File::open_rw(&path).unwrap();
8996 file.dataset_writer("notes")
8997 .unwrap()
8998 .write_vlen_strings_slice(0, &["w", "x", "y", "z"])
8999 .unwrap();
9000 file.close().unwrap();
9001 }
9002 let file = H5File::open(&path).unwrap();
9003 assert_eq!(
9004 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
9005 vec!["w", "x", "y", "z"]
9006 );
9007 std::fs::remove_file(&path).ok();
9008 }
9009
9010 #[test]
9016 fn write_vlen_strings_slice_keeps_an_oversized_collections_block_whole() {
9017 let big = |tag: char| std::iter::repeat_n(tag, 2000).collect::<String>();
9018 let size_after = |cycles: usize| {
9019 let path = temp_path(&format!("vlen_slice_heap_big_{cycles}"));
9020 let file = H5File::create(&path).unwrap();
9021 let seed: Vec<String> = "abcd".chars().map(big).collect();
9022 let refs: Vec<&str> = seed.iter().map(|s| s.as_str()).collect();
9023 file.write_vlen_strings("notes", &refs).unwrap();
9026 let ds = file.dataset_writer("notes").unwrap();
9027 for _ in 0..cycles {
9028 let head = big('x');
9031 ds.write_vlen_strings_slice(0, &[&head]).unwrap();
9032 let tail: Vec<String> = "yzw".chars().map(big).collect();
9033 let tail_refs: Vec<&str> = tail.iter().map(|s| s.as_str()).collect();
9034 ds.write_vlen_strings_slice(1, &tail_refs).unwrap();
9035 }
9036 file.close().unwrap();
9037 let n = std::fs::metadata(&path).unwrap().len();
9038 let read = H5File::open(&path).unwrap();
9039 assert_eq!(
9040 read.dataset("notes").unwrap().read_vlen_strings().unwrap(),
9041 vec![big('x'), big('y'), big('z'), big('w')]
9042 );
9043 drop(read);
9044 std::fs::remove_file(&path).ok();
9045 n
9046 };
9047
9048 let settled = size_after(4);
9049 assert_eq!(size_after(30), settled, "30 cycles against 4");
9050 }
9051
9052 #[test]
9055 fn write_vlen_strings_slice_releases_a_buffered_elements_object() {
9056 let path = temp_path("vlen_slice_heap_buffered");
9057 let file = H5File::create(&path).unwrap();
9058 file.create_appendable_vlen_dataset("notes", 4, None)
9059 .unwrap();
9060 file.append_vlen_strings("notes", &["a", "b", "c"]).unwrap();
9061 let ds = file.dataset_writer("notes").unwrap();
9062 for i in 0..20 {
9063 ds.write_vlen_strings_slice(1, &[&format!("patch {i}")])
9064 .unwrap();
9065 }
9066 file.close().unwrap();
9067
9068 let file = H5File::open(&path).unwrap();
9069 assert_eq!(
9070 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
9071 vec!["a", "patch 19", "c"]
9072 );
9073 let size = std::fs::metadata(&path).unwrap().len();
9074 std::fs::remove_file(&path).ok();
9075 assert!(
9076 size < 20 * 4096,
9077 "20 buffered updates left {size} bytes, one collection per update"
9078 );
9079 }
9080
9081 #[test]
9086 fn write_slice_into_the_buffered_tail_survives_close() {
9087 let path = temp_path("slice_into_buffered_tail");
9088 {
9089 let file = H5File::create(&path).unwrap();
9090 let ds = file
9091 .new_dataset::<i32>()
9092 .shape([0])
9093 .chunk(&[4])
9094 .max_shape(&[None])
9095 .create("d")
9096 .unwrap();
9097 ds.append(&[10, 11, 12, 13, 50, 51]).unwrap();
9099 ds.write_slice(&[4], &[1], &[99]).unwrap();
9100 file.close().unwrap();
9101 }
9102 {
9103 let file = H5File::open(&path).unwrap();
9104 let ds = file.dataset("d").unwrap();
9105 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![10, 11, 12, 13, 99, 51]);
9106 }
9107 std::fs::remove_file(&path).ok();
9108 }
9109
9110 #[test]
9115 fn extend_does_not_move_buffered_appends() {
9116 let path = temp_path("extend_keeps_buffered_rows");
9117 {
9118 let file = H5File::create(&path).unwrap();
9119 let ds = file
9120 .new_dataset::<i32>()
9121 .shape([0])
9122 .chunk(&[4])
9123 .max_shape(&[None])
9124 .create("d")
9125 .unwrap();
9126 ds.append(&[10, 11, 12, 13, 50, 51]).unwrap(); ds.extend(&[10]).unwrap();
9128 file.close().unwrap();
9129 }
9130 {
9131 let file = H5File::open(&path).unwrap();
9132 let ds = file.dataset("d").unwrap();
9133 assert_eq!(
9134 ds.read_raw::<i32>().unwrap(),
9135 vec![10, 11, 12, 13, 50, 51, 0, 0, 0, 0]
9136 );
9137 }
9138 std::fs::remove_file(&path).ok();
9139 }
9140
9141 #[test]
9147 fn append_to_a_btree_v2_dataset_survives_close() {
9148 let path = temp_path("append_bt2_close");
9149 {
9150 let file = H5File::create(&path).unwrap();
9151 let ds = file
9152 .new_dataset::<i32>()
9153 .shape([0, 3])
9154 .chunk(&[4, 3])
9155 .max_shape(&[None, None])
9156 .create("d")
9157 .unwrap();
9158 ds.append(&[1, 2, 3]).unwrap();
9162 ds.append(&(4..=15).collect::<Vec<i32>>()).unwrap();
9163 file.close().unwrap();
9164 }
9165 {
9166 let file = H5File::open(&path).unwrap();
9167 let ds = file.dataset("d").unwrap();
9168 assert_eq!(ds.shape(), vec![5, 3]);
9169 assert_eq!(
9170 ds.read_raw::<i32>().unwrap(),
9171 (1..=15).collect::<Vec<i32>>()
9172 );
9173 }
9174 std::fs::remove_file(&path).ok();
9175 }
9176
9177 #[test]
9181 fn append_scatters_frames_across_narrow_chunk_tiles() {
9182 let path = temp_path("append_narrow_chunks");
9183 {
9184 let file = H5File::create(&path).unwrap();
9185 let ds = file
9186 .new_dataset::<i32>()
9187 .shape([0, 8])
9188 .chunk(&[2, 4])
9189 .max_shape(&[None, Some(8)])
9190 .create("d")
9191 .unwrap();
9192 ds.append(&(0..24).collect::<Vec<i32>>()).unwrap();
9195 file.close().unwrap();
9196 }
9197 {
9198 let file = H5File::open(&path).unwrap();
9199 let ds = file.dataset("d").unwrap();
9200 assert_eq!(ds.shape(), vec![3, 8]);
9201 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..24).collect::<Vec<i32>>());
9202 }
9203 std::fs::remove_file(&path).ok();
9204 }
9205
9206 #[test]
9210 fn append_to_a_full_fixed_array_dataset_errors() {
9211 let path = temp_path("append_fa_errors");
9212 let file = H5File::create(&path).unwrap();
9213 let ds = file
9214 .new_dataset::<i32>()
9215 .shape([4, 3])
9216 .chunk(&[2, 3])
9217 .create("d")
9218 .unwrap();
9219 let err = ds.append(&(0..6).collect::<Vec<i32>>()).unwrap_err();
9220 assert!(
9221 err.to_string().contains("chunk grid"),
9222 "unexpected error: {err}"
9223 );
9224 file.close().unwrap();
9225 std::fs::remove_file(&path).ok();
9226 }
9227
9228 #[test]
9234 fn fixed_array_with_a_larger_max_shape_grows_and_survives_close() {
9235 let path = temp_path("fa_growable_dim0");
9236 {
9237 let file = H5File::create(&path).unwrap();
9238 let ds = file
9239 .new_dataset::<i32>()
9240 .shape([4, 3])
9241 .chunk(&[2, 3])
9242 .max_shape(&[Some(10), Some(3)])
9243 .create("d")
9244 .unwrap();
9245 ds.write_raw(&(0..12).collect::<Vec<i32>>()).unwrap();
9246 ds.append(&(12..18).collect::<Vec<i32>>()).unwrap();
9247 file.close().unwrap();
9248 }
9249 {
9250 let file = H5File::open(&path).unwrap();
9251 let ds = file.dataset("d").unwrap();
9252 assert_eq!(ds.shape(), vec![6, 3]);
9253 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..18).collect::<Vec<i32>>());
9254 }
9255 std::fs::remove_file(&path).ok();
9256 }
9257
9258 #[test]
9264 fn fixed_array_growable_inner_dimension_keeps_chunk_slots() {
9265 let path = temp_path("fa_growable_dim1");
9266 {
9267 let file = H5File::create(&path).unwrap();
9268 let ds = file
9269 .new_dataset::<i32>()
9270 .shape([4, 3])
9271 .chunk(&[2, 3])
9272 .max_shape(&[Some(4), Some(9)])
9273 .create("d")
9274 .unwrap();
9275 ds.write_raw(&(0..12).collect::<Vec<i32>>()).unwrap();
9276 ds.extend(&[4, 6]).unwrap();
9277 ds.write_slice(&[0, 3], &[4, 3], &(12..24).collect::<Vec<i32>>())
9278 .unwrap();
9279 file.close().unwrap();
9280 }
9281 {
9282 let file = H5File::open(&path).unwrap();
9283 let ds = file.dataset("d").unwrap();
9284 assert_eq!(ds.shape(), vec![4, 6]);
9285 let mut expect = Vec::new();
9288 for r in 0i32..4 {
9289 expect.extend((r * 3)..(r * 3 + 3));
9290 expect.extend((12 + r * 3)..(12 + r * 3 + 3));
9291 }
9292 assert_eq!(ds.read_raw::<i32>().unwrap(), expect);
9293 }
9294 std::fs::remove_file(&path).ok();
9295 }
9296
9297 #[test]
9301 fn extend_beyond_the_maximum_is_rejected() {
9302 let path = temp_path("extend_beyond_max");
9303 let file = H5File::create(&path).unwrap();
9304 let ds = file
9305 .new_dataset::<i32>()
9306 .shape([4, 3])
9307 .chunk(&[2, 3])
9308 .max_shape(&[Some(6), Some(3)])
9309 .create("d")
9310 .unwrap();
9311 ds.extend(&[6, 3]).unwrap();
9312 let err = ds.extend(&[8, 3]).unwrap_err();
9313 assert!(
9314 err.to_string().contains("exceeds the maximum"),
9315 "unexpected error: {err}"
9316 );
9317 file.close().unwrap();
9318 std::fs::remove_file(&path).ok();
9319 }
9320
9321 #[test]
9327 fn builder_accepts_an_unlimited_inner_dimension() {
9328 let path = temp_path("unlimited_inner_dim");
9329 let file = H5File::create(&path).unwrap();
9330 let ds = file
9331 .new_dataset::<i32>()
9332 .shape([4, 0])
9333 .chunk(&[2, 2])
9334 .max_shape(&[Some(4), None])
9335 .create("d")
9336 .unwrap();
9337 assert_eq!(ds.shape(), vec![4, 0]);
9338
9339 ds.extend(&[4, 2]).unwrap();
9344 ds.write_slice(&[0, 0], &[4, 2], &[1, 2, 3, 4, 5, 6, 7, 8])
9345 .unwrap();
9346 ds.extend(&[4, 4]).unwrap();
9347 ds.write_slice(&[0, 2], &[4, 2], &[9, 10, 11, 12, 13, 14, 15, 16])
9348 .unwrap();
9349
9350 file.close().unwrap();
9351 let file = H5File::open(&path).unwrap();
9352 let ds = file.dataset("d").unwrap();
9353 assert_eq!(
9354 ds.read_slice::<i32>(&[0, 0], &[4, 4]).unwrap(),
9355 vec![1, 2, 9, 10, 3, 4, 11, 12, 5, 6, 13, 14, 7, 8, 15, 16]
9356 );
9357 std::fs::remove_file(&path).ok();
9358 }
9359
9360 #[test]
9365 fn builder_rejects_a_chunk_wider_than_a_fixed_max_dimension() {
9366 let path = temp_path("builder_chunk_wider_than_max");
9367 let file = H5File::create(&path).unwrap();
9368 let err = match file
9369 .new_dataset::<i32>()
9370 .shape([0, 2])
9371 .chunk(&[2, 4])
9372 .max_shape(&[None, Some(2)])
9373 .create("v5")
9374 {
9375 Ok(_) => panic!("create accepted a chunk wider than the fixed max dimension"),
9376 Err(e) => e,
9377 };
9378 assert!(
9379 err.to_string().contains("maximum dimension size"),
9380 "unexpected error: {err}"
9381 );
9382 file.close().unwrap();
9383 std::fs::remove_file(&path).ok();
9384 }
9385
9386 #[test]
9389 fn numeric_int_checked_conversion_boundaries() {
9390 let path = temp_path("numeric_int_bounds");
9391 {
9392 let file = H5File::create(&path).unwrap();
9393 let ds = file.new_dataset::<u64>().shape([2]).create("u").unwrap();
9394 ds.write_raw(&[1u64, u64::MAX]).unwrap();
9395 let ds = file.new_dataset::<i32>().shape([2]).create("i").unwrap();
9396 ds.write_raw(&[-1i32, 5]).unwrap();
9397 file.close().unwrap();
9398 }
9399 let file = H5File::open(&path).unwrap();
9400
9401 let u = file.dataset("u").unwrap();
9402 assert_eq!(u.read_numeric_as::<u64>().unwrap(), vec![1, u64::MAX]);
9403 assert_eq!(
9404 u.read_numeric_as::<i128>().unwrap(),
9405 vec![1, i128::from(u64::MAX)]
9406 );
9407 let err = u.read_numeric_as::<i64>().unwrap_err();
9408 assert!(
9409 err.to_string()
9410 .contains("value 18446744073709551615 at element 1 does not fit in i64"),
9411 "unexpected error: {err}"
9412 );
9413
9414 let i = file.dataset("i").unwrap();
9415 assert_eq!(i.read_numeric_as::<i64>().unwrap(), vec![-1, 5]);
9416 let err = i.read_numeric_as::<u32>().unwrap_err();
9417 assert!(
9418 err.to_string()
9419 .contains("value -1 at element 0 does not fit in u32"),
9420 "unexpected error: {err}"
9421 );
9422 std::fs::remove_file(&path).ok();
9423 }
9424
9425 #[test]
9427 fn numeric_float_widening_only() {
9428 let path = temp_path("numeric_float");
9429 {
9430 let file = H5File::create(&path).unwrap();
9431 let ds = file.new_dataset::<f32>().shape([2]).create("f4").unwrap();
9432 ds.write_raw(&[1.5f32, -2.25]).unwrap();
9433 let ds = file.new_dataset::<f64>().shape([1]).create("f8").unwrap();
9434 ds.write_raw(&[3.75f64]).unwrap();
9435 file.close().unwrap();
9436 }
9437 let file = H5File::open(&path).unwrap();
9438
9439 let f4 = file.dataset("f4").unwrap();
9440 assert_eq!(f4.read_numeric_as::<f32>().unwrap(), vec![1.5, -2.25]);
9441 assert_eq!(f4.read_numeric_as::<f64>().unwrap(), vec![1.5, -2.25]);
9442
9443 let f8 = file.dataset("f8").unwrap();
9444 assert_eq!(f8.read_numeric_as::<f64>().unwrap(), vec![3.75]);
9445 let err = f8.read_numeric_as::<f32>().unwrap_err();
9446 assert!(
9447 err.to_string().contains("narrowing"),
9448 "unexpected error: {err}"
9449 );
9450 std::fs::remove_file(&path).ok();
9451 }
9452
9453 #[test]
9456 fn numeric_cross_class_and_non_numeric_rejected() {
9457 let path = temp_path("numeric_cross_class");
9458 {
9459 let file = H5File::create(&path).unwrap();
9460 let ds = file.new_dataset::<f64>().shape([1]).create("f8").unwrap();
9461 ds.write_raw(&[1.0f64]).unwrap();
9462 let ds = file.new_dataset::<i32>().shape([1]).create("i4").unwrap();
9463 ds.write_raw(&[7i32]).unwrap();
9464 file.write_vlen_strings("s", &["a", "b"]).unwrap();
9465 file.close().unwrap();
9466 }
9467 let file = H5File::open(&path).unwrap();
9468
9469 let err = file
9470 .dataset("f8")
9471 .unwrap()
9472 .read_numeric_as::<i64>()
9473 .unwrap_err();
9474 assert!(
9475 err.to_string().contains("floating-point dataset as i64"),
9476 "unexpected error: {err}"
9477 );
9478 let err = file
9479 .dataset("i4")
9480 .unwrap()
9481 .read_numeric_as::<f64>()
9482 .unwrap_err();
9483 assert!(
9484 err.to_string().contains("integer dataset as f64"),
9485 "unexpected error: {err}"
9486 );
9487 let err = file
9488 .dataset("s")
9489 .unwrap()
9490 .read_numeric_as::<i64>()
9491 .unwrap_err();
9492 assert!(
9493 err.to_string().contains("is not numeric"),
9494 "unexpected error: {err}"
9495 );
9496 std::fs::remove_file(&path).ok();
9497 }
9498
9499 #[test]
9503 fn numeric_big_endian_decode() {
9504 use super::numeric;
9505 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
9506
9507 let dt = DatatypeMessage::FixedPoint {
9508 size: 4,
9509 byte_order: ByteOrder::BigEndian,
9510 signed: true,
9511 bit_offset: 0,
9512 bit_precision: 32,
9513 };
9514 let mut raw = Vec::new();
9515 raw.extend_from_slice(&(-2i32).to_be_bytes());
9516 raw.extend_from_slice(&(100_000i32).to_be_bytes());
9517 let kind = numeric::classify(&dt).unwrap();
9518 assert_eq!(
9519 numeric::convert::<i64>(kind, &raw).unwrap(),
9520 vec![-2, 100_000]
9521 );
9522
9523 let dt = DatatypeMessage::FloatingPoint {
9524 size: 8,
9525 byte_order: ByteOrder::BigEndian,
9526 sign_location: 63,
9527 bit_offset: 0,
9528 bit_precision: 64,
9529 exponent_location: 52,
9530 exponent_size: 11,
9531 mantissa_location: 0,
9532 mantissa_size: 52,
9533 exponent_bias: 1023,
9534 };
9535 let raw = (-2.25f64).to_be_bytes();
9536 let kind = numeric::classify(&dt).unwrap();
9537 assert_eq!(numeric::convert::<f64>(kind, &raw).unwrap(), vec![-2.25]);
9538 }
9539
9540 #[test]
9545 fn attr_read_numeric_validates_datatype() {
9546 use crate::types::{Complex64, HBool, VarLenUnicode};
9547 let path = temp_path("attr_read_numeric_validate");
9548 {
9549 let file = H5File::create(&path).unwrap();
9550 let ds = file.new_dataset::<f32>().shape([2]).create("d").unwrap();
9551 ds.write_raw(&[1.0f32; 2]).unwrap();
9552 let a = ds.new_attr::<f64>().shape(()).create("f8").unwrap();
9553 a.write_numeric(&1.5f64).unwrap();
9554 let a = ds.new_attr::<i32>().shape(()).create("i4").unwrap();
9555 a.write_numeric(&-7i32).unwrap();
9556 let a = ds.new_attr::<HBool>().shape(()).create("b").unwrap();
9557 a.write_numeric(&HBool::from(true)).unwrap();
9558 let a = ds.new_attr::<Complex64>().shape(()).create("z").unwrap();
9559 a.write_numeric(&Complex64 { re: 1.0, im: -2.0 }).unwrap();
9560 let a = ds
9561 .new_attr::<VarLenUnicode>()
9562 .shape(())
9563 .create("s")
9564 .unwrap();
9565 a.write_scalar(&VarLenUnicode("text".into())).unwrap();
9566 file.close().unwrap();
9567 }
9568 let file = H5File::open(&path).unwrap();
9569 let ds = file.dataset("d").unwrap();
9570
9571 let f8 = ds.attr("f8").unwrap();
9572 assert_eq!(f8.read_numeric::<f64>().unwrap(), 1.5);
9573 let err = f8.read_numeric::<f32>().unwrap_err();
9575 assert!(
9576 err.to_string().contains("read_numeric_as"),
9577 "unexpected error: {err}"
9578 );
9579 assert!(f8.read_numeric::<i64>().is_err());
9580
9581 let i4 = ds.attr("i4").unwrap();
9582 assert_eq!(i4.read_numeric::<i32>().unwrap(), -7);
9583 assert!(i4.read_numeric::<u32>().is_err());
9584
9585 assert!(bool::from(
9586 ds.attr("b").unwrap().read_numeric::<HBool>().unwrap()
9587 ));
9588 let z = ds.attr("z").unwrap().read_numeric::<Complex64>().unwrap();
9589 assert_eq!((z.re, z.im), (1.0, -2.0));
9590
9591 let s = ds.attr("s").unwrap();
9594 assert!(s.read_numeric::<f64>().is_err());
9595 assert!(s.read_numeric_as::<f64>().is_err());
9596 std::fs::remove_file(&path).ok();
9597 }
9598
9599 #[test]
9603 fn attr_read_numeric_as_converts() {
9604 let path = temp_path("attr_read_numeric_as");
9605 {
9606 let file = H5File::create(&path).unwrap();
9607 let ds = file.new_dataset::<f32>().shape([2]).create("d").unwrap();
9608 ds.write_raw(&[1.0f32; 2]).unwrap();
9609 let a = ds.new_attr::<i32>().shape(()).create("i4").unwrap();
9610 a.write_numeric(&-7i32).unwrap();
9611 let a = ds.new_attr::<u64>().shape(()).create("u8max").unwrap();
9612 a.write_numeric(&u64::MAX).unwrap();
9613 let a = ds.new_attr::<i16>().shape([3]).create("arr").unwrap();
9614 a.write_array(&[1i16, -2, 3]).unwrap();
9615 file.close().unwrap();
9616 }
9617 let file = H5File::open(&path).unwrap();
9618 let ds = file.dataset("d").unwrap();
9619 assert_eq!(
9620 ds.attr("i4").unwrap().read_numeric_as::<i64>().unwrap(),
9621 vec![-7]
9622 );
9623 let err = ds
9624 .attr("u8max")
9625 .unwrap()
9626 .read_numeric_as::<i64>()
9627 .unwrap_err();
9628 assert!(
9629 err.to_string().contains("does not fit in i64"),
9630 "unexpected error: {err}"
9631 );
9632 assert_eq!(
9633 ds.attr("arr").unwrap().read_numeric_as::<i32>().unwrap(),
9634 vec![1, -2, 3]
9635 );
9636 assert!(ds.attr("i4").unwrap().read_numeric_as::<f64>().is_err());
9637 std::fs::remove_file(&path).ok();
9638 }
9639
9640 #[test]
9642 fn numeric_slice_conversion() {
9643 let path = temp_path("numeric_slice");
9644 {
9645 let file = H5File::create(&path).unwrap();
9646 let ds = file.new_dataset::<i16>().shape([2, 3]).create("m").unwrap();
9647 ds.write_raw(&[1i16, 2, 3, 4, 5, 6]).unwrap();
9648 file.close().unwrap();
9649 }
9650 let file = H5File::open(&path).unwrap();
9651 let m = file.dataset("m").unwrap();
9652 assert_eq!(
9653 m.read_numeric_slice_as::<i32>(&[0, 1], &[2, 2]).unwrap(),
9654 vec![2, 3, 5, 6]
9655 );
9656 std::fs::remove_file(&path).ok();
9657 }
9658
9659 #[test]
9664 fn null_dataspace_distinct_from_scalar() {
9665 let path = temp_path("null_vs_scalar");
9666 {
9667 let file = H5File::create(&path).unwrap();
9668 file.new_dataset::<i32>().null().create("empty").unwrap();
9669 let scalar = file.new_dataset::<i32>().scalar().create("scalar").unwrap();
9670 scalar.write_raw(&[42i32]).unwrap();
9671 file.close().unwrap();
9672 }
9673 let file = H5File::open(&path).unwrap();
9674
9675 let empty = file.dataset("empty").unwrap();
9676 assert!(empty.is_null());
9677 assert_eq!(empty.shape(), Vec::<usize>::new());
9678 assert_eq!(empty.total_elements(), 0);
9679 assert_eq!(empty.read_raw_bytes().unwrap(), Vec::<u8>::new());
9680
9681 let scalar = file.dataset("scalar").unwrap();
9682 assert!(!scalar.is_null());
9683 assert_eq!(scalar.shape(), Vec::<usize>::new());
9684 assert_eq!(scalar.total_elements(), 1);
9685 assert_eq!(scalar.read_raw::<i32>().unwrap(), vec![42]);
9686
9687 std::fs::remove_file(&path).ok();
9688 }
9689
9690 #[test]
9694 fn null_dataspace_rejects_writes() {
9695 let path = temp_path("null_write_rejected");
9696 let file = H5File::create(&path).unwrap();
9697 let ds = file.new_dataset::<i32>().null().create("empty").unwrap();
9698 assert!(ds.write_raw(&[1i32]).is_err());
9699 assert!(ds.write_raw_bytes(&[0u8; 4]).is_err());
9700 file.close().unwrap();
9701 std::fs::remove_file(&path).ok();
9702 }
9703
9704 #[test]
9708 fn null_dataspace_rejects_chunking_and_fill_value() {
9709 let path = temp_path("null_chunk_rejected");
9710 let file = H5File::create(&path).unwrap();
9711 assert!(file
9712 .new_dataset::<i32>()
9713 .null()
9714 .chunk(&[4])
9715 .create("a")
9716 .is_err());
9717 assert!(file
9718 .new_dataset::<i32>()
9719 .null()
9720 .fill_value(7i32)
9721 .create("b")
9722 .is_err());
9723 file.close().unwrap();
9724 std::fs::remove_file(&path).ok();
9725 }
9726
9727 #[test]
9732 fn a_committed_type_that_cannot_be_shared_creates_no_dataset() {
9733 use crate::format::messages::datatype::DatatypeMessage;
9734
9735 let path = temp_path("committed_refused");
9736 let file = H5File::create(&path).unwrap();
9737 file.commit_datatype("t", DatatypeMessage::i32_type())
9738 .unwrap();
9739
9740 assert!(file
9741 .new_dataset::<i32>()
9742 .committed_type("absent")
9743 .shape([2usize])
9744 .create("a")
9745 .is_err());
9746 assert!(file
9747 .new_dataset::<u64>()
9748 .committed_type("t")
9749 .object_references()
9750 .shape([2usize])
9751 .create("b")
9752 .is_err());
9753 file.new_dataset::<i32>()
9756 .shape([2usize])
9757 .create("taken")
9758 .unwrap();
9759 assert!(file
9760 .commit_datatype("taken", DatatypeMessage::i32_type())
9761 .is_err());
9762 assert!(file
9763 .commit_datatype("t", DatatypeMessage::f64_type())
9764 .is_err());
9765
9766 assert_eq!(file.dataset_names(), vec!["taken".to_string()]);
9767 assert_eq!(file.named_datatype_names(), vec!["t".to_string()]);
9768 file.close().unwrap();
9769 std::fs::remove_file(&path).ok();
9770 }
9771
9772 #[test]
9776 fn deleting_a_group_takes_the_committed_datatypes_it_named() {
9777 use crate::format::messages::datatype::DatatypeMessage;
9778
9779 let path = temp_path("committed_group_deleted");
9780 {
9781 let file = H5File::create(&path).unwrap();
9782 let types = file.create_group("types").unwrap();
9783 types
9784 .commit_datatype("t", DatatypeMessage::i32_type())
9785 .unwrap();
9786 assert_eq!(file.named_datatype_names(), vec!["types/t".to_string()]);
9787
9788 file.delete_group("types").unwrap();
9789 assert!(file.named_datatype_names().is_empty());
9790 assert!(file
9791 .new_dataset::<i32>()
9792 .committed_type("types/t")
9793 .shape([2usize])
9794 .create("d")
9795 .is_err());
9796
9797 let types = file.create_group("types").unwrap();
9799 types
9800 .commit_datatype("t", DatatypeMessage::f64_type())
9801 .unwrap();
9802 file.close().unwrap();
9803 }
9804 let file = H5File::open(&path).unwrap();
9805 assert_eq!(file.named_datatype_names(), vec!["types/t".to_string()]);
9806 assert_eq!(
9807 file.named_datatype("types/t").unwrap().datatype().unwrap(),
9808 crate::format::messages::datatype::DatatypeMessage::f64_type()
9809 );
9810 drop(file);
9811 std::fs::remove_file(&path).ok();
9812 }
9813
9814 fn compact_image(path: &std::path::Path, name: &str) -> Option<Vec<u8>> {
9818 use crate::format::messages::data_layout::DataLayoutMessage;
9819 let mut reader = crate::io::reader::Hdf5Reader::open(path).unwrap();
9820 match &reader.dataset_info(name).unwrap().layout {
9821 DataLayoutMessage::Compact { data } => Some(data.clone()),
9822 other => panic!("{name}: expected a compact layout, got {other:?}"),
9823 }
9824 }
9825
9826 #[test]
9830 fn a_compact_dataset_stores_its_data_in_the_layout_message() {
9831 let path = temp_path("compact_roundtrip");
9832 let values: Vec<i32> = (0..16).collect();
9833 {
9834 let file = H5File::create(&path).unwrap();
9835 file.new_dataset::<i32>()
9836 .shape([16usize])
9837 .compact()
9838 .create("d")
9839 .unwrap()
9840 .write_raw(&values)
9841 .unwrap();
9842 file.close().unwrap();
9843 }
9844
9845 let image = compact_image(&path, "d").unwrap();
9846 assert_eq!(
9847 image,
9848 values
9849 .iter()
9850 .flat_map(|v| v.to_le_bytes())
9851 .collect::<Vec<_>>()
9852 );
9853
9854 let file = H5File::open(&path).unwrap();
9855 let ds = file.dataset("d").unwrap();
9856 assert_eq!(ds.shape(), vec![16]);
9857 assert_eq!(ds.read_raw::<i32>().unwrap(), values);
9858 std::fs::remove_file(&path).ok();
9859 }
9860
9861 #[test]
9865 fn a_compact_dataset_lands_in_its_group() {
9866 let path = temp_path("compact_in_group");
9867 {
9868 let file = H5File::create(&path).unwrap();
9869 let g = file.root_group().create_group("g").unwrap();
9870 g.new_dataset::<u8>()
9871 .shape([4usize])
9872 .compact()
9873 .create("d")
9874 .unwrap()
9875 .write_raw(&[1u8, 2, 3, 4])
9876 .unwrap();
9877 file.close().unwrap();
9878 }
9879 assert_eq!(compact_image(&path, "/g/d").unwrap(), vec![1u8, 2, 3, 4]);
9880
9881 let file = H5File::open(&path).unwrap();
9882 assert_eq!(
9883 file.dataset("/g/d").unwrap().read_raw::<u8>().unwrap(),
9884 vec![1u8, 2, 3, 4]
9885 );
9886 std::fs::remove_file(&path).ok();
9887 }
9888
9889 #[test]
9893 fn an_unwritten_compact_dataset_reads_back_as_its_fill_value() {
9894 let path = temp_path("compact_fill");
9895 {
9896 let file = H5File::create(&path).unwrap();
9897 file.new_dataset::<i32>()
9898 .shape([4usize])
9899 .compact()
9900 .fill_value(-7i32)
9901 .create("d")
9902 .unwrap();
9903 file.close().unwrap();
9904 }
9905 let file = H5File::open(&path).unwrap();
9906 assert_eq!(
9907 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
9908 vec![-7i32; 4]
9909 );
9910 std::fs::remove_file(&path).ok();
9911 }
9912
9913 #[test]
9918 fn a_reopened_compact_dataset_keeps_its_image() {
9919 let path = temp_path("compact_reopen");
9920 let values: Vec<i32> = (100..108).collect();
9921 {
9922 let file = H5File::create(&path).unwrap();
9923 file.new_dataset::<i32>()
9924 .shape([8usize])
9925 .compact()
9926 .create("d")
9927 .unwrap()
9928 .write_raw(&values)
9929 .unwrap();
9930 file.close().unwrap();
9931 }
9932 {
9933 let file = H5File::open_rw(&path).unwrap();
9934 file.dataset_writer("d")
9935 .unwrap()
9936 .new_attr::<i32>()
9937 .shape(())
9938 .create("note")
9939 .unwrap()
9940 .write_numeric(&1i32)
9941 .unwrap();
9942 file.close().unwrap();
9943 }
9944 assert_eq!(
9945 compact_image(&path, "d").unwrap(),
9946 values
9947 .iter()
9948 .flat_map(|v| v.to_le_bytes())
9949 .collect::<Vec<_>>()
9950 );
9951
9952 let file = H5File::open(&path).unwrap();
9953 assert_eq!(
9954 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
9955 values
9956 );
9957 std::fs::remove_file(&path).ok();
9958 }
9959
9960 #[test]
9964 fn the_compact_ceiling_is_checked_in_bytes() {
9965 let path = temp_path("compact_ceiling");
9966 let file = H5File::create(&path).unwrap();
9967
9968 let fits = crate::MAX_COMPACT_DATA / 4;
9969 file.new_dataset::<i32>()
9970 .shape([fits])
9971 .compact()
9972 .create("fits")
9973 .unwrap();
9974
9975 let over = fits + 1;
9976 let err = match file
9977 .new_dataset::<i32>()
9978 .shape([over])
9979 .compact()
9980 .create("over")
9981 {
9982 Err(e) => e.to_string(),
9983 Ok(_) => panic!("an image {} bytes wide must be refused", over * 4),
9984 };
9985 assert!(
9986 err.contains(&(over * 4).to_string())
9987 && err.contains(&crate::MAX_COMPACT_DATA.to_string()),
9988 "the error must name both sizes: {err}"
9989 );
9990
9991 file.close().unwrap();
9992 std::fs::remove_file(&path).ok();
9993 }
9994
9995 #[test]
9999 fn compact_rejects_chunking_filters_growth_and_a_null_dataspace() {
10000 let path = temp_path("compact_rejects");
10001 let file = H5File::create(&path).unwrap();
10002 assert!(file
10003 .new_dataset::<i32>()
10004 .shape([4usize])
10005 .compact()
10006 .chunk(&[4])
10007 .create("a")
10008 .is_err());
10009 assert!(file
10010 .new_dataset::<i32>()
10011 .shape([4usize])
10012 .compact()
10013 .deflate(4)
10014 .create("b")
10015 .is_err());
10016 assert!(file
10017 .new_dataset::<i32>()
10018 .shape([4usize])
10019 .compact()
10020 .max_shape(&[None])
10021 .create("c")
10022 .is_err());
10023 assert!(file
10024 .new_dataset::<i32>()
10025 .shape([4usize])
10026 .compact()
10027 .max_shape(&[Some(8)])
10028 .create("d")
10029 .is_err());
10030 assert!(file
10031 .new_dataset::<i32>()
10032 .null()
10033 .compact()
10034 .create("e")
10035 .is_err());
10036 file.close().unwrap();
10037 std::fs::remove_file(&path).ok();
10038 }
10039
10040 fn stored_pipeline(
10042 path: &std::path::Path,
10043 name: &str,
10044 ) -> crate::format::messages::filter::FilterPipeline {
10045 let mut reader = crate::io::reader::Hdf5Reader::open(path).unwrap();
10046 reader
10047 .dataset_info(name)
10048 .unwrap()
10049 .filter_pipeline
10050 .clone()
10051 .unwrap_or_else(|| panic!("{name}: no filter pipeline"))
10052 }
10053
10054 #[test]
10058 fn shuffle_alone_is_a_filter_pipeline() {
10059 use crate::format::messages::filter::{FilterPipeline, FILTER_SHUFFLE};
10060 let path = temp_path("shuffle_alone");
10061 let values: Vec<i32> = (0..64).collect();
10062 {
10063 let file = H5File::create(&path).unwrap();
10064 file.new_dataset::<i32>()
10065 .shape([64usize])
10066 .chunk(&[16])
10067 .shuffle()
10068 .create("d")
10069 .unwrap()
10070 .write_raw(&values)
10071 .unwrap();
10072 file.close().unwrap();
10073 }
10074 let pipeline = stored_pipeline(&path, "d");
10075 assert_eq!(pipeline, FilterPipeline::shuffle(4));
10076 assert_eq!(pipeline.filters[0].id, FILTER_SHUFFLE);
10077
10078 let file = H5File::open(&path).unwrap();
10079 assert_eq!(
10080 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
10081 values
10082 );
10083 std::fs::remove_file(&path).ok();
10084 }
10085
10086 #[cfg(feature = "deflate")]
10090 #[test]
10091 fn shuffle_composes_with_deflate() {
10092 let path = temp_path("shuffle_then_deflate");
10093 let values: Vec<i32> = (0..64).collect();
10094 {
10095 let file = H5File::create(&path).unwrap();
10096 for (name, ds) in [
10097 ("split", file.new_dataset::<i32>().shuffle().deflate(6)),
10098 ("combined", file.new_dataset::<i32>().shuffle_deflate(6)),
10099 ] {
10100 ds.shape([64usize])
10101 .chunk(&[16])
10102 .create(name)
10103 .unwrap()
10104 .write_raw(&values)
10105 .unwrap();
10106 }
10107 file.close().unwrap();
10108 }
10109 assert_eq!(
10110 stored_pipeline(&path, "split"),
10111 crate::format::messages::filter::FilterPipeline::shuffle_deflate(4, 6)
10112 );
10113 assert_eq!(
10114 stored_pipeline(&path, "split"),
10115 stored_pipeline(&path, "combined")
10116 );
10117
10118 let file = H5File::open(&path).unwrap();
10119 for name in ["split", "combined"] {
10120 assert_eq!(
10121 file.dataset(name).unwrap().read_raw::<i32>().unwrap(),
10122 values,
10123 "{name}"
10124 );
10125 }
10126 std::fs::remove_file(&path).ok();
10127 }
10128
10129 #[test]
10134 fn shuffle_records_the_stored_element_width() {
10135 use crate::format::messages::datatype::DatatypeMessage;
10136 use crate::format::messages::filter::FilterPipeline;
10137 let path = temp_path("shuffle_override_width");
10138 let bytes: Vec<u8> = (0..16u8).collect();
10139 {
10140 let file = H5File::create(&path).unwrap();
10141 file.new_dataset::<u8>()
10142 .datatype(DatatypeMessage::i32_type())
10143 .shape([4usize])
10144 .chunk(&[4])
10145 .shuffle()
10146 .create("d")
10147 .unwrap()
10148 .write_raw_bytes(&bytes)
10149 .unwrap();
10150 file.close().unwrap();
10151 }
10152 assert_eq!(stored_pipeline(&path, "d"), FilterPipeline::shuffle(4));
10153
10154 let file = H5File::open(&path).unwrap();
10155 assert_eq!(
10156 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
10157 bytes
10158 .chunks(4)
10159 .map(|c| i32::from_le_bytes(c.try_into().unwrap()))
10160 .collect::<Vec<_>>()
10161 );
10162 std::fs::remove_file(&path).ok();
10163 }
10164
10165 #[test]
10170 fn a_virtual_dataset_refuses_every_write() {
10171 use crate::Selection;
10172 let path = temp_path("vds_write_refused");
10173 let file = H5File::create(&path).unwrap();
10174 let ds = file
10175 .new_dataset::<i32>()
10176 .shape([16usize])
10177 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
10178 .create("vds")
10179 .unwrap();
10180 for err in [
10181 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap_err(),
10182 ds.write_slice(&[0], &[2], &[1i32, 2]).unwrap_err(),
10183 ] {
10184 let msg = err.to_string();
10185 assert!(msg.contains("virtual dataset"), "{msg}");
10186 }
10187 file.close().unwrap();
10188 std::fs::remove_file(&path).ok();
10189 }
10190
10191 #[test]
10198 fn a_printf_source_name_needs_the_mapping_shape_that_uses_it() {
10199 use crate::Selection;
10200 let path = temp_path("vds_printf");
10201 let file = H5File::create(&path).unwrap();
10202 for (f, d) in [("src_%b.h5", "src"), ("src.h5", "block_%b")] {
10203 let err = match file
10204 .new_dataset::<i32>()
10205 .shape([16usize])
10206 .virtual_mapping(Selection::All, f, d, Selection::All)
10207 .create("vds")
10208 {
10209 Ok(_) => panic!("a bounded mapping has one block, so %b names nothing"),
10210 Err(e) => e.to_string(),
10211 };
10212 assert!(err.contains("printf specifier"), "{err}");
10213 }
10214 let err = match file
10216 .new_dataset::<i32>()
10217 .shape([1usize, 2])
10218 .max_shape(&[None, Some(2)])
10219 .virtual_mapping(unlimited_rows(), "src_%z.h5", "src", Selection::All)
10220 .create("vds_bad")
10221 {
10222 Ok(_) => panic!("%z is not a legal conversion"),
10223 Err(e) => e.to_string(),
10224 };
10225 assert!(err.contains("invalid format specifier"), "{err}");
10226 file.close().unwrap();
10227 std::fs::remove_file(&path).ok();
10228 }
10229
10230 #[test]
10235 fn a_printf_mapping_stitches_one_source_per_block() {
10236 use crate::Selection;
10237 let path = temp_path("vds_printf_blocks");
10238 {
10239 let file = H5File::create(&path).unwrap();
10240 for (b, base) in [(0, 0i32), (1, 100), (3, 300)] {
10241 file.new_dataset::<i32>()
10242 .shape([2usize])
10243 .create(&format!("block{b}"))
10244 .unwrap()
10245 .write_raw(&[base, base + 1])
10246 .unwrap();
10247 }
10248 file.new_dataset::<i32>()
10249 .shape([1usize, 2])
10250 .max_shape(&[None, Some(2)])
10251 .virtual_mapping(unlimited_rows(), ".", "block%b", Selection::All)
10252 .create("vds")
10253 .unwrap();
10254 file.close().unwrap();
10255 }
10256 let file = H5File::open(&path).unwrap();
10257 let ds = file.dataset("vds").unwrap();
10258 assert_eq!(ds.shape(), vec![2, 2]);
10260 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10261 drop(file);
10262 std::fs::remove_file(&path).ok();
10263 }
10264
10265 #[test]
10271 fn a_source_that_cannot_be_opened_reads_as_the_fill_value() {
10272 use crate::{Hyperslab, HyperslabBlock, Selection};
10273 let block = |start: u64, end: u64| Selection::Hyperslab {
10274 rank: 1,
10275 form: Hyperslab::Blocks(vec![HyperslabBlock {
10276 start: vec![start],
10277 end: vec![end],
10278 }]),
10279 };
10280 let path = temp_path("vds_absent_source");
10281 {
10282 let file = H5File::create(&path).unwrap();
10283 file.new_dataset::<i32>()
10284 .shape([4usize])
10285 .create("here")
10286 .unwrap()
10287 .write_raw(&[1i32, 2, 3, 4])
10288 .unwrap();
10289 file.new_dataset::<i32>()
10290 .shape([12usize])
10291 .fill_value(-3i32)
10292 .virtual_mapping(block(0, 3), ".", "here", block(0, 3))
10293 .virtual_mapping(block(4, 7), ".", "absent", block(0, 3))
10295 .virtual_mapping(block(8, 11), "no_such_vds_source.h5", "src", block(0, 3))
10297 .create("vds")
10298 .unwrap();
10299 file.close().unwrap();
10300 }
10301 let file = H5File::open(&path).unwrap();
10302 let ds = file.dataset("vds").unwrap();
10303 assert_eq!(
10304 ds.read_raw::<i32>().unwrap(),
10305 vec![1, 2, 3, 4, -3, -3, -3, -3, -3, -3, -3, -3]
10306 );
10307 assert_eq!(
10310 ds.read_slice::<i32>(&[2], &[6]).unwrap(),
10311 vec![3, 4, -3, -3, -3, -3]
10312 );
10313 drop(file);
10314 std::fs::remove_file(&path).ok();
10315 }
10316
10317 #[test]
10321 fn an_escaped_percent_is_a_literal_in_a_source_name() {
10322 use crate::Selection;
10323 let path = temp_path("vds_escaped_pct");
10324 {
10325 let file = H5File::create(&path).unwrap();
10326 file.new_dataset::<i32>()
10327 .shape([4usize])
10328 .create("od%d")
10329 .unwrap()
10330 .write_raw(&[5i32, 6, 7, 8])
10331 .unwrap();
10332 file.new_dataset::<i32>()
10333 .shape([4usize])
10334 .virtual_mapping(Selection::All, ".", "od%%d", Selection::All)
10335 .create("vds")
10336 .unwrap();
10337 file.close().unwrap();
10338 }
10339 let file = H5File::open(&path).unwrap();
10340 let ds = file.dataset("vds").unwrap();
10341 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![5, 6, 7, 8]);
10342 assert_eq!(ds.virtual_mappings().unwrap()[0].source_dset_name, "od%%d");
10344 drop(file);
10345 std::fs::remove_file(&path).ok();
10346 }
10347
10348 #[test]
10354 fn an_unlimited_mapping_takes_its_extent_from_its_source() {
10355 let path = temp_path("vds_unlimited");
10356 {
10357 let file = H5File::create(&path).unwrap();
10358 file.new_dataset::<i32>()
10359 .shape([5usize, 2])
10360 .chunk(&[5, 2])
10361 .max_shape(&[None, Some(2)])
10362 .create("src")
10363 .unwrap()
10364 .write_raw(&(0..10i32).collect::<Vec<_>>())
10365 .unwrap();
10366 file.new_dataset::<i32>()
10367 .shape([1usize, 2])
10368 .max_shape(&[None, Some(2)])
10369 .virtual_mapping(unlimited_rows(), ".", "src", unlimited_rows())
10370 .create("vds")
10371 .unwrap();
10372 file.close().unwrap();
10373 }
10374 let file = H5File::open(&path).unwrap();
10375 let ds = file.dataset("vds").unwrap();
10376 assert_eq!(ds.shape(), vec![5, 2]);
10377 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..10).collect::<Vec<i32>>());
10378 drop(file);
10379 std::fs::remove_file(&path).ok();
10380 }
10381
10382 fn printf_gap_file(tag: &str) -> std::path::PathBuf {
10386 use crate::Selection;
10387 let path = temp_path(tag);
10388 let file = H5File::create(&path).unwrap();
10389 for (b, base) in [(0, 0i32), (1, 100), (3, 300)] {
10390 file.new_dataset::<i32>()
10391 .shape([2usize])
10392 .create(&format!("block{b}"))
10393 .unwrap()
10394 .write_raw(&[base, base + 1])
10395 .unwrap();
10396 }
10397 file.new_dataset::<i32>()
10398 .shape([1usize, 2])
10399 .max_shape(&[None, Some(2)])
10400 .fill_value(-7i32)
10401 .virtual_mapping(unlimited_rows(), ".", "block%b", Selection::All)
10402 .create("vds")
10403 .unwrap();
10404 file.close().unwrap();
10405 path
10406 }
10407
10408 #[test]
10414 fn a_printf_gap_looks_past_the_missing_block() {
10415 use crate::DatasetAccess;
10416 let path = printf_gap_file("vds_printf_gap");
10417 let file = H5File::open(&path).unwrap();
10418 for (gap, shape, data) in [
10419 (0u64, vec![2usize, 2], vec![0i32, 1, 100, 101]),
10420 (1, vec![4, 2], vec![0, 1, 100, 101, -7, -7, 300, 301]),
10421 (2, vec![4, 2], vec![0, 1, 100, 101, -7, -7, 300, 301]),
10422 ] {
10423 let ds = file
10424 .dataset_with("vds", DatasetAccess::new().virtual_printf_gap(gap))
10425 .unwrap();
10426 assert_eq!(ds.shape(), shape, "gap {gap}");
10427 assert_eq!(ds.read_raw::<i32>().unwrap(), data, "gap {gap}");
10428 }
10429 let ds = file.dataset("vds").unwrap();
10432 assert_eq!(ds.shape(), vec![2, 2]);
10433 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10434 drop(file);
10435 std::fs::remove_file(&path).ok();
10436 }
10437
10438 #[test]
10447 fn a_relative_source_resolves_next_to_the_virtual_dataset() {
10448 use crate::Selection;
10449 let dir = std::env::temp_dir().join(format!(
10450 "rust_hdf5_vds_beside_{}_{:?}",
10451 std::process::id(),
10452 std::thread::current().id()
10453 ));
10454 std::fs::create_dir_all(&dir).unwrap();
10455 {
10456 let file = H5File::create(dir.join("src.h5")).unwrap();
10457 file.new_dataset::<i32>()
10458 .shape([2usize, 4])
10459 .create("data")
10460 .unwrap()
10461 .write_raw(&(0..8i32).collect::<Vec<_>>())
10462 .unwrap();
10463 file.close().unwrap();
10464 }
10465 {
10466 let file = H5File::create(dir.join("v.h5")).unwrap();
10467 file.new_dataset::<i32>()
10468 .shape([2usize, 4])
10469 .fill_value(-9i32)
10470 .virtual_mapping(Selection::All, "src.h5", "data", Selection::All)
10473 .create("v")
10474 .unwrap();
10475 file.close().unwrap();
10476 }
10477 assert_ne!(std::env::current_dir().unwrap(), dir);
10480 let file = H5File::open(dir.join("v.h5")).unwrap();
10481 let ds = file.dataset("v").unwrap();
10482 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..8i32).collect::<Vec<_>>());
10483 drop(file);
10484 std::fs::remove_dir_all(&dir).ok();
10485 }
10486
10487 #[test]
10499 fn the_first_open_of_a_virtual_dataset_fixes_the_properties_for_later_opens() {
10500 use crate::DatasetAccess;
10501 let path = printf_gap_file("vds_printf_first_open");
10502 let file = H5File::open(&path).unwrap();
10503 let gap = |g: u64| DatasetAccess::new().virtual_printf_gap(g);
10504 {
10505 let a = file.dataset_with("vds", gap(0)).unwrap();
10506 let b = file.dataset_with("vds", gap(1)).unwrap();
10507 assert_eq!(a.shape(), vec![2, 2]);
10508 assert_eq!(b.shape(), vec![2, 2]);
10509 assert_eq!(b.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10510 }
10511 {
10512 let c = file.dataset_with("vds", gap(1)).unwrap();
10513 let d = file.dataset_with("vds", gap(0)).unwrap();
10514 assert_eq!(c.shape(), vec![4, 2]);
10515 assert_eq!(d.shape(), vec![4, 2]);
10516 assert_eq!(
10517 d.read_raw::<i32>().unwrap(),
10518 vec![0, 1, 100, 101, -7, -7, 300, 301]
10519 );
10520 }
10521 let e = file.dataset_with("vds", gap(0)).unwrap();
10522 assert_eq!(e.shape(), vec![2, 2]);
10523 assert_eq!(e.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10524 drop(e);
10525 drop(file);
10526 std::fs::remove_file(&path).ok();
10527 }
10528
10529 #[test]
10534 fn the_first_missing_view_ignores_the_printf_gap() {
10535 use crate::{DatasetAccess, VirtualView};
10536 let path = printf_gap_file("vds_printf_first_missing");
10537 let file = H5File::open(&path).unwrap();
10538 for gap in [0u64, 2] {
10539 let ds = file
10540 .dataset_with(
10541 "vds",
10542 DatasetAccess::new()
10543 .virtual_view(VirtualView::FirstMissing)
10544 .virtual_printf_gap(gap),
10545 )
10546 .unwrap();
10547 assert_eq!(ds.shape(), vec![2, 2], "gap {gap}");
10548 assert_eq!(
10549 ds.read_raw::<i32>().unwrap(),
10550 vec![0, 1, 100, 101],
10551 "gap {gap}"
10552 );
10553 }
10554 drop(file);
10555 std::fs::remove_file(&path).ok();
10556 }
10557
10558 #[test]
10566 fn the_view_decides_whether_a_trailing_gap_is_inside_the_extent() {
10567 use crate::format::selection::UNLIMITED;
10568 use crate::{DatasetAccess, Hyperslab, RegularHyperslab, Selection, VirtualView};
10569 let strided = || Selection::Hyperslab {
10570 rank: 2,
10571 form: Hyperslab::Regular(RegularHyperslab {
10572 start: vec![0, 0],
10573 stride: vec![3, 1],
10574 count: vec![UNLIMITED, 1],
10575 block: vec![2, 2],
10576 }),
10577 };
10578 let path = temp_path("vds_view_trail");
10579 {
10580 let file = H5File::create(&path).unwrap();
10581 file.new_dataset::<i32>()
10582 .shape([3usize, 2])
10583 .max_shape(&[None, Some(2)])
10584 .chunk(&[1, 2])
10585 .create("src")
10586 .unwrap()
10587 .write_raw(&(0..6i32).collect::<Vec<_>>())
10588 .unwrap();
10589 file.new_dataset::<i32>()
10590 .shape([1usize, 2])
10591 .max_shape(&[None, Some(2)])
10592 .fill_value(-9i32)
10593 .virtual_mapping(strided(), ".", "src", strided())
10594 .create("vds")
10595 .unwrap();
10596 file.close().unwrap();
10597 }
10598 let file = H5File::open(&path).unwrap();
10599 {
10600 let last = file.dataset("vds").unwrap();
10601 assert_eq!(last.shape(), vec![2, 2]);
10602 assert_eq!(last.read_raw::<i32>().unwrap(), vec![0, 1, 2, 3]);
10603 }
10604 let first = file
10606 .dataset_with(
10607 "vds",
10608 DatasetAccess::new().virtual_view(VirtualView::FirstMissing),
10609 )
10610 .unwrap();
10611 assert_eq!(first.shape(), vec![3, 2]);
10612 assert_eq!(first.read_raw::<i32>().unwrap(), vec![0, 1, 2, 3, -9, -9]);
10613 drop(file);
10614 std::fs::remove_file(&path).ok();
10615 }
10616
10617 #[test]
10622 fn the_access_property_list_reads_back_and_refuses_hsize_undef() {
10623 use crate::{DatasetAccess, VirtualView};
10624 let plist = DatasetAccess::new();
10625 assert_eq!(plist.view(), VirtualView::LastAvailable);
10626 assert_eq!(plist.printf_gap(), 0);
10627 let set = plist
10628 .virtual_view(VirtualView::FirstMissing)
10629 .virtual_printf_gap(4);
10630 assert_eq!(set.view(), VirtualView::FirstMissing);
10631 assert_eq!(set.printf_gap(), 4);
10634
10635 let path = printf_gap_file("vds_printf_gap_undef");
10636 let file = H5File::open(&path).unwrap();
10637 let err = match file.dataset_with("vds", DatasetAccess::new().virtual_printf_gap(u64::MAX))
10638 {
10639 Ok(_) => panic!("HSIZE_UNDEF is not a valid printf gap size"),
10640 Err(e) => e.to_string(),
10641 };
10642 assert!(err.contains("HSIZE_UNDEF"), "{err}");
10643 drop(file);
10644 std::fs::remove_file(&path).ok();
10645 }
10646
10647 fn unlimited_rows() -> crate::Selection {
10650 use crate::format::selection::UNLIMITED;
10651 use crate::{Hyperslab, RegularHyperslab, Selection};
10652 Selection::Hyperslab {
10653 rank: 2,
10654 form: Hyperslab::Regular(RegularHyperslab {
10655 start: vec![0, 0],
10656 stride: vec![1, 1],
10657 count: vec![UNLIMITED, 1],
10658 block: vec![1, 2],
10659 }),
10660 }
10661 }
10662
10663 #[test]
10668 fn an_unlimited_virtual_selection_over_a_limited_source_is_refused() {
10669 use crate::Selection;
10670 let path = temp_path("vds_unlim_limited_src");
10671 let file = H5File::create(&path).unwrap();
10672 let err = match file
10673 .new_dataset::<i32>()
10674 .shape([1usize, 2])
10675 .max_shape(&[None, Some(2)])
10676 .virtual_mapping(unlimited_rows(), "src.h5", "src", Selection::All)
10677 .create("vds")
10678 {
10679 Ok(_) => panic!("no printf substitution names the mapping's later blocks"),
10680 Err(e) => e.to_string(),
10681 };
10682 assert!(err.contains("printf"), "{err}");
10683 file.close().unwrap();
10684 std::fs::remove_file(&path).ok();
10685 }
10686
10687 #[test]
10690 fn a_virtual_dataset_cannot_also_be_chunked_or_external() {
10691 use crate::Selection;
10692 let path = temp_path("vds_exclusive");
10693 let file = H5File::create(&path).unwrap();
10694 let builder = || {
10695 file.new_dataset::<i32>().shape([16usize]).virtual_mapping(
10696 Selection::All,
10697 "src.h5",
10698 "src",
10699 Selection::All,
10700 )
10701 };
10702 for (which, res) in [
10703 ("chunked", builder().chunk(&[4]).create("a")),
10704 ("compact", builder().compact().create("b")),
10705 (
10706 "external",
10707 builder().external(&[("x.raw", 0, 64)]).create("c"),
10708 ),
10709 ("references", builder().object_references().create("d")),
10710 ] {
10711 match res {
10712 Ok(_) => panic!("a virtual dataset cannot also be {which}"),
10713 Err(e) => assert!(e.to_string().contains("virtual dataset"), "{which}: {e}"),
10714 }
10715 }
10716 file.close().unwrap();
10717 std::fs::remove_file(&path).ok();
10718 }
10719
10720 #[test]
10725 fn deleting_a_virtual_dataset_frees_its_mapping_list() {
10726 use crate::Selection;
10727 let path = temp_path("vds_delete");
10728 let baseline = {
10729 let file = H5File::create(&path).unwrap();
10730 file.new_dataset::<i32>()
10731 .shape([16usize])
10732 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
10733 .create("vds")
10734 .unwrap();
10735 file.delete_dataset("vds").unwrap();
10736 file.close().unwrap();
10737 std::fs::metadata(&path).unwrap().len()
10738 };
10739 std::fs::remove_file(&path).ok();
10740
10741 let file = H5File::create(&path).unwrap();
10745 for i in 0..10 {
10746 let name = format!("vds{i}");
10747 file.new_dataset::<i32>()
10748 .shape([16usize])
10749 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
10750 .create(&name)
10751 .unwrap();
10752 file.delete_dataset(&name).unwrap();
10753 }
10754 file.close().unwrap();
10755 assert_eq!(std::fs::metadata(&path).unwrap().len(), baseline);
10756 std::fs::remove_file(&path).ok();
10757 }
10758
10759 #[test]
10762 fn storage_layout_reports_each_class() {
10763 use crate::StorageLayout;
10764 let path = temp_path("storage_layout");
10765 {
10766 let file = H5File::create(&path).unwrap();
10767 file.new_dataset::<i32>()
10768 .shape([4usize])
10769 .create("contig")
10770 .unwrap();
10771 file.new_dataset::<i32>()
10772 .shape([4usize])
10773 .compact()
10774 .create("compact")
10775 .unwrap();
10776 file.new_dataset::<i32>()
10777 .shape([8usize])
10778 .chunk(&[4])
10779 .create("chunked")
10780 .unwrap();
10781 file.close().unwrap();
10782 }
10783 let file = H5File::open(&path).unwrap();
10784 assert_eq!(
10785 file.dataset("contig").unwrap().storage_layout().unwrap(),
10786 StorageLayout::Contiguous
10787 );
10788 assert_eq!(
10789 file.dataset("compact").unwrap().storage_layout().unwrap(),
10790 StorageLayout::Compact
10791 );
10792 assert_eq!(
10793 file.dataset("chunked").unwrap().storage_layout().unwrap(),
10794 StorageLayout::Chunked
10795 );
10796 }
10797
10798 #[test]
10800 fn storage_layout_errors_in_write_mode() {
10801 let path = temp_path("storage_layout_write_mode");
10802 let file = H5File::create(&path).unwrap();
10803 let ds = file
10804 .new_dataset::<i32>()
10805 .shape([4usize])
10806 .create("data")
10807 .unwrap();
10808 assert!(ds.storage_layout().is_err());
10809 file.close().unwrap();
10810 }
10811
10812 #[test]
10817 fn chunk_index_reports_the_stored_kind() {
10818 use crate::ChunkIndex;
10819 let path = temp_path("chunk_index");
10820 {
10821 let file = H5File::create(&path).unwrap();
10822 file.new_dataset::<i32>()
10823 .shape([4usize])
10824 .create("contig")
10825 .unwrap();
10826 file.new_dataset::<i32>()
10827 .shape([16usize])
10828 .chunk(&[4])
10829 .max_shape(&[None])
10830 .create("earray")
10831 .unwrap();
10832 file.set_libver_latest(false).unwrap();
10833 file.new_dataset::<i32>()
10834 .shape([8usize])
10835 .chunk(&[4])
10836 .max_shape(&[None])
10837 .create("btree1")
10838 .unwrap();
10839 file.close().unwrap();
10840 }
10841 let file = H5File::open(&path).unwrap();
10842 assert_eq!(file.dataset("contig").unwrap().chunk_index().unwrap(), None);
10843 assert_eq!(
10844 file.dataset("earray").unwrap().chunk_index().unwrap(),
10845 Some(ChunkIndex::ExtensibleArray)
10846 );
10847 assert_eq!(
10848 file.dataset("btree1").unwrap().chunk_index().unwrap(),
10849 Some(ChunkIndex::BtreeV1)
10850 );
10851 }
10852
10853 #[test]
10857 fn filters_reports_the_stored_pipeline() {
10858 use crate::format::messages::filter::{FILTER_DEFLATE, FILTER_SHUFFLE, FLAG_OPTIONAL};
10859 let path = temp_path("filters");
10860 {
10861 let file = H5File::create(&path).unwrap();
10862 file.new_dataset::<i32>()
10863 .shape([16usize])
10864 .create("unfiltered")
10865 .unwrap();
10866 file.new_dataset::<i32>()
10867 .shape([16usize])
10868 .chunk(&[4])
10869 .shuffle()
10870 .deflate(6)
10871 .create("filtered")
10872 .unwrap();
10873 file.close().unwrap();
10874 }
10875 let file = H5File::open(&path).unwrap();
10876 assert_eq!(
10877 file.dataset("unfiltered").unwrap().filters().unwrap(),
10878 Vec::new()
10879 );
10880 let filters = file.dataset("filtered").unwrap().filters().unwrap();
10881 assert_eq!(filters.len(), 2);
10882 assert_eq!(filters[0].id, FILTER_SHUFFLE);
10883 assert_eq!(filters[0].flags, FLAG_OPTIONAL);
10884 assert_eq!(filters[1].id, FILTER_DEFLATE);
10885 assert_eq!(filters[1].cd_values, vec![6]);
10886 }
10887
10888 #[test]
10894 fn fill_value_reports_the_stored_value() {
10895 use crate::FillValue;
10896 let path = temp_path("fill_value");
10897 {
10898 let file = H5File::create(&path).unwrap();
10899 file.new_dataset::<i32>()
10900 .shape([4usize])
10901 .create("unset")
10902 .unwrap();
10903 file.new_dataset::<i32>()
10904 .shape([4usize])
10905 .fill_value(-7i32)
10906 .create("set")
10907 .unwrap();
10908 file.close().unwrap();
10909 }
10910 let file = H5File::open(&path).unwrap();
10911 assert_eq!(
10912 file.dataset("unset").unwrap().fill_value().unwrap(),
10913 FillValue::Default
10914 );
10915 assert_eq!(
10916 file.dataset("set").unwrap().fill_value().unwrap(),
10917 FillValue::UserDefined((-7i32).to_le_bytes().to_vec())
10918 );
10919 }
10920
10921 #[test]
10926 fn the_unlimited_external_slot_keeps_its_three_rules() {
10927 use crate::format::messages::external_file_list::UNLIMITED;
10928 let path = temp_path("efl_unlim_rules");
10929 let file = H5File::create(&path).unwrap();
10930
10931 let err = match file
10934 .new_dataset::<i32>()
10935 .shape([8usize])
10936 .external(&[("a.raw", 0, UNLIMITED), ("b.raw", 0, 32)])
10937 .create("mid")
10938 {
10939 Ok(_) => panic!("an unlimited slot absorbs everything behind it"),
10940 Err(e) => e.to_string(),
10941 };
10942 assert!(err.contains("only be the last"), "{err}");
10943
10944 let err = match file
10946 .new_dataset::<i32>()
10947 .shape([8usize])
10948 .max_shape(&[None])
10949 .external(&[("a.raw", 0, 32)])
10950 .create("finite")
10951 {
10952 Ok(_) => panic!("no finite reservation covers an unlimited extent"),
10953 Err(e) => e.to_string(),
10954 };
10955 assert!(err.contains("unlimited dataspace"), "{err}");
10956
10957 let err = match file
10959 .new_dataset::<i32>()
10960 .shape([2usize, 4])
10961 .max_shape(&[Some(2), None])
10962 .external(&[("a.raw", 0, UNLIMITED)])
10963 .create("dim1")
10964 {
10965 Ok(_) => panic!("only the slowest-varying dimension may be extendible"),
10966 Err(e) => e.to_string(),
10967 };
10968 assert!(err.contains("only the first dimension"), "{err}");
10969
10970 file.new_dataset::<i32>()
10973 .shape([8usize])
10974 .max_shape(&[None])
10975 .external(&[("a.raw", 0, 16), ("b.raw", 0, UNLIMITED)])
10976 .create("ok")
10977 .unwrap()
10978 .write_raw(&(0..8i32).collect::<Vec<_>>())
10979 .unwrap();
10980 file.close().unwrap();
10981 std::fs::remove_file(&path).ok();
10982 for raw in ["a.raw", "b.raw"] {
10983 std::fs::remove_file(raw).ok();
10984 }
10985 }
10986
10987 #[test]
10992 fn an_unlimited_external_slot_reads_zero_past_the_end_of_its_file() {
10993 use crate::format::messages::external_file_list::UNLIMITED;
10994 let dir = std::env::temp_dir().join(format!("rh5_efl_short_{}", std::process::id()));
10995 std::fs::create_dir_all(&dir).unwrap();
10996 let path = dir.join("f.h5");
10997 let raw = dir.join("short.raw");
10998 std::fs::write(&raw, [0u8; 16]).unwrap();
11000 {
11001 let file = H5File::create(&path).unwrap();
11002 file.new_dataset::<i32>()
11003 .shape([8usize])
11004 .max_shape(&[None])
11005 .external(&[(raw.to_str().unwrap(), 0, UNLIMITED)])
11006 .create("data")
11007 .unwrap();
11008 file.close().unwrap();
11009 }
11010 let file = H5File::open(&path).unwrap();
11011 assert_eq!(
11012 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
11013 vec![0i32; 8]
11014 );
11015 drop(file);
11016 std::fs::remove_dir_all(&dir).ok();
11017 }
11018
11019 #[test]
11023 fn external_files_reports_the_stored_segments() {
11024 let path = temp_path("external_files");
11025 {
11026 let file = H5File::create(&path).unwrap();
11027 file.new_dataset::<i32>()
11028 .shape([4usize])
11029 .create("contig")
11030 .unwrap();
11031 file.new_dataset::<i32>()
11032 .shape([16usize])
11033 .external(&[("a.raw", 0, 32), ("b.raw", 8, 32)])
11034 .create("external")
11035 .unwrap();
11036 file.close().unwrap();
11037 }
11038 let file = H5File::open(&path).unwrap();
11039 assert_eq!(
11040 file.dataset("contig").unwrap().external_files().unwrap(),
11041 Vec::new()
11042 );
11043 let segments = file.dataset("external").unwrap().external_files().unwrap();
11044 assert_eq!(segments.len(), 2);
11045 assert_eq!(segments[0].name, "a.raw");
11046 assert_eq!(segments[0].offset, 0);
11047 assert_eq!(segments[0].size, 32);
11048 assert_eq!(segments[1].name, "b.raw");
11049 assert_eq!(segments[1].offset, 8);
11050 assert_eq!(segments[1].size, 32);
11051 }
11052
11053 #[test]
11058 fn max_shape_reports_unlimited_and_fixed_axes() {
11059 let path = temp_path("max_shape");
11060 {
11061 let file = H5File::create(&path).unwrap();
11062 file.new_dataset::<i32>()
11063 .shape([4usize, 8])
11064 .create("fixed")
11065 .unwrap();
11066 file.new_dataset::<i32>()
11067 .shape([4usize, 8])
11068 .chunk(&[2, 8])
11069 .max_shape(&[None, Some(8)])
11070 .create("unlimited")
11071 .unwrap();
11072 file.close().unwrap();
11073 }
11074 let file = H5File::open(&path).unwrap();
11075 assert_eq!(
11076 file.dataset("fixed").unwrap().max_shape().unwrap(),
11077 vec![Some(4), Some(8)]
11078 );
11079 assert_eq!(
11080 file.dataset("unlimited").unwrap().max_shape().unwrap(),
11081 vec![None, Some(8)]
11082 );
11083 }
11084
11085 #[test]
11089 fn virtual_mappings_reports_the_stored_mappings() {
11090 use crate::Selection;
11091 let path = temp_path("virtual_mappings");
11092 {
11093 let file = H5File::create(&path).unwrap();
11094 file.new_dataset::<i32>()
11095 .shape([4usize])
11096 .create("plain")
11097 .unwrap();
11098 file.new_dataset::<i32>()
11099 .shape([16usize])
11100 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
11101 .create("vds")
11102 .unwrap();
11103 file.close().unwrap();
11104 }
11105 let file = H5File::open(&path).unwrap();
11106 assert_eq!(
11107 file.dataset("plain").unwrap().virtual_mappings().unwrap(),
11108 Vec::new()
11109 );
11110 let mappings = file.dataset("vds").unwrap().virtual_mappings().unwrap();
11111 assert_eq!(mappings.len(), 1);
11112 assert_eq!(mappings[0].source_file_name, "src.h5");
11113 assert_eq!(mappings[0].source_dset_name, "src");
11114 assert_eq!(mappings[0].source_selection, Selection::All);
11115 assert_eq!(mappings[0].virtual_selection, Selection::All);
11116 }
11117}