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::{ChunkBytes, 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
1459pub(crate) const HOST_BYTE_ORDER: ByteOrder = if cfg!(target_endian = "big") {
1461 ByteOrder::BigEndian
1462} else {
1463 ByteOrder::LittleEndian
1464};
1465
1466pub(crate) const FOREIGN_BYTE_ORDER: ByteOrder = match HOST_BYTE_ORDER {
1468 ByteOrder::LittleEndian => ByteOrder::BigEndian,
1469 ByteOrder::BigEndian => ByteOrder::LittleEndian,
1470};
1471
1472#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1474enum ByteOrderAction {
1475 Keep,
1477 SwapElements,
1479 Refuse,
1481}
1482
1483fn byte_order_action(datatype: &DatatypeMessage, width: usize) -> ByteOrderAction {
1492 match datatype.scalar_byte_order() {
1493 Some(order) if order == FOREIGN_BYTE_ORDER && width > 1 => ByteOrderAction::SwapElements,
1494 Some(_) => ByteOrderAction::Keep,
1495 None if datatype.contains_byte_order(FOREIGN_BYTE_ORDER) => ByteOrderAction::Refuse,
1496 None => ByteOrderAction::Keep,
1497 }
1498}
1499
1500#[cfg(feature = "mmap")]
1511pub(crate) fn stored_image_mismatch(
1512 datatype: &DatatypeMessage,
1513 width: usize,
1514) -> Option<&'static str> {
1515 match byte_order_action(datatype, width) {
1516 ByteOrderAction::SwapElements => return Some("they are stored in the foreign byte order"),
1517 ByteOrderAction::Refuse => {
1518 return Some("it is a composite storing members in the foreign byte order")
1519 }
1520 ByteOrderAction::Keep => {}
1521 }
1522 if crate::format::nbit_scaleoffset::datatype_needs_bit_conversion(datatype) {
1523 return Some("the significant bits do not fill the stored element");
1524 }
1525 None
1526}
1527
1528pub(crate) fn to_host_byte_order(
1541 bytes: &mut [u8],
1542 datatype: &DatatypeMessage,
1543 width: usize,
1544) -> Result<()> {
1545 match byte_order_action(datatype, width) {
1546 ByteOrderAction::Keep => {}
1547 ByteOrderAction::SwapElements => {
1548 for elem in bytes.chunks_exact_mut(width) {
1549 elem.reverse();
1550 }
1551 }
1552 ByteOrderAction::Refuse => {
1553 return Err(Hdf5Error::TypeMismatch(format!(
1554 "dataset datatype {datatype} stores {FOREIGN_BYTE_ORDER:?} values, which a \
1555 typed read cannot reinterpret element by element; read_raw_bytes() returns \
1556 the image to decode member by member"
1557 )))
1558 }
1559 }
1560 Ok(())
1561}
1562
1563pub(crate) fn vlen_sequence_images<'a, T: H5Type>(
1572 items: &'a [&'a [T]],
1573) -> Result<Vec<std::borrow::Cow<'a, [u8]>>> {
1574 let base = T::hdf5_type();
1575 items
1576 .iter()
1577 .map(|item| {
1578 let host = unsafe {
1583 std::slice::from_raw_parts(item.as_ptr() as *const u8, std::mem::size_of_val(*item))
1584 };
1585 to_stored_byte_order(host, &base, T::element_size())
1586 })
1587 .collect()
1588}
1589
1590pub(crate) fn to_stored_byte_order<'a>(
1604 bytes: &'a [u8],
1605 datatype: &DatatypeMessage,
1606 width: usize,
1607) -> Result<std::borrow::Cow<'a, [u8]>> {
1608 match byte_order_action(datatype, width) {
1609 ByteOrderAction::Keep => Ok(std::borrow::Cow::Borrowed(bytes)),
1610 ByteOrderAction::SwapElements => {
1611 let mut owned = bytes.to_vec();
1612 for elem in owned.chunks_exact_mut(width) {
1613 elem.reverse();
1614 }
1615 Ok(std::borrow::Cow::Owned(owned))
1616 }
1617 ByteOrderAction::Refuse => Err(Hdf5Error::TypeMismatch(format!(
1618 "dataset datatype {datatype} stores {FOREIGN_BYTE_ORDER:?} values, which a typed \
1619 write cannot lay out element by element; write_raw_bytes() takes the image the \
1620 caller encodes member by member"
1621 ))),
1622 }
1623}
1624
1625fn trim_fixed_string(elem: &[u8], padding: u8, index: usize) -> Result<&[u8]> {
1632 crate::format::messages::datatype::fixed_string_content(elem, padding).ok_or_else(|| {
1633 Hdf5Error::InvalidState(format!(
1634 "string {index} uses padding rule {padding}, which the format reserves"
1635 ))
1636 })
1637}
1638
1639fn decode_string(bytes: &[u8], charset: u8, lossy: bool, index: usize) -> Result<String> {
1644 if lossy {
1645 return Ok(String::from_utf8_lossy(bytes).into_owned());
1646 }
1647 match charset {
1648 0 => match bytes.iter().position(|&b| b >= 0x80) {
1650 None => Ok(String::from_utf8_lossy(bytes).into_owned()),
1651 Some(at) => Err(Hdf5Error::InvalidState(format!(
1652 "string {index} declares the ASCII character set but byte {at} is {:#04x}",
1653 bytes[at]
1654 ))),
1655 },
1656 1 => String::from_utf8(bytes.to_vec()).map_err(|e| {
1657 Hdf5Error::InvalidState(format!(
1658 "string {index} declares UTF-8 but is not valid UTF-8: {e}"
1659 ))
1660 }),
1661 other => Err(Hdf5Error::InvalidState(format!(
1662 "string {index} uses character set {other}, which the format reserves"
1663 ))),
1664 }
1665}
1666
1667#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1674pub enum StorageLayout {
1675 Compact,
1677 Contiguous,
1682 Chunked,
1686 Virtual,
1689}
1690
1691#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1700pub enum ChunkIndex {
1701 BtreeV1,
1704 BtreeV2,
1706 SingleChunk,
1709 Implicit,
1712 FixedArray,
1714 ExtensibleArray,
1716}
1717
1718#[derive(Debug, Clone, PartialEq, Eq)]
1721pub enum FillValue {
1722 Default,
1725 Undefined,
1728 UserDefined(Vec<u8>),
1730}
1731
1732#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1741pub enum FillTime {
1742 Alloc,
1744 Never,
1746 IfSet,
1750}
1751
1752impl FillTime {
1753 fn wire_byte(self) -> u8 {
1756 match self {
1757 Self::Alloc => 0,
1758 Self::Never => 1,
1759 Self::IfSet => 2,
1760 }
1761 }
1762}
1763
1764#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1776pub enum AllocTime {
1777 Early,
1779 Late,
1781 Incr,
1784}
1785
1786#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1793pub enum VirtualView {
1794 #[default]
1798 LastAvailable,
1799 FirstMissing,
1808}
1809
1810#[derive(Debug, Clone, PartialEq, Eq, Default)]
1835pub struct DatasetAccess {
1836 view: VirtualView,
1837 printf_gap: u64,
1838 virtual_prefix: Option<String>,
1839 efile_prefix: Option<String>,
1840}
1841
1842impl DatasetAccess {
1843 pub fn new() -> Self {
1848 Self::default()
1849 }
1850
1851 pub fn virtual_view(mut self, view: VirtualView) -> Self {
1855 self.view = view;
1856 self
1857 }
1858
1859 pub fn virtual_printf_gap(mut self, gap: u64) -> Self {
1868 self.printf_gap = gap;
1869 self
1870 }
1871
1872 pub fn view(&self) -> VirtualView {
1874 self.view
1875 }
1876
1877 pub fn virtual_prefix(mut self, prefix: impl Into<String>) -> Self {
1896 self.virtual_prefix = Some(prefix.into());
1897 self
1898 }
1899
1900 pub fn efile_prefix(mut self, prefix: impl Into<String>) -> Self {
1930 self.efile_prefix = Some(prefix.into());
1931 self
1932 }
1933
1934 pub fn printf_gap(&self) -> u64 {
1938 self.printf_gap
1939 }
1940
1941 pub fn virtual_prefix_value(&self) -> Option<&str> {
1946 self.virtual_prefix.as_deref()
1947 }
1948
1949 pub fn efile_prefix_value(&self) -> Option<&str> {
1954 self.efile_prefix.as_deref()
1955 }
1956
1957 pub(crate) fn effective_printf_gap(&self) -> u64 {
1965 match self.view {
1966 VirtualView::LastAvailable => self.printf_gap,
1967 VirtualView::FirstMissing => 0,
1968 }
1969 }
1970
1971 pub(crate) fn validate(&self) -> Result<()> {
1974 if self.printf_gap == u64::MAX {
1975 return Err(Hdf5Error::InvalidState(
1976 "virtual_printf_gap(u64::MAX) is libhdf5's HSIZE_UNDEF, which \
1977 H5Pset_virtual_printf_gap refuses as \"not a valid printf gap size\""
1978 .into(),
1979 ));
1980 }
1981 Ok(())
1982 }
1983}
1984
1985fn element_count(counts: &[u64]) -> Result<usize> {
1988 counts
1989 .iter()
1990 .try_fold(1usize, |acc, &c| {
1991 usize::try_from(c).ok().and_then(|c| acc.checked_mul(c))
1992 })
1993 .ok_or_else(|| {
1994 Hdf5Error::InvalidState(format!("selection {counts:?} has more elements than usize"))
1995 })
1996}
1997
1998impl H5Dataset {
1999 pub(crate) fn new_reader(
2001 file_inner: SharedInner,
2002 name: String,
2003 shape: Vec<usize>,
2004 element_size: usize,
2005 open: Option<crate::io::reader::DatasetOpenToken>,
2006 ) -> Self {
2007 Self {
2008 file_inner,
2009 info: DatasetInfo::Reader {
2010 name,
2011 shape,
2012 element_size,
2013 },
2014 _open: open,
2015 }
2016 }
2017
2018 pub(crate) fn new_writer(
2029 file_inner: SharedInner,
2030 index: usize,
2031 parts: crate::io::writer::DatasetHandleParts,
2032 ) -> Self {
2033 Self {
2034 file_inner,
2035 info: DatasetInfo::Writer {
2036 index,
2037 shape: parts.shape,
2038 element_size: parts.element_size,
2039 chunk_index: parts.chunk_index,
2040 is_null: false,
2041 },
2042 _open: parts.open,
2043 }
2044 }
2045
2046 pub fn shape(&self) -> Vec<usize> {
2048 match &self.info {
2049 DatasetInfo::Writer { shape, .. } => shape.clone(),
2050 DatasetInfo::Reader { shape, .. } => shape.clone(),
2051 }
2052 }
2053
2054 pub fn ndims(&self) -> usize {
2056 match &self.info {
2057 DatasetInfo::Writer { shape, .. } => shape.len(),
2058 DatasetInfo::Reader { shape, .. } => shape.len(),
2059 }
2060 }
2061
2062 pub fn total_elements(&self) -> usize {
2068 if self.is_null() {
2069 return 0;
2070 }
2071 match &self.info {
2072 DatasetInfo::Writer { shape, .. } => shape.iter().product(),
2073 DatasetInfo::Reader { shape, .. } => shape.iter().product(),
2074 }
2075 }
2076
2077 pub fn element_size(&self) -> usize {
2079 match &self.info {
2080 DatasetInfo::Writer { element_size, .. } => *element_size,
2081 DatasetInfo::Reader { element_size, .. } => *element_size,
2082 }
2083 }
2084
2085 pub fn is_null(&self) -> bool {
2090 match &self.info {
2091 DatasetInfo::Writer { is_null, .. } => *is_null,
2092 DatasetInfo::Reader { name, .. } => {
2093 let mut inner = borrow_inner_mut(&self.file_inner);
2094 match &mut *inner {
2095 H5FileInner::Reader(reader) => reader
2096 .dataset_info(name)
2097 .map(|info| info.dataspace.is_null())
2098 .unwrap_or(false),
2099 _ => false,
2100 }
2101 }
2102 }
2103 }
2104
2105 pub fn datatype(&self) -> Result<DatatypeMessage> {
2136 match &self.info {
2137 DatasetInfo::Reader { name, .. } => {
2138 let mut inner = borrow_inner_mut(&self.file_inner);
2139 match &mut *inner {
2140 H5FileInner::Reader(reader) => reader
2141 .dataset_info(name)
2142 .map(|info| info.datatype.clone())
2143 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2144 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2145 }
2146 }
2147 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2148 "datatype() is only available in read mode".into(),
2149 )),
2150 }
2151 }
2152
2153 pub fn chunk_dims(&self) -> Option<Vec<usize>> {
2155 match &self.info {
2156 DatasetInfo::Reader { name, .. } => {
2157 let mut inner = borrow_inner_mut(&self.file_inner);
2158 if let H5FileInner::Reader(reader) = &mut *inner {
2159 if let Some(info) = reader.dataset_info(name) {
2160 use crate::format::messages::data_layout::DataLayoutMessage;
2161 let chunk_dims = match &info.layout {
2162 DataLayoutMessage::ChunkedV4 { chunk_dims, .. }
2163 | DataLayoutMessage::ChunkedV3 { chunk_dims, .. } => Some(chunk_dims),
2164 _ => None,
2165 };
2166 if let Some(chunk_dims) = chunk_dims {
2167 return Some(
2169 chunk_dims[..chunk_dims.len() - 1]
2170 .iter()
2171 .map(|&d| d as usize)
2172 .collect(),
2173 );
2174 }
2175 }
2176 }
2177 None
2178 }
2179 DatasetInfo::Writer { .. } => None,
2180 }
2181 }
2182
2183 pub fn is_chunked(&self) -> bool {
2185 match &self.info {
2186 DatasetInfo::Writer { chunk_index, .. } => chunk_index.is_some(),
2187 DatasetInfo::Reader { name, .. } => {
2188 let mut inner = borrow_inner_mut(&self.file_inner);
2189 match &mut *inner {
2190 H5FileInner::Reader(reader) => {
2191 if let Some(info) = reader.dataset_info(name) {
2192 use crate::format::messages::data_layout::DataLayoutMessage;
2193 matches!(
2194 info.layout,
2195 DataLayoutMessage::ChunkedV4 { .. }
2196 | DataLayoutMessage::ChunkedV3 { .. }
2197 )
2198 } else {
2199 false
2200 }
2201 }
2202 _ => false,
2203 }
2204 }
2205 }
2206 }
2207
2208 pub fn storage_layout(&self) -> Result<StorageLayout> {
2215 match &self.info {
2216 DatasetInfo::Reader { name, .. } => {
2217 let mut inner = borrow_inner_mut(&self.file_inner);
2218 match &mut *inner {
2219 H5FileInner::Reader(reader) => {
2220 use crate::format::messages::data_layout::DataLayoutMessage;
2221 reader
2222 .dataset_info(name)
2223 .map(|info| match &info.layout {
2224 DataLayoutMessage::Compact { .. } => StorageLayout::Compact,
2225 DataLayoutMessage::Contiguous { .. } => StorageLayout::Contiguous,
2226 DataLayoutMessage::ChunkedV3 { .. }
2227 | DataLayoutMessage::ChunkedV4 { .. } => StorageLayout::Chunked,
2228 DataLayoutMessage::Virtual { .. } => StorageLayout::Virtual,
2229 })
2230 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))
2231 }
2232 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2233 }
2234 }
2235 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2236 "storage_layout() is only available in read mode".into(),
2237 )),
2238 }
2239 }
2240
2241 pub fn chunk_index(&self) -> Result<Option<ChunkIndex>> {
2249 match &self.info {
2250 DatasetInfo::Reader { name, .. } => {
2251 let mut inner = borrow_inner_mut(&self.file_inner);
2252 match &mut *inner {
2253 H5FileInner::Reader(reader) => {
2254 use crate::format::messages::data_layout::{
2255 ChunkIndexType, DataLayoutMessage,
2256 };
2257 reader
2258 .dataset_info(name)
2259 .map(|info| match &info.layout {
2260 DataLayoutMessage::ChunkedV3 { .. } => Some(ChunkIndex::BtreeV1),
2261 DataLayoutMessage::ChunkedV4 { index_type, .. } => {
2262 Some(match index_type {
2263 ChunkIndexType::SingleChunk => ChunkIndex::SingleChunk,
2264 ChunkIndexType::Implicit => ChunkIndex::Implicit,
2265 ChunkIndexType::FixedArray => ChunkIndex::FixedArray,
2266 ChunkIndexType::ExtensibleArray => {
2267 ChunkIndex::ExtensibleArray
2268 }
2269 ChunkIndexType::BTreeV2 => ChunkIndex::BtreeV2,
2270 })
2271 }
2272 _ => None,
2273 })
2274 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))
2275 }
2276 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2277 }
2278 }
2279 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2280 "chunk_index() is only available in read mode".into(),
2281 )),
2282 }
2283 }
2284
2285 pub fn filters(&self) -> Result<Vec<Filter>> {
2294 match &self.info {
2295 DatasetInfo::Reader { name, .. } => {
2296 let mut inner = borrow_inner_mut(&self.file_inner);
2297 match &mut *inner {
2298 H5FileInner::Reader(reader) => reader
2299 .dataset_info(name)
2300 .map(|info| {
2301 info.filter_pipeline
2302 .as_ref()
2303 .map(|fp| fp.filters.clone())
2304 .unwrap_or_default()
2305 })
2306 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2307 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2308 }
2309 }
2310 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2311 "filters() is only available in read mode".into(),
2312 )),
2313 }
2314 }
2315
2316 pub fn fill_value(&self) -> Result<FillValue> {
2323 match &self.info {
2324 DatasetInfo::Reader { name, .. } => {
2325 let mut inner = borrow_inner_mut(&self.file_inner);
2326 match &mut *inner {
2327 H5FileInner::Reader(reader) => reader
2328 .dataset_info(name)
2329 .map(|info| match info.fill_defined {
2330 0 => FillValue::Undefined,
2331 2 => {
2332 FillValue::UserDefined(info.fill_value.clone().unwrap_or_default())
2333 }
2334 _ => FillValue::Default,
2335 })
2336 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2337 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2338 }
2339 }
2340 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2341 "fill_value() is only available in read mode".into(),
2342 )),
2343 }
2344 }
2345
2346 pub fn fill_time(&self) -> Result<FillTime> {
2354 match &self.info {
2355 DatasetInfo::Reader { name, .. } => {
2356 let mut inner = borrow_inner_mut(&self.file_inner);
2357 match &mut *inner {
2358 H5FileInner::Reader(reader) => reader
2359 .dataset_info(name)
2360 .map(|info| match info.fill_write_time {
2361 0 => FillTime::Alloc,
2362 1 => FillTime::Never,
2363 _ => FillTime::IfSet,
2364 })
2365 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2366 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2367 }
2368 }
2369 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2370 "fill_time() is only available in read mode".into(),
2371 )),
2372 }
2373 }
2374
2375 pub fn alloc_time(&self) -> Result<AllocTime> {
2383 match &self.info {
2384 DatasetInfo::Reader { name, .. } => {
2385 let mut inner = borrow_inner_mut(&self.file_inner);
2386 match &mut *inner {
2387 H5FileInner::Reader(reader) => reader
2388 .dataset_info(name)
2389 .map(|info| match info.alloc_time {
2390 1 => AllocTime::Early,
2391 3 => AllocTime::Incr,
2392 _ => AllocTime::Late,
2393 })
2394 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2395 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2396 }
2397 }
2398 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2399 "alloc_time() is only available in read mode".into(),
2400 )),
2401 }
2402 }
2403
2404 pub fn external_files(&self) -> Result<Vec<ExternalFileSegment>> {
2413 match &self.info {
2414 DatasetInfo::Reader { name, .. } => {
2415 let mut inner = borrow_inner_mut(&self.file_inner);
2416 match &mut *inner {
2417 H5FileInner::Reader(reader) => reader
2418 .dataset_info(name)
2419 .map(|info| info.external_files.clone())
2420 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2421 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2422 }
2423 }
2424 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2425 "external_files() is only available in read mode".into(),
2426 )),
2427 }
2428 }
2429
2430 pub fn max_shape(&self) -> Result<Vec<Option<usize>>> {
2440 match &self.info {
2441 DatasetInfo::Reader { name, .. } => {
2442 let mut inner = borrow_inner_mut(&self.file_inner);
2443 match &mut *inner {
2444 H5FileInner::Reader(reader) => reader
2445 .dataset_info(name)
2446 .map(|info| match &info.dataspace.max_dims {
2447 Some(max_dims) => max_dims
2448 .iter()
2449 .map(|&d| (d != u64::MAX).then_some(d as usize))
2450 .collect(),
2451 None => info
2452 .dataspace
2453 .dims
2454 .iter()
2455 .map(|&d| Some(d as usize))
2456 .collect(),
2457 })
2458 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2459 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2460 }
2461 }
2462 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2463 "max_shape() is only available in read mode".into(),
2464 )),
2465 }
2466 }
2467
2468 pub fn virtual_mappings(&self) -> Result<Vec<VirtualMapping>> {
2477 match &self.info {
2478 DatasetInfo::Reader { name, .. } => {
2479 let mut inner = borrow_inner_mut(&self.file_inner);
2480 match &mut *inner {
2481 H5FileInner::Reader(reader) => reader
2482 .dataset_info(name)
2483 .map(|info| {
2484 info.virtual_mappings
2485 .as_ref()
2486 .map(|vml| vml.mappings.clone())
2487 .unwrap_or_default()
2488 })
2489 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2490 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2491 }
2492 }
2493 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2494 "virtual_mappings() is only available in read mode".into(),
2495 )),
2496 }
2497 }
2498
2499 pub fn attr_names(&self) -> Result<Vec<String>> {
2501 match &self.info {
2502 DatasetInfo::Reader { name, .. } => {
2503 let mut inner = borrow_inner_mut(&self.file_inner);
2504 match &mut *inner {
2505 H5FileInner::Reader(reader) => Ok(reader.dataset_attr_names(name)?),
2506 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2507 }
2508 }
2509 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2510 "attr_names not available in write mode".into(),
2511 )),
2512 }
2513 }
2514
2515 pub fn attr_unreadable_reason(&self, attr_name: &str) -> Result<Option<String>> {
2523 match &self.info {
2524 DatasetInfo::Reader { name, .. } => {
2525 let mut inner = borrow_inner_mut(&self.file_inner);
2526 match &mut *inner {
2527 H5FileInner::Reader(reader) => Ok(reader
2528 .dataset_attr_unreadable_reason(name, attr_name)
2529 .map(str::to_string)),
2530 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2531 }
2532 }
2533 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2534 "attr_unreadable_reason not available in write mode".into(),
2535 )),
2536 }
2537 }
2538
2539 pub fn attrs_unreadable_reason(&self) -> Result<Option<String>> {
2549 match &self.info {
2550 DatasetInfo::Reader { name, .. } => {
2551 let mut inner = borrow_inner_mut(&self.file_inner);
2552 match &mut *inner {
2553 H5FileInner::Reader(reader) => Ok(reader
2554 .dataset_attrs_unreadable_reason(name)
2555 .map(str::to_string)),
2556 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2557 }
2558 }
2559 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2560 "attrs_unreadable_reason not available in write mode".into(),
2561 )),
2562 }
2563 }
2564
2565 pub fn attr_storage(&self) -> Result<AttributeStorage> {
2569 match &self.info {
2570 DatasetInfo::Reader { name, .. } => {
2571 let mut inner = borrow_inner_mut(&self.file_inner);
2572 match &mut *inner {
2573 H5FileInner::Reader(reader) => Ok(reader.dataset_attr_storage(name)?),
2574 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2575 }
2576 }
2577 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2578 "attr_storage not available in write mode".into(),
2579 )),
2580 }
2581 }
2582
2583 pub fn header_attr_count(&self) -> Result<u64> {
2586 match &self.info {
2587 DatasetInfo::Reader { name, .. } => {
2588 let mut inner = borrow_inner_mut(&self.file_inner);
2589 match &mut *inner {
2590 H5FileInner::Reader(reader) => Ok(reader.dataset_header_attr_count(name)?),
2591 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2592 }
2593 }
2594 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2595 "header_attr_count not available in write mode".into(),
2596 )),
2597 }
2598 }
2599
2600 pub fn attr(&self, attr_name: &str) -> Result<crate::attribute::H5Attribute> {
2602 match &self.info {
2603 DatasetInfo::Reader { name, .. } => {
2604 let mut inner = borrow_inner_mut(&self.file_inner);
2605 match &mut *inner {
2606 H5FileInner::Reader(reader) => {
2607 let attr_msg = reader.dataset_attr(name, attr_name)?.clone();
2608 Ok(crate::attribute::H5Attribute::new_reader(
2609 clone_inner(&self.file_inner),
2610 attr_msg,
2611 ))
2612 }
2613 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2614 }
2615 }
2616 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2617 "attr() not available in write mode".into(),
2618 )),
2619 }
2620 }
2621
2622 pub fn new_attr<T: 'static>(&self) -> AttrBuilder<'_, T> {
2638 let ds_index = match &self.info {
2639 DatasetInfo::Writer { index, .. } => *index,
2640 DatasetInfo::Reader { .. } => {
2641 usize::MAX
2644 }
2645 };
2646 AttrBuilder::new(&self.file_inner, ds_index)
2647 }
2648
2649 pub fn set_attr_typed(
2655 &self,
2656 name: &str,
2657 datatype: DatatypeMessage,
2658 value: Vec<u8>,
2659 ) -> Result<()> {
2660 let attr = crate::attribute::typed_scalar_attr(name, datatype, value)?;
2661 let DatasetInfo::Writer { index, .. } = &self.info else {
2662 return Err(Hdf5Error::InvalidState(
2663 "cannot write attributes in read mode".into(),
2664 ));
2665 };
2666 match &*borrow_inner(&self.file_inner) {
2667 H5FileInner::Writer(writer) => {
2668 writer.add_dataset_attribute(*index, attr)?;
2669 Ok(())
2670 }
2671 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
2672 "cannot write attributes in read mode".into(),
2673 )),
2674 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
2675 }
2676 }
2677
2678 pub fn write_raw<T: H5Type>(&self, data: &[T]) -> Result<()> {
2693 match &self.info {
2694 DatasetInfo::Writer {
2695 index,
2696 shape,
2697 element_size,
2698 chunk_index,
2699 is_null,
2700 } => {
2701 if *is_null {
2702 return Err(Hdf5Error::InvalidState(
2703 "cannot write to a NULL dataspace dataset".into(),
2704 ));
2705 }
2706 let total_elements: usize = shape.iter().product();
2707 if data.len() != total_elements {
2708 return Err(Hdf5Error::InvalidState(format!(
2709 "data length {} does not match dataset size {}",
2710 data.len(),
2711 total_elements,
2712 )));
2713 }
2714
2715 if T::element_size() != *element_size {
2717 return Err(Hdf5Error::TypeMismatch(format!(
2718 "write type has element size {} but dataset expects {}",
2719 T::element_size(),
2720 element_size,
2721 )));
2722 }
2723
2724 let byte_len = data.len() * T::element_size();
2728 let host =
2729 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) };
2730 let datatype = {
2731 let inner = borrow_inner(&self.file_inner);
2732 match &*inner {
2733 H5FileInner::Writer(writer) => writer.dataset_datatype(*index),
2734 _ => {
2735 return Err(Hdf5Error::InvalidState(
2736 "file is no longer in write mode".into(),
2737 ))
2738 }
2739 }
2740 };
2741 let stored = to_stored_byte_order(host, &datatype, T::element_size())?;
2742
2743 if let Some(kind) = *chunk_index {
2744 return self.write_full_image_chunked(*index, kind, &stored, *element_size);
2748 }
2749
2750 let inner = borrow_inner(&self.file_inner);
2751 match &*inner {
2752 H5FileInner::Writer(writer) => {
2753 writer.write_dataset_raw(*index, &stored)?;
2754 Ok(())
2755 }
2756 _ => Err(Hdf5Error::InvalidState(
2757 "file is no longer in write mode".into(),
2758 )),
2759 }
2760 }
2761 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
2762 "cannot write to a dataset opened in read mode".into(),
2763 )),
2764 }
2765 }
2766
2767 pub fn write_raw_bytes(&self, bytes: &[u8]) -> Result<()> {
2805 match &self.info {
2806 DatasetInfo::Writer {
2807 index,
2808 shape,
2809 element_size,
2810 chunk_index,
2811 is_null,
2812 } => {
2813 if *is_null {
2814 return Err(Hdf5Error::InvalidState(
2815 "cannot write to a NULL dataspace dataset".into(),
2816 ));
2817 }
2818 let expected: usize = shape.iter().product::<usize>() * *element_size;
2819 if bytes.len() != expected {
2820 return Err(Hdf5Error::InvalidState(format!(
2821 "raw byte length {} does not match dataset size {} \
2822 (product(shape) * element_size {})",
2823 bytes.len(),
2824 expected,
2825 element_size,
2826 )));
2827 }
2828 if let Some(kind) = *chunk_index {
2829 return self.write_full_image_chunked(*index, kind, bytes, *element_size);
2832 }
2833 let inner = borrow_inner(&self.file_inner);
2834 match &*inner {
2835 H5FileInner::Writer(writer) => {
2836 writer.write_dataset_raw(*index, bytes)?;
2837 Ok(())
2838 }
2839 _ => Err(Hdf5Error::InvalidState(
2840 "file is no longer in write mode".into(),
2841 )),
2842 }
2843 }
2844 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
2845 "cannot write to a dataset opened in read mode".into(),
2846 )),
2847 }
2848 }
2849
2850 fn write_full_image_chunked(
2860 &self,
2861 index: usize,
2862 kind: ChunkIndexKind,
2863 bytes: &[u8],
2864 element_size: usize,
2865 ) -> Result<()> {
2866 let inner = borrow_inner(&self.file_inner);
2867 let writer = match &*inner {
2868 H5FileInner::Writer(w) => w,
2869 _ => {
2870 return Err(Hdf5Error::InvalidState(
2871 "file is no longer in write mode".into(),
2872 ))
2873 }
2874 };
2875 let cell = writer.ds(index);
2879 let _op = cell.op.lock();
2880 writer.flush_append_buffer(index)?;
2883 let chunk_dims = writer
2884 .dataset_chunk_dims(index)
2885 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
2886 .to_vec();
2887 let dims = writer.dataset_dims(index).to_vec();
2888 let rank = dims.len();
2889
2890 let mut grid = vec![0u64; rank];
2892 for d in 0..rank {
2893 grid[d] = if chunk_dims[d] > 0 {
2894 dims[d].div_ceil(chunk_dims[d])
2895 } else {
2896 0
2897 };
2898 }
2899 let total_chunks: u64 = grid.iter().product();
2900
2901 let coords_of = |linear: u64| -> Vec<u64> {
2907 let mut rem = linear;
2908 let mut coords = vec![0u64; rank];
2909 for d in (0..rank).rev() {
2910 coords[d] = rem % grid[d];
2911 rem /= grid[d];
2912 }
2913 coords
2914 };
2915
2916 let batched = writer.dataset_is_filtered(index)
2922 && matches!(
2923 kind,
2924 ChunkIndexKind::ExtensibleArray | ChunkIndexKind::FixedArray
2925 );
2926 if !batched {
2927 let mut staging = Vec::new();
2933 for linear in 0..total_chunks {
2934 let coords = coords_of(linear);
2935 let chunk =
2936 match Self::contiguous_chunk_span(&dims, &chunk_dims, &coords, element_size) {
2937 Some(span) => &bytes[span],
2938 None => {
2939 Self::gather_chunk_into(
2940 &mut staging,
2941 bytes,
2942 &dims,
2943 &chunk_dims,
2944 &coords,
2945 element_size,
2946 );
2947 &staging[..]
2948 }
2949 };
2950 writer.write_chunk_at_coords(index, &coords, chunk)?;
2951 }
2952 } else {
2953 const BATCH_WINDOW: u64 = 256;
2964 let mut start = 0u64;
2965 while start < total_chunks {
2966 let end = (start + BATCH_WINDOW).min(total_chunks);
2967 let items: Vec<(Vec<u64>, Cow<'_, [u8]>)> = (start..end)
2968 .map(|counter| {
2969 let coords = coords_of(counter);
2970 let data = match Self::contiguous_chunk_span(
2971 &dims,
2972 &chunk_dims,
2973 &coords,
2974 element_size,
2975 ) {
2976 Some(span) => Cow::Borrowed(&bytes[span]),
2977 None => {
2978 let mut buf = Vec::new();
2979 Self::gather_chunk_into(
2980 &mut buf,
2981 bytes,
2982 &dims,
2983 &chunk_dims,
2984 &coords,
2985 element_size,
2986 );
2987 Cow::Owned(buf)
2988 }
2989 };
2990 (coords, data)
2991 })
2992 .collect();
2993 if kind == ChunkIndexKind::FixedArray {
2994 let pairs: Vec<(&[u64], &[u8])> = items
2995 .iter()
2996 .map(|(c, d)| (c.as_slice(), d.as_ref()))
2997 .collect();
2998 writer.write_chunks_fixed_array_batch_inner(index, &pairs)?;
2999 } else {
3000 let mut pairs: Vec<(u64, &[u8])> = Vec::with_capacity(items.len());
3001 for (c, d) in &items {
3002 pairs.push((writer.chunk_slot(index, c)?, d.as_ref()));
3003 }
3004 writer.write_chunks_batch_inner(index, &pairs)?;
3005 }
3006 start = end;
3007 }
3008 }
3009 Ok(())
3010 }
3011
3012 fn contiguous_chunk_span(
3024 dims: &[u64],
3025 chunk_dims: &[u64],
3026 coords: &[u64],
3027 element_size: usize,
3028 ) -> Option<std::ops::Range<usize>> {
3029 let rank = dims.len();
3030 if rank == 0 || chunk_dims[1..] != dims[1..] {
3031 return None;
3032 }
3033 if (coords[0] + 1) * chunk_dims[0] > dims[0] {
3034 return None;
3035 }
3036 let plane: u64 = dims[1..].iter().product::<u64>() * element_size as u64;
3037 let start = usize::try_from(coords[0] * chunk_dims[0] * plane).ok()?;
3038 let len = usize::try_from(chunk_dims[0] * plane).ok()?;
3039 Some(start..start.checked_add(len)?)
3040 }
3041
3042 fn gather_chunk_into(
3050 out: &mut Vec<u8>,
3051 source: &[u8],
3052 dims: &[u64],
3053 chunk_dims: &[u64],
3054 coords: &[u64],
3055 element_size: usize,
3056 ) {
3057 let rank = dims.len();
3058 let chunk_elems: u64 = chunk_dims.iter().product();
3059 let chunk_bytes = chunk_elems as usize * element_size;
3060 if rank == 0 {
3061 out.clear();
3063 out.resize(chunk_bytes, 0);
3064 if source.len() >= element_size {
3065 out[..element_size].copy_from_slice(&source[..element_size]);
3066 }
3067 return;
3068 }
3069
3070 let mut extent = vec![0u64; rank];
3073 for d in 0..rank {
3074 let start = coords[d] * chunk_dims[d];
3075 let end = ((coords[d] + 1) * chunk_dims[d]).min(dims[d]);
3076 extent[d] = end.saturating_sub(start);
3077 }
3078 if out.len() != chunk_bytes {
3083 out.clear();
3084 out.resize(chunk_bytes, 0);
3085 } else if extent != chunk_dims {
3086 out.fill(0);
3087 }
3088 if extent.contains(&0) {
3089 return; }
3091
3092 let mut src_stride = vec![1u64; rank];
3095 let mut dst_stride = vec![1u64; rank];
3096 for d in (0..rank - 1).rev() {
3097 src_stride[d] = src_stride[d + 1] * dims[d + 1];
3098 dst_stride[d] = dst_stride[d + 1] * chunk_dims[d + 1];
3099 }
3100
3101 let last = rank - 1;
3103 let run = extent[last] as usize * element_size;
3104 let outer: u64 = extent[..last].iter().product::<u64>().max(1);
3105 let mut idx = vec![0u64; rank]; for _ in 0..outer {
3107 let mut src_off = 0u64;
3108 let mut dst_off = 0u64;
3109 for d in 0..rank {
3110 let global = coords[d] * chunk_dims[d] + idx[d];
3111 src_off += global * src_stride[d];
3112 dst_off += idx[d] * dst_stride[d];
3113 }
3114 let s = src_off as usize * element_size;
3115 let dpos = dst_off as usize * element_size;
3116 out[dpos..dpos + run].copy_from_slice(&source[s..s + run]);
3117
3118 let mut d = last;
3121 while d > 0 {
3122 d -= 1;
3123 idx[d] += 1;
3124 if idx[d] < extent[d] {
3125 break;
3126 }
3127 idx[d] = 0;
3128 }
3129 }
3130 }
3131
3132 pub fn write_chunk(&self, chunk_idx: usize, data: &[u8]) -> Result<()> {
3140 match &self.info {
3141 DatasetInfo::Writer {
3142 index, chunk_index, ..
3143 } => {
3144 let Some(kind) = *chunk_index else {
3145 return Err(Hdf5Error::InvalidState(
3146 "write_chunk is only for chunked datasets".into(),
3147 ));
3148 };
3149 if kind == ChunkIndexKind::BtreeV2 {
3150 return Err(Hdf5Error::InvalidState(
3151 "this dataset uses a v2 B-tree chunk index; use write_chunk_at \
3152 with the chunk's grid coordinates"
3153 .into(),
3154 ));
3155 }
3156
3157 let inner = borrow_inner(&self.file_inner);
3158 match &*inner {
3159 H5FileInner::Writer(writer) => {
3160 let cell = writer.ds(*index);
3163 let _op = cell.op.lock();
3164 match kind {
3165 ChunkIndexKind::FixedArray
3169 | ChunkIndexKind::Implicit
3170 | ChunkIndexKind::SingleChunk
3171 | ChunkIndexKind::BtreeV1 => {
3172 let coords =
3173 writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3174 writer.write_chunk_at_coords(*index, &coords, data)?;
3175 }
3176 _ => writer.write_chunk_inner(*index, chunk_idx as u64, data)?,
3177 }
3178 Ok(())
3179 }
3180 _ => Err(Hdf5Error::InvalidState(
3181 "file is no longer in write mode".into(),
3182 )),
3183 }
3184 }
3185 DatasetInfo::Reader { .. } => {
3186 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3187 }
3188 }
3189 }
3190
3191 pub fn write_chunk_raw(&self, chunk_idx: usize, data: &[u8], filter_mask: u32) -> Result<()> {
3218 match &self.info {
3219 DatasetInfo::Writer {
3220 index, chunk_index, ..
3221 } => {
3222 let Some(kind) = *chunk_index else {
3223 return Err(Hdf5Error::InvalidState(
3224 "write_chunk_raw is only for chunked datasets".into(),
3225 ));
3226 };
3227 if kind == ChunkIndexKind::BtreeV2 {
3228 return Err(Hdf5Error::InvalidState(
3229 "this dataset uses a v2 B-tree chunk index; use \
3230 write_chunk_raw_at with the chunk's grid coordinates"
3231 .into(),
3232 ));
3233 }
3234 if kind == ChunkIndexKind::Implicit {
3235 return Err(Hdf5Error::InvalidState(
3236 "this dataset uses the implicit chunk index, which stores \
3237 every chunk at its full unfiltered size and has nowhere to \
3238 record a stored size or a filter mask"
3239 .into(),
3240 ));
3241 }
3242
3243 let inner = borrow_inner(&self.file_inner);
3244 match &*inner {
3245 H5FileInner::Writer(writer) => {
3246 let cell = writer.ds(*index);
3249 let _op = cell.op.lock();
3250 if kind == ChunkIndexKind::FixedArray {
3251 let coords = writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3254 writer.write_compressed_chunk_fixed_array_inner(
3255 *index,
3256 &coords,
3257 data,
3258 filter_mask,
3259 )?;
3260 } else if kind == ChunkIndexKind::BtreeV1 {
3261 let coords = writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3264 writer.write_compressed_chunk_btree_v1_inner(
3265 *index,
3266 &coords,
3267 data,
3268 filter_mask,
3269 )?;
3270 } else if kind == ChunkIndexKind::SingleChunk {
3271 let coords = writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3275 writer.write_compressed_chunk_single_chunk_inner(
3276 *index,
3277 &coords,
3278 data,
3279 filter_mask,
3280 )?;
3281 } else {
3282 writer.write_compressed_chunk_inner(
3283 *index,
3284 chunk_idx as u64,
3285 data,
3286 filter_mask,
3287 )?;
3288 }
3289 Ok(())
3290 }
3291 _ => Err(Hdf5Error::InvalidState(
3292 "file is no longer in write mode".into(),
3293 )),
3294 }
3295 }
3296 DatasetInfo::Reader { .. } => {
3297 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3298 }
3299 }
3300 }
3301
3302 pub fn write_chunk_at(&self, chunk_coords: &[usize], data: &[u8]) -> Result<()> {
3323 self.write_chunk_at_inner(chunk_coords, ChunkBytes::Unfiltered(data), "write_chunk_at")
3324 }
3325
3326 pub fn write_chunk_raw_at(
3344 &self,
3345 chunk_coords: &[usize],
3346 data: &[u8],
3347 filter_mask: u32,
3348 ) -> Result<()> {
3349 self.write_chunk_at_inner(
3350 chunk_coords,
3351 ChunkBytes::Prefiltered { data, filter_mask },
3352 "write_chunk_raw_at",
3353 )
3354 }
3355
3356 fn write_chunk_at_inner(
3362 &self,
3363 chunk_coords: &[usize],
3364 bytes: ChunkBytes<'_>,
3365 what: &str,
3366 ) -> Result<()> {
3367 match &self.info {
3368 DatasetInfo::Writer {
3369 index, chunk_index, ..
3370 } => {
3371 let Some(kind) = *chunk_index else {
3372 return Err(Hdf5Error::InvalidState(format!(
3373 "{what} is only for chunked datasets"
3374 )));
3375 };
3376 let coords: Vec<u64> = chunk_coords.iter().map(|&c| c as u64).collect();
3377 let inner = borrow_inner(&self.file_inner);
3378 let writer = match &*inner {
3379 H5FileInner::Writer(w) => w,
3380 _ => {
3381 return Err(Hdf5Error::InvalidState(
3382 "file is no longer in write mode".into(),
3383 ))
3384 }
3385 };
3386 let cell = writer.ds(*index);
3390 let _op = cell.op.lock();
3391 let dims = writer.dataset_dims(*index).to_vec();
3392 let mut new_dims = dims.clone();
3405 if !matches!(
3406 kind,
3407 ChunkIndexKind::FixedArray
3408 | ChunkIndexKind::Implicit
3409 | ChunkIndexKind::SingleChunk
3410 ) {
3411 let chunk_dims = writer.dataset_chunk_dims(*index).ok_or_else(|| {
3412 Hdf5Error::InvalidState("dataset has no chunk info".into())
3413 })?;
3414 let max_dims = writer.dataset_max_dims(*index);
3415 for (d, new) in new_dims.iter_mut().enumerate() {
3416 let (Some(&c), Some(&cd), Some(&max)) =
3417 (coords.get(d), chunk_dims.get(d), max_dims.get(d))
3418 else {
3419 break;
3420 };
3421 let needed = c
3422 .checked_add(1)
3423 .and_then(|c| c.checked_mul(cd))
3424 .ok_or_else(|| {
3425 Hdf5Error::InvalidState(format!(
3426 "chunk coordinate {c} in dimension {d} is too large"
3427 ))
3428 })?
3429 .min(max);
3430 *new = (*new).max(needed);
3431 }
3432 }
3433 writer.write_chunk_at_coords_inner(*index, &coords, bytes)?;
3434 if new_dims != dims {
3435 writer.extend_dataset_inner(*index, &new_dims)?;
3436 }
3437 Ok(())
3438 }
3439 DatasetInfo::Reader { .. } => {
3440 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3441 }
3442 }
3443 }
3444
3445 pub fn write_chunks_batch(&self, chunks: &[(usize, &[u8])]) -> Result<()> {
3451 match &self.info {
3452 DatasetInfo::Writer {
3453 index, chunk_index, ..
3454 } => {
3455 if chunk_index.is_none() {
3456 return Err(Hdf5Error::InvalidState(
3457 "write_chunks_batch is only for chunked datasets".into(),
3458 ));
3459 }
3460 let pairs: Vec<(u64, &[u8])> = chunks
3461 .iter()
3462 .map(|(idx, data)| (*idx as u64, *data))
3463 .collect();
3464 let inner = borrow_inner(&self.file_inner);
3465 match &*inner {
3466 H5FileInner::Writer(writer) => {
3467 writer.write_chunks_batch(*index, &pairs)?;
3468 Ok(())
3469 }
3470 _ => Err(Hdf5Error::InvalidState(
3471 "file is no longer in write mode".into(),
3472 )),
3473 }
3474 }
3475 DatasetInfo::Reader { .. } => {
3476 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3477 }
3478 }
3479 }
3480
3481 pub fn append<T: H5Type>(&self, data: &[T]) -> Result<()> {
3504 match &self.info {
3505 DatasetInfo::Writer {
3506 index,
3507 element_size,
3508 chunk_index,
3509 ..
3510 } => {
3511 if chunk_index.is_none() {
3512 return Err(Hdf5Error::InvalidState(
3513 "append is only for chunked datasets".into(),
3514 ));
3515 }
3516 if T::element_size() != *element_size {
3517 return Err(Hdf5Error::TypeMismatch(format!(
3518 "append type has element size {} but dataset expects {}",
3519 T::element_size(),
3520 element_size,
3521 )));
3522 }
3523
3524 let ds_index = *index;
3525 let es = *element_size;
3526
3527 let inner = borrow_inner(&self.file_inner);
3528 let writer = match &*inner {
3529 H5FileInner::Writer(w) => w,
3530 _ => {
3531 return Err(Hdf5Error::InvalidState(
3532 "file is no longer in write mode".into(),
3533 ))
3534 }
3535 };
3536
3537 let cell = writer.ds(ds_index);
3542 let _op = cell.op.lock();
3543
3544 let chunk_dims = writer
3545 .dataset_chunk_dims(ds_index)
3546 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
3547 .to_vec();
3548 let dims = writer.dataset_dims(ds_index).to_vec();
3549
3550 let frame_elems: usize = if dims.len() > 1 {
3552 dims[1..].iter().map(|&d| d as usize).product()
3553 } else {
3554 1
3555 };
3556
3557 if frame_elems == 0 {
3558 return Err(Hdf5Error::InvalidState(
3559 "cannot append to dataset with zero-size trailing dimensions".into(),
3560 ));
3561 }
3562
3563 if !data.len().is_multiple_of(frame_elems) {
3564 return Err(Hdf5Error::InvalidState(format!(
3565 "data length {} is not a multiple of frame size {}",
3566 data.len(),
3567 frame_elems,
3568 )));
3569 }
3570
3571 let n_new_frames = data.len() / frame_elems;
3572 let current_dim0 = dims[0] as usize;
3573
3574 let chunk_dim0 = chunk_dims[0] as usize;
3576 let frame_bytes = frame_elems * es;
3577
3578 let host = unsafe {
3579 std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * es)
3580 };
3581 let datatype = writer.dataset_datatype(ds_index);
3582 let raw = to_stored_byte_order(host, &datatype, es)?;
3583
3584 let taken = { writer.ds(ds_index).lock().append.take() };
3589 let (base_dim0, buffered_frames, mut combined) = match taken {
3590 Some(b) if b.base + b.frames == current_dim0 as u64 => {
3591 (b.base as usize, b.frames as usize, b.bytes)
3592 }
3593 Some(b) => {
3594 writer.write_append_frames(ds_index, b.base, b.frames, &b.bytes)?;
3595 (current_dim0, 0, Vec::new())
3596 }
3597 None => (current_dim0, 0, Vec::new()),
3598 };
3599 combined.extend_from_slice(&raw);
3600
3601 let total_frames = buffered_frames + n_new_frames;
3602
3603 let last_boundary = ((base_dim0 + total_frames) / chunk_dim0) * chunk_dim0;
3610 let write_frames = last_boundary.saturating_sub(base_dim0);
3611 let tail_frames = total_frames - write_frames;
3612 if write_frames > 0 {
3613 writer.write_append_frames(
3614 ds_index,
3615 base_dim0 as u64,
3616 write_frames as u64,
3617 &combined[..write_frames * frame_bytes],
3618 )?;
3619 }
3620 if tail_frames > 0 {
3621 let ds = writer.ds(ds_index);
3622 let mut m = ds.lock();
3623 m.append = Some(crate::io::writer::AppendBuffer {
3624 base: (base_dim0 + write_frames) as u64,
3625 frames: tail_frames as u64,
3626 bytes: combined[write_frames * frame_bytes..].to_vec(),
3627 });
3628 }
3629
3630 let logical_dim0 = base_dim0 + total_frames;
3632 let mut new_dims: Vec<u64> = dims;
3633 new_dims[0] = logical_dim0 as u64;
3634 writer.extend_dataset_inner(ds_index, &new_dims)?;
3635
3636 Ok(())
3637 }
3638 DatasetInfo::Reader { .. } => {
3639 Err(Hdf5Error::InvalidState("cannot append in read mode".into()))
3640 }
3641 }
3642 }
3643
3644 pub fn extend(&self, new_dims: &[usize]) -> Result<()> {
3646 match &self.info {
3647 DatasetInfo::Writer {
3648 index, chunk_index, ..
3649 } => {
3650 if chunk_index.is_none() {
3651 return Err(Hdf5Error::InvalidState(
3652 "extend is only for chunked datasets".into(),
3653 ));
3654 }
3655
3656 let dims_u64: Vec<u64> = new_dims.iter().map(|&d| d as u64).collect();
3657 let inner = borrow_inner(&self.file_inner);
3658 match &*inner {
3659 H5FileInner::Writer(writer) => {
3660 writer.extend_dataset(*index, &dims_u64)?;
3661 Ok(())
3662 }
3663 _ => Err(Hdf5Error::InvalidState(
3664 "file is no longer in write mode".into(),
3665 )),
3666 }
3667 }
3668 DatasetInfo::Reader { .. } => {
3669 Err(Hdf5Error::InvalidState("cannot extend in read mode".into()))
3670 }
3671 }
3672 }
3673
3674 pub fn set_extent(&self, new_dims: &[usize]) -> Result<()> {
3688 match &self.info {
3689 DatasetInfo::Writer { index, .. } => {
3690 let dims_u64: Vec<u64> = new_dims.iter().map(|&d| d as u64).collect();
3691 let inner = borrow_inner(&self.file_inner);
3692 match &*inner {
3693 H5FileInner::Writer(writer) => {
3694 writer.set_dataset_extent(*index, &dims_u64)?;
3695 Ok(())
3696 }
3697 _ => Err(Hdf5Error::InvalidState(
3698 "file is no longer in write mode".into(),
3699 )),
3700 }
3701 }
3702 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
3703 "cannot set extent in read mode".into(),
3704 )),
3705 }
3706 }
3707
3708 pub fn flush(&self) -> Result<()> {
3710 match &self.info {
3711 DatasetInfo::Writer { index, .. } => {
3712 let inner = borrow_inner(&self.file_inner);
3713 match &*inner {
3714 H5FileInner::Writer(writer) => {
3715 writer.flush_dataset(*index)?;
3716 Ok(())
3717 }
3718 _ => Ok(()),
3719 }
3720 }
3721 DatasetInfo::Reader { .. } => Ok(()),
3722 }
3723 }
3724
3725 pub fn read_slice<T: H5Type>(&self, starts: &[usize], counts: &[usize]) -> Result<Vec<T>> {
3730 match &self.info {
3731 DatasetInfo::Reader {
3732 name,
3733 shape,
3734 element_size,
3735 } => {
3736 if T::element_size() != *element_size {
3737 return Err(Hdf5Error::TypeMismatch(format!(
3738 "read type has element size {} but dataset has element size {}",
3739 T::element_size(),
3740 element_size,
3741 )));
3742 }
3743 let datatype = self.datatype()?;
3744 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
3745 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
3746
3747 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
3751 check_hyperslab(&dims, &starts_u64, &counts_u64)?;
3752 let count = element_count(&counts_u64)?;
3753 let mut inner = borrow_inner_mut(&self.file_inner);
3754 let H5FileInner::Reader(reader) = &mut *inner else {
3755 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
3756 };
3757 read_image_into_new(count, |image| {
3761 reader.read_slice_into_dst(
3762 name,
3763 &starts_u64,
3764 &counts_u64,
3765 image,
3766 ReadDst::Fresh,
3767 )?;
3768 to_host_byte_order(image, &datatype, T::element_size())
3769 })
3770 }
3771 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
3772 "cannot read_slice from a dataset in write mode".into(),
3773 )),
3774 }
3775 }
3776
3777 pub fn read_hyperslab<T: H5Type>(
3796 &self,
3797 start: &[usize],
3798 stride: &[usize],
3799 count: &[usize],
3800 block: &[usize],
3801 ) -> Result<Vec<T>> {
3802 match &self.info {
3803 DatasetInfo::Reader {
3804 name, element_size, ..
3805 } => {
3806 if T::element_size() != *element_size {
3807 return Err(Hdf5Error::TypeMismatch(format!(
3808 "read type has element size {} but dataset has element size {}",
3809 T::element_size(),
3810 element_size,
3811 )));
3812 }
3813 let datatype = self.datatype()?;
3814 let start_u64: Vec<u64> = start.iter().map(|&s| s as u64).collect();
3815 let stride_u64: Vec<u64> = stride.iter().map(|&s| s as u64).collect();
3816 let count_u64: Vec<u64> = count.iter().map(|&c| c as u64).collect();
3817 let block_u64: Vec<u64> = block.iter().map(|&b| b as u64).collect();
3818
3819 let selected: Vec<u64> = count_u64
3820 .iter()
3821 .zip(&block_u64)
3822 .map(|(&c, &b)| c.saturating_mul(b))
3823 .collect();
3824 let n = element_count(&selected)?;
3825 let mut inner = borrow_inner_mut(&self.file_inner);
3826 let H5FileInner::Reader(reader) = &mut *inner else {
3827 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
3828 };
3829 read_image_into_new(n, |image| {
3830 reader.read_hyperslab_into(
3831 name,
3832 &start_u64,
3833 &stride_u64,
3834 &count_u64,
3835 &block_u64,
3836 image,
3837 )?;
3838 to_host_byte_order(image, &datatype, T::element_size())
3839 })
3840 }
3841 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
3842 "cannot read_hyperslab from a dataset in write mode".into(),
3843 )),
3844 }
3845 }
3846
3847 pub fn read_points<T: H5Type>(&self, points: &[Vec<usize>]) -> Result<Vec<T>> {
3862 match &self.info {
3863 DatasetInfo::Reader {
3864 name, element_size, ..
3865 } => {
3866 if T::element_size() != *element_size {
3867 return Err(Hdf5Error::TypeMismatch(format!(
3868 "read type has element size {} but dataset has element size {}",
3869 T::element_size(),
3870 element_size,
3871 )));
3872 }
3873 let datatype = self.datatype()?;
3874 let points_u64: Vec<Vec<u64>> = points
3875 .iter()
3876 .map(|p| p.iter().map(|&c| c as u64).collect())
3877 .collect();
3878
3879 let mut inner = borrow_inner_mut(&self.file_inner);
3880 let H5FileInner::Reader(reader) = &mut *inner else {
3881 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
3882 };
3883 read_image_into_new(points_u64.len(), |image| {
3884 reader.read_points_into(name, &points_u64, image, ReadDst::Fresh)?;
3885 to_host_byte_order(image, &datatype, T::element_size())
3886 })
3887 }
3888 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
3889 "cannot read_points from a dataset in write mode".into(),
3890 )),
3891 }
3892 }
3893
3894 pub fn read_chunk_raw_at(&self, chunk_coords: &[usize]) -> Result<(Vec<u8>, u32)> {
3916 match &self.info {
3917 DatasetInfo::Reader { name, .. } => {
3918 let coords_u64: Vec<u64> = chunk_coords.iter().map(|&c| c as u64).collect();
3919 let mut inner = borrow_inner_mut(&self.file_inner);
3920 match &mut *inner {
3921 H5FileInner::Reader(reader) => Ok(reader.read_chunk_raw_at(name, &coords_u64)?),
3922 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
3923 }
3924 }
3925 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
3926 "cannot read_chunk_raw_at from a dataset in write mode".into(),
3927 )),
3928 }
3929 }
3930
3931 pub fn write_slice<T: H5Type>(
3944 &self,
3945 starts: &[usize],
3946 counts: &[usize],
3947 data: &[T],
3948 ) -> Result<()> {
3949 match &self.info {
3950 DatasetInfo::Writer {
3951 index,
3952 element_size,
3953 ..
3954 } => {
3955 if T::element_size() != *element_size {
3956 return Err(Hdf5Error::TypeMismatch(format!(
3957 "write type has element size {} but dataset expects {}",
3958 T::element_size(),
3959 element_size,
3960 )));
3961 }
3962
3963 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
3964 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
3965
3966 let inner = borrow_inner(&self.file_inner);
3967 let H5FileInner::Writer(writer) = &*inner else {
3968 return Err(Hdf5Error::InvalidState(
3969 "file is no longer in write mode".into(),
3970 ));
3971 };
3972
3973 check_hyperslab(&writer.dataset_dims(*index), &starts_u64, &counts_u64)?;
3978 let expected = element_count(&counts_u64)?;
3979 if data.len() != expected {
3980 return Err(Hdf5Error::InvalidState(format!(
3981 "data length {} does not match slice size {}",
3982 data.len(),
3983 expected,
3984 )));
3985 }
3986
3987 let byte_len = data.len() * T::element_size();
3988 let host =
3989 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) };
3990
3991 let datatype = writer.dataset_datatype(*index);
3992 let stored = to_stored_byte_order(host, &datatype, T::element_size())?;
3993 writer.write_slice(*index, &starts_u64, &counts_u64, &stored)?;
3994 Ok(())
3995 }
3996 DatasetInfo::Reader { .. } => {
3997 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3998 }
3999 }
4000 }
4001
4002 pub fn write_vlen_strings_slice(&self, start: usize, strings: &[&str]) -> Result<()> {
4026 match &self.info {
4027 DatasetInfo::Writer { index, .. } => {
4028 let inner = borrow_inner(&self.file_inner);
4029 match &*inner {
4030 H5FileInner::Writer(writer) => {
4031 writer.write_vlen_strings_slice(*index, start as u64, strings)?;
4032 Ok(())
4033 }
4034 _ => Err(Hdf5Error::InvalidState(
4035 "file is no longer in write mode".into(),
4036 )),
4037 }
4038 }
4039 DatasetInfo::Reader { .. } => {
4040 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4041 }
4042 }
4043 }
4044
4045 pub fn read_vlen_strings(&self) -> Result<Vec<String>> {
4050 match &self.info {
4051 DatasetInfo::Reader { name, .. } => {
4052 let mut inner = borrow_inner_mut(&self.file_inner);
4053 match &mut *inner {
4054 H5FileInner::Reader(reader) => Ok(reader.read_vlen_strings(name)?),
4055 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4056 }
4057 }
4058 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4059 "cannot read vlen strings from a dataset in write mode".into(),
4060 )),
4061 }
4062 }
4063
4064 pub fn read_vlen_bytes(&self) -> Result<Vec<Vec<u8>>> {
4071 match &self.info {
4072 DatasetInfo::Reader { name, .. } => {
4073 let mut inner = borrow_inner_mut(&self.file_inner);
4074 match &mut *inner {
4075 H5FileInner::Reader(reader) => Ok(reader.read_vlen_bytes(name)?),
4076 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4077 }
4078 }
4079 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4080 "cannot read vlen bytes from a dataset in write mode".into(),
4081 )),
4082 }
4083 }
4084
4085 pub fn write_object_references(&self, paths: &[&str]) -> Result<()> {
4095 match &self.info {
4096 DatasetInfo::Writer { index, .. } => {
4097 let inner = borrow_inner(&self.file_inner);
4098 match &*inner {
4099 H5FileInner::Writer(writer) => {
4100 writer.write_object_references(*index, 0, paths)?;
4101 Ok(())
4102 }
4103 _ => Err(Hdf5Error::InvalidState(
4104 "file is no longer in write mode".into(),
4105 )),
4106 }
4107 }
4108 DatasetInfo::Reader { .. } => {
4109 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4110 }
4111 }
4112 }
4113
4114 pub fn set_scale(&self, name: Option<&str>) -> Result<()> {
4130 let index = self.writer_index("set_scale")?;
4131 let inner = borrow_inner(&self.file_inner);
4132 match &*inner {
4133 H5FileInner::Writer(writer) => Ok(writer.set_dimension_scale(index, name)?),
4134 _ => Err(Hdf5Error::InvalidState(
4135 "file is no longer in write mode".into(),
4136 )),
4137 }
4138 }
4139
4140 pub fn attach_scale(&self, axis: usize, scale: &H5Dataset) -> Result<()> {
4160 let did = self.writer_index("attach_scale")?;
4161 let dsid = scale.writer_index("attach_scale")?;
4162 if !same_inner(&self.file_inner, &scale.file_inner) {
4163 return Err(Hdf5Error::InvalidState(
4164 "the dimension scale belongs to another file".into(),
4165 ));
4166 }
4167 let inner = borrow_inner(&self.file_inner);
4168 match &*inner {
4169 H5FileInner::Writer(writer) => Ok(writer.attach_dimension_scale(did, dsid, axis)?),
4170 _ => Err(Hdf5Error::InvalidState(
4171 "file is no longer in write mode".into(),
4172 )),
4173 }
4174 }
4175
4176 fn writer_index(&self, what: &str) -> Result<usize> {
4179 match &self.info {
4180 DatasetInfo::Writer { index, .. } => Ok(*index),
4181 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(format!(
4182 "{what} is only available in write mode"
4183 ))),
4184 }
4185 }
4186
4187 pub fn write_region_references(&self, targets: &[(&str, Selection)]) -> Result<()> {
4215 match &self.info {
4216 DatasetInfo::Writer { index, .. } => {
4217 let inner = borrow_inner(&self.file_inner);
4218 match &*inner {
4219 H5FileInner::Writer(writer) => {
4220 writer.write_region_references(*index, 0, targets)?;
4221 Ok(())
4222 }
4223 _ => Err(Hdf5Error::InvalidState(
4224 "file is no longer in write mode".into(),
4225 )),
4226 }
4227 }
4228 DatasetInfo::Reader { .. } => {
4229 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4230 }
4231 }
4232 }
4233
4234 pub fn write_std_region_references(&self, targets: &[(&str, Selection)]) -> Result<()> {
4248 let targets: Vec<(&str, ReferenceTarget)> = targets
4249 .iter()
4250 .map(|(path, selection)| (*path, ReferenceTarget::Region(selection.clone())))
4251 .collect();
4252 self.write_revised_references(&targets)
4253 }
4254
4255 pub fn write_attribute_references(&self, targets: &[(&str, &str)]) -> Result<()> {
4266 let targets: Vec<(&str, ReferenceTarget)> = targets
4267 .iter()
4268 .map(|(path, name)| (*path, ReferenceTarget::Attribute((*name).to_string())))
4269 .collect();
4270 self.write_revised_references(&targets)
4271 }
4272
4273 fn write_revised_references(&self, targets: &[(&str, ReferenceTarget)]) -> Result<()> {
4276 match &self.info {
4277 DatasetInfo::Writer { index, .. } => {
4278 let inner = borrow_inner(&self.file_inner);
4279 match &*inner {
4280 H5FileInner::Writer(writer) => {
4281 writer.write_revised_references(*index, 0, targets)?;
4282 Ok(())
4283 }
4284 _ => Err(Hdf5Error::InvalidState(
4285 "file is no longer in write mode".into(),
4286 )),
4287 }
4288 }
4289 DatasetInfo::Reader { .. } => {
4290 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4291 }
4292 }
4293 }
4294
4295 pub fn read_references(&self) -> Result<Vec<Reference>> {
4322 match &self.info {
4323 DatasetInfo::Reader { name, .. } => {
4324 let mut inner = borrow_inner_mut(&self.file_inner);
4325 match &mut *inner {
4326 H5FileInner::Reader(reader) => Ok(reader.read_references(name)?),
4327 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4328 }
4329 }
4330 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4331 "cannot read references from a dataset in write mode".into(),
4332 )),
4333 }
4334 }
4335
4336 pub fn read_strings(&self) -> Result<Vec<String>> {
4355 self.read_strings_inner(false)
4356 }
4357
4358 pub fn read_strings_lossy(&self) -> Result<Vec<String>> {
4364 self.read_strings_inner(true)
4365 }
4366
4367 fn read_strings_inner(&self, lossy: bool) -> Result<Vec<String>> {
4371 if matches!(self.info, DatasetInfo::Writer { .. }) {
4372 return Err(Hdf5Error::InvalidState(
4373 "cannot read strings from a dataset in write mode".into(),
4374 ));
4375 }
4376 match self.datatype()? {
4377 DatatypeMessage::VarLenString { charset, .. } => self
4378 .read_vlen_bytes()?
4379 .iter()
4380 .enumerate()
4381 .map(|(i, bytes)| decode_string(bytes, charset, lossy, i))
4382 .collect(),
4383 DatatypeMessage::FixedString {
4384 size,
4385 padding,
4386 charset,
4387 } => {
4388 let width = size as usize;
4389 if width == 0 {
4390 return Err(Hdf5Error::InvalidState(
4392 "fixed-string datatype has zero width".into(),
4393 ));
4394 }
4395 let raw = self.read_raw_bytes()?;
4398 raw.chunks_exact(width)
4399 .enumerate()
4400 .map(|(i, elem)| {
4401 decode_string(trim_fixed_string(elem, padding, i)?, charset, lossy, i)
4402 })
4403 .collect()
4404 }
4405 other => Err(Hdf5Error::InvalidState(format!(
4406 "read_strings is only for string datasets, this one is {other:?}"
4407 ))),
4408 }
4409 }
4410
4411 pub fn read_raw<T: H5Type>(&self) -> Result<Vec<T>> {
4423 match &self.info {
4424 DatasetInfo::Reader {
4425 name, element_size, ..
4426 } => {
4427 if T::element_size() != *element_size {
4428 return Err(Hdf5Error::TypeMismatch(format!(
4429 "read type has element size {} but dataset has element size {}",
4430 T::element_size(),
4431 element_size,
4432 )));
4433 }
4434
4435 let datatype = self.datatype()?;
4436 let mut inner = borrow_inner_mut(&self.file_inner);
4437 let H5FileInner::Reader(reader) = &mut *inner else {
4438 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
4439 };
4440 let total = reader.dataset_raw_size(name)? as usize;
4441 if !total.is_multiple_of(T::element_size()) {
4442 return Err(Hdf5Error::TypeMismatch(format!(
4443 "raw data size {total} is not a multiple of element size {}",
4444 T::element_size(),
4445 )));
4446 }
4447
4448 read_image_into_new(total / T::element_size(), |image| {
4452 reader.read_dataset_raw_into_dst(name, image, ReadDst::Fresh)?;
4453 to_host_byte_order(image, &datatype, T::element_size())
4454 })
4455 }
4456 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4457 "cannot read from a dataset in write mode".into(),
4458 )),
4459 }
4460 }
4461
4462 #[cfg(feature = "mmap")]
4525 pub fn read_mapped<T: H5Type>(&self) -> Result<crate::mapped::MappedView<T>> {
4526 self.mapped_view(crate::mapped::ViewRange::Whole)
4527 }
4528
4529 #[cfg(feature = "mmap")]
4546 pub fn read_mapped_slice<T: H5Type>(
4547 &self,
4548 starts: &[usize],
4549 counts: &[usize],
4550 ) -> Result<crate::mapped::MappedView<T>> {
4551 self.mapped_view(crate::mapped::ViewRange::Slab { starts, counts })
4552 }
4553
4554 #[cfg(feature = "mmap")]
4559 fn mapped_view<T: H5Type>(
4560 &self,
4561 range: crate::mapped::ViewRange<'_>,
4562 ) -> Result<crate::mapped::MappedView<T>> {
4563 let DatasetInfo::Reader { name, .. } = &self.info else {
4564 return Err(Hdf5Error::InvalidState(
4565 "cannot read from a dataset in write mode".into(),
4566 ));
4567 };
4568 let mut inner = borrow_inner_mut(&self.file_inner);
4569 let H5FileInner::Reader(reader) = &mut *inner else {
4570 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
4571 };
4572 let src = reader.dataset_view_source(name)?;
4573 crate::mapped::view::<T>(&src, range).map_err(Hdf5Error::NotViewable)
4574 }
4575
4576 pub fn read_raw_bytes(&self) -> Result<Vec<u8>> {
4583 match &self.info {
4584 DatasetInfo::Reader { name, .. } => {
4585 let mut inner = borrow_inner_mut(&self.file_inner);
4586 match &mut *inner {
4587 H5FileInner::Reader(reader) => Ok(reader.read_dataset_raw(name)?),
4588 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4589 }
4590 }
4591 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4592 "cannot read from a dataset in write mode".into(),
4593 )),
4594 }
4595 }
4596
4597 pub fn read_numeric_as<T: ReadNumeric>(&self) -> Result<Vec<T>> {
4621 match &self.info {
4622 DatasetInfo::Reader { name, .. } => {
4623 let (kind, raw) = {
4624 let mut inner = borrow_inner_mut(&self.file_inner);
4625 match &mut *inner {
4626 H5FileInner::Reader(reader) => {
4627 let info = reader
4628 .dataset_info(name)
4629 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))?;
4630 let kind = numeric::classify(&info.datatype)?;
4631 (kind, reader.read_dataset_raw(name)?)
4632 }
4633 _ => {
4634 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4635 }
4636 }
4637 };
4638 numeric::convert(kind, &raw)
4639 }
4640 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4641 "cannot read from a dataset in write mode".into(),
4642 )),
4643 }
4644 }
4645
4646 pub fn read_numeric_slice_as<T: ReadNumeric>(
4653 &self,
4654 starts: &[usize],
4655 counts: &[usize],
4656 ) -> Result<Vec<T>> {
4657 match &self.info {
4658 DatasetInfo::Reader { name, .. } => {
4659 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
4660 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
4661 let (kind, raw) = {
4662 let mut inner = borrow_inner_mut(&self.file_inner);
4663 match &mut *inner {
4664 H5FileInner::Reader(reader) => {
4665 let info = reader
4666 .dataset_info(name)
4667 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))?;
4668 let kind = numeric::classify(&info.datatype)?;
4669 (kind, reader.read_slice(name, &starts_u64, &counts_u64)?)
4670 }
4671 _ => {
4672 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4673 }
4674 }
4675 };
4676 numeric::convert(kind, &raw)
4677 }
4678 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4679 "cannot read_slice from a dataset in write mode".into(),
4680 )),
4681 }
4682 }
4683
4684 pub fn read_raw_into<T: H5Type>(&self, out: &mut [T]) -> Result<()> {
4706 match &self.info {
4707 DatasetInfo::Reader {
4708 name, element_size, ..
4709 } => {
4710 if T::element_size() != *element_size {
4711 return Err(Hdf5Error::TypeMismatch(format!(
4712 "read type has element size {} but dataset has element size {}",
4713 T::element_size(),
4714 element_size,
4715 )));
4716 }
4717 let datatype = self.datatype()?;
4718 let byte_len = out.len() * T::element_size();
4724 let bytes = unsafe {
4725 std::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, byte_len)
4726 };
4727 {
4728 let mut inner = borrow_inner_mut(&self.file_inner);
4729 match &mut *inner {
4730 H5FileInner::Reader(reader) => reader.read_dataset_raw_into(name, bytes)?,
4731 _ => {
4732 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4733 }
4734 }
4735 }
4736 to_host_byte_order(bytes, &datatype, T::element_size())
4737 }
4738 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4739 "cannot read from a dataset in write mode".into(),
4740 )),
4741 }
4742 }
4743
4744 pub fn read_slice_into<T: H5Type>(
4763 &self,
4764 out: &mut [T],
4765 starts: &[usize],
4766 counts: &[usize],
4767 ) -> Result<()> {
4768 match &self.info {
4769 DatasetInfo::Reader {
4770 name, element_size, ..
4771 } => {
4772 if T::element_size() != *element_size {
4773 return Err(Hdf5Error::TypeMismatch(format!(
4774 "read type has element size {} but dataset has element size {}",
4775 T::element_size(),
4776 element_size,
4777 )));
4778 }
4779 let datatype = self.datatype()?;
4780 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
4781 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
4782 let byte_len = out.len() * T::element_size();
4785 let bytes = unsafe {
4786 std::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, byte_len)
4787 };
4788 {
4789 let mut inner = borrow_inner_mut(&self.file_inner);
4790 match &mut *inner {
4791 H5FileInner::Reader(reader) => {
4792 reader.read_slice_into(name, &starts_u64, &counts_u64, bytes)?
4793 }
4794 _ => {
4795 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4796 }
4797 }
4798 }
4799 to_host_byte_order(bytes, &datatype, T::element_size())
4800 }
4801 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4802 "cannot read from a dataset in write mode".into(),
4803 )),
4804 }
4805 }
4806}
4807
4808pub trait ReadNumeric: numeric::Sealed {}
4819impl<T: numeric::Sealed> ReadNumeric for T {}
4820
4821pub(crate) mod numeric {
4822 use crate::error::{Hdf5Error, Result};
4826 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage, IeeeFormat};
4827
4828 #[derive(Clone, Copy)]
4830 pub enum SourceKind {
4831 Int {
4832 size: usize,
4833 signed: bool,
4834 byte_order: ByteOrder,
4835 },
4836 F16(ByteOrder),
4837 F32(ByteOrder),
4838 F64(ByteOrder),
4839 }
4840
4841 impl SourceKind {
4842 fn element_size(self) -> usize {
4843 match self {
4844 SourceKind::Int { size, .. } => size,
4845 SourceKind::F16(_) => 2,
4846 SourceKind::F32(_) => 4,
4847 SourceKind::F64(_) => 8,
4848 }
4849 }
4850 }
4851
4852 fn f16_bits_to_f32(bits: u16) -> f32 {
4857 let sign = u32::from(bits >> 15);
4858 let exponent = u32::from((bits >> 10) & 0x1f);
4859 let mantissa = u32::from(bits & 0x03ff);
4860 if exponent == 0 {
4861 let magnitude = mantissa as f32 * (1.0 / 16_777_216.0);
4865 return if sign == 1 { -magnitude } else { magnitude };
4866 }
4867 let out = if exponent == 0x1f {
4868 (sign << 31) | 0x7f80_0000 | (mantissa << 13)
4871 } else {
4872 (sign << 31) | ((exponent + 112) << 23) | (mantissa << 13)
4875 };
4876 f32::from_bits(out)
4877 }
4878
4879 pub fn classify(dt: &DatatypeMessage) -> Result<SourceKind> {
4885 match *dt {
4886 DatatypeMessage::FixedPoint {
4887 size,
4888 byte_order,
4889 signed,
4890 bit_offset,
4891 bit_precision,
4892 } => {
4893 if !matches!(size, 1 | 2 | 4 | 8)
4894 || bit_offset != 0
4895 || u32::from(bit_precision) != size * 8
4896 {
4897 return Err(Hdf5Error::TypeMismatch(format!(
4898 "fixed-point datatype (size {size}, bit offset {bit_offset}, \
4899 precision {bit_precision}) is not a standard-width integer",
4900 )));
4901 }
4902 Ok(SourceKind::Int {
4903 size: size as usize,
4904 signed,
4905 byte_order,
4906 })
4907 }
4908 DatatypeMessage::BitField {
4909 size,
4910 byte_order,
4911 bit_offset,
4912 bit_precision,
4913 } => {
4914 if !matches!(size, 1 | 2 | 4 | 8)
4918 || bit_offset != 0
4919 || u32::from(bit_precision) != size * 8
4920 {
4921 return Err(Hdf5Error::TypeMismatch(format!(
4922 "bit-field datatype (size {size}, bit offset {bit_offset}, \
4923 precision {bit_precision}) is not a whole-width bit field",
4924 )));
4925 }
4926 Ok(SourceKind::Int {
4927 size: size as usize,
4928 signed: false,
4929 byte_order,
4930 })
4931 }
4932 DatatypeMessage::FloatingPoint {
4933 size,
4934 byte_order,
4935 exponent_size,
4936 mantissa_size,
4937 ..
4938 } => match dt.ieee_format() {
4939 Some(IeeeFormat::Binary16) => Ok(SourceKind::F16(byte_order)),
4940 Some(IeeeFormat::Binary32) => Ok(SourceKind::F32(byte_order)),
4941 Some(IeeeFormat::Binary64) => Ok(SourceKind::F64(byte_order)),
4942 None => Err(Hdf5Error::TypeMismatch(format!(
4943 "floating-point datatype (size {size}, exponent {exponent_size} bits, \
4944 mantissa {mantissa_size} bits) is not an IEEE 754 interchange format",
4945 ))),
4946 },
4947 ref other => Err(Hdf5Error::TypeMismatch(format!(
4948 "dataset datatype '{other}' is not numeric",
4949 ))),
4950 }
4951 }
4952
4953 pub fn convert<T: Sealed>(kind: SourceKind, raw: &[u8]) -> Result<Vec<T>> {
4961 let size = kind.element_size();
4962 if !raw.len().is_multiple_of(size) {
4963 return Err(Hdf5Error::TypeMismatch(format!(
4964 "raw data size {} is not a multiple of element size {size}",
4965 raw.len(),
4966 )));
4967 }
4968 let mut out = vec![T::default(); raw.len() / size];
4969 match kind {
4970 SourceKind::Int {
4971 size,
4972 signed,
4973 byte_order,
4974 } => {
4975 let le = byte_order == ByteOrder::LittleEndian;
4976 match (size, signed) {
4977 (1, true) => ints::<1, i64, T>(raw, le, &mut out),
4978 (1, false) => ints::<1, u64, T>(raw, le, &mut out),
4979 (2, true) => ints::<2, i64, T>(raw, le, &mut out),
4980 (2, false) => ints::<2, u64, T>(raw, le, &mut out),
4981 (4, true) => ints::<4, i64, T>(raw, le, &mut out),
4982 (4, false) => ints::<4, u64, T>(raw, le, &mut out),
4983 (8, true) => ints::<8, i64, T>(raw, le, &mut out),
4984 (8, false) => ints::<8, u64, T>(raw, le, &mut out),
4985 _ => unreachable!("classify admits 1-, 2-, 4- and 8-byte integers only"),
4986 }
4987 }
4988 SourceKind::F16(order) => floats::<2, T>(raw, order, &mut out, |b| {
4989 T::from_f32(f16_bits_to_f32(u16::from_le_bytes(b)))
4990 }),
4991 SourceKind::F32(order) => {
4992 floats::<4, T>(raw, order, &mut out, |b| T::from_f32(f32::from_le_bytes(b)))
4993 }
4994 SourceKind::F64(order) => {
4995 floats::<8, T>(raw, order, &mut out, |b| T::from_f64(f64::from_le_bytes(b)))
4996 }
4997 }?;
4998 Ok(out)
4999 }
5000
5001 fn ints<const N: usize, L: IntLane, T: Sealed>(
5003 raw: &[u8],
5004 le: bool,
5005 out: &mut [T],
5006 ) -> Result<()> {
5007 let (chunks, _) = raw.as_chunks::<N>();
5008 for (index, (o, &chunk)) in out.iter_mut().zip(chunks).enumerate() {
5009 let mut bytes = chunk;
5010 if !le {
5011 bytes.reverse();
5012 }
5013 *o = L::load(bytes).into_target(index)?;
5014 }
5015 Ok(())
5016 }
5017
5018 fn floats<const N: usize, T: Sealed>(
5021 raw: &[u8],
5022 order: ByteOrder,
5023 out: &mut [T],
5024 decode: impl Fn([u8; N]) -> Result<T>,
5025 ) -> Result<()> {
5026 let (chunks, _) = raw.as_chunks::<N>();
5027 for (o, &chunk) in out.iter_mut().zip(chunks) {
5028 let mut bytes = chunk;
5029 if order == ByteOrder::BigEndian {
5030 bytes.reverse();
5031 }
5032 *o = decode(bytes)?;
5033 }
5034 Ok(())
5035 }
5036
5037 pub trait IntLane: Copy {
5041 fn load<const N: usize>(bytes: [u8; N]) -> Self;
5043 fn into_target<T: Sealed>(self, index: usize) -> Result<T>;
5044 }
5045
5046 impl IntLane for u64 {
5047 fn load<const N: usize>(bytes: [u8; N]) -> Self {
5048 let mut padded = [0u8; 8];
5049 padded[..N].copy_from_slice(&bytes);
5050 u64::from_le_bytes(padded)
5051 }
5052 fn into_target<T: Sealed>(self, index: usize) -> Result<T> {
5053 T::from_u64(self, index)
5054 }
5055 }
5056
5057 impl IntLane for i64 {
5058 fn load<const N: usize>(bytes: [u8; N]) -> Self {
5059 let shift = 64 - 8 * N as u32;
5061 ((u64::load(bytes) << shift) as i64) >> shift
5062 }
5063 fn into_target<T: Sealed>(self, index: usize) -> Result<T> {
5064 T::from_i64(self, index)
5065 }
5066 }
5067
5068 pub trait Sealed: Copy + Default {
5072 fn from_i64(v: i64, index: usize) -> Result<Self>;
5073 fn from_u64(v: u64, index: usize) -> Result<Self>;
5074 fn from_f32(v: f32) -> Result<Self>;
5075 fn from_f64(v: f64) -> Result<Self>;
5076 }
5077
5078 macro_rules! int_targets {
5079 ($($t:ty),* $(,)?) => {$(
5080 impl Sealed for $t {
5081 fn from_i64(v: i64, index: usize) -> Result<Self> {
5082 <$t>::try_from(v).map_err(|_| int_overflow::<$t>(v, index))
5083 }
5084 fn from_u64(v: u64, index: usize) -> Result<Self> {
5085 <$t>::try_from(v).map_err(|_| int_overflow::<$t>(v, index))
5086 }
5087 fn from_f32(_: f32) -> Result<Self> {
5088 Err(float_as_int::<$t>())
5089 }
5090 fn from_f64(_: f64) -> Result<Self> {
5091 Err(float_as_int::<$t>())
5092 }
5093 }
5094 )*};
5095 }
5096 int_targets!(i8, i16, i32, i64, u8, u16, u32, u64);
5097
5098 impl Sealed for i128 {
5101 fn from_i64(v: i64, _index: usize) -> Result<Self> {
5102 Ok(Self::from(v))
5103 }
5104 fn from_u64(v: u64, _index: usize) -> Result<Self> {
5105 Ok(Self::from(v))
5106 }
5107 fn from_f32(_: f32) -> Result<Self> {
5108 Err(float_as_int::<i128>())
5109 }
5110 fn from_f64(_: f64) -> Result<Self> {
5111 Err(float_as_int::<i128>())
5112 }
5113 }
5114
5115 impl Sealed for u128 {
5116 fn from_i64(v: i64, index: usize) -> Result<Self> {
5117 Self::try_from(v).map_err(|_| int_overflow::<u128>(v, index))
5118 }
5119 fn from_u64(v: u64, _index: usize) -> Result<Self> {
5120 Ok(Self::from(v))
5121 }
5122 fn from_f32(_: f32) -> Result<Self> {
5123 Err(float_as_int::<u128>())
5124 }
5125 fn from_f64(_: f64) -> Result<Self> {
5126 Err(float_as_int::<u128>())
5127 }
5128 }
5129
5130 fn int_overflow<T>(v: impl std::fmt::Display, index: usize) -> Hdf5Error {
5131 Hdf5Error::TypeMismatch(format!(
5132 "value {v} at element {index} does not fit in {}",
5133 std::any::type_name::<T>()
5134 ))
5135 }
5136
5137 fn float_as_int<T>() -> Hdf5Error {
5138 Hdf5Error::TypeMismatch(format!(
5139 "cannot read a floating-point dataset as {}; read as f64 and convert explicitly",
5140 std::any::type_name::<T>()
5141 ))
5142 }
5143
5144 impl Sealed for f32 {
5145 fn from_i64(_: i64, _index: usize) -> Result<Self> {
5146 Err(int_as_f32())
5147 }
5148 fn from_u64(_: u64, _index: usize) -> Result<Self> {
5149 Err(int_as_f32())
5150 }
5151 fn from_f32(v: f32) -> Result<Self> {
5152 Ok(v)
5153 }
5154 fn from_f64(_: f64) -> Result<Self> {
5155 Err(Hdf5Error::TypeMismatch(
5156 "narrowing an f64 dataset to f32 loses precision; read as f64".into(),
5157 ))
5158 }
5159 }
5160
5161 fn int_as_f32() -> Hdf5Error {
5162 Hdf5Error::TypeMismatch(
5163 "cannot read an integer dataset as f32; read as an integer type and convert \
5164 explicitly"
5165 .into(),
5166 )
5167 }
5168
5169 impl Sealed for f64 {
5170 fn from_i64(_: i64, _index: usize) -> Result<Self> {
5171 Err(int_as_f64())
5172 }
5173 fn from_u64(_: u64, _index: usize) -> Result<Self> {
5174 Err(int_as_f64())
5175 }
5176 fn from_f32(v: f32) -> Result<Self> {
5178 Ok(f64::from(v))
5179 }
5180 fn from_f64(v: f64) -> Result<Self> {
5181 Ok(v)
5182 }
5183 }
5184
5185 fn int_as_f64() -> Hdf5Error {
5186 Hdf5Error::TypeMismatch(
5187 "cannot read an integer dataset as f64; integers above 2^53 lose precision — \
5188 read as an integer type and convert explicitly"
5189 .into(),
5190 )
5191 }
5192
5193 #[cfg(test)]
5194 mod tests {
5195 use super::*;
5196
5197 #[test]
5201 fn f16_widening_is_exact() {
5202 let cases: [(u16, f32); 10] = [
5203 (0x0000, 0.0),
5204 (0x3c00, 1.0),
5205 (0xc000, -2.0),
5206 (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),
5212 (0xfc00, f32::NEG_INFINITY),
5213 ];
5214 for (bits, expected) in cases {
5215 let got = f16_bits_to_f32(bits);
5216 assert_eq!(got, expected, "0x{bits:04x} widened to {got}");
5217 }
5218
5219 let neg_zero = f16_bits_to_f32(0x8000);
5220 assert_eq!(neg_zero, 0.0);
5221 assert!(neg_zero.is_sign_negative(), "-0.0 lost its sign");
5222
5223 let nan = f16_bits_to_f32(0x7e01);
5224 assert!(nan.is_nan());
5225 assert_eq!(nan.to_bits(), 0x7fc0_2000);
5227 }
5228
5229 #[test]
5230 fn f16_source_converts_and_honors_byte_order() {
5231 let kind = classify(&DatatypeMessage::f16_type()).unwrap();
5232 let raw = [0x00, 0x3c, 0x00, 0xc0, 0x55, 0x35, 0xff, 0x7b];
5234 assert_eq!(
5235 convert::<f32>(kind, &raw).unwrap(),
5236 vec![1.0, -2.0, 0.333_251_95, 65504.0]
5237 );
5238 assert_eq!(
5239 convert::<f64>(kind, &raw).unwrap(),
5240 vec![1.0, -2.0, 0.333_251_953_125, 65504.0]
5241 );
5242
5243 let DatatypeMessage::FloatingPoint { .. } = DatatypeMessage::f16_type() else {
5244 unreachable!()
5245 };
5246 let mut be = DatatypeMessage::f16_type();
5247 if let DatatypeMessage::FloatingPoint { byte_order, .. } = &mut be {
5248 *byte_order = ByteOrder::BigEndian;
5249 }
5250 let be_kind = classify(&be).unwrap();
5251 assert_eq!(convert::<f32>(be_kind, &[0x3c, 0x00]).unwrap(), vec![1.0]);
5252 }
5253
5254 #[test]
5257 fn non_ieee_float_is_refused() {
5258 let mut odd = DatatypeMessage::f32_type();
5259 if let DatatypeMessage::FloatingPoint { exponent_bias, .. } = &mut odd {
5260 *exponent_bias = 63;
5261 }
5262 assert!(odd.ieee_format().is_none());
5263 let err = classify(&odd).err().expect("non-IEEE float was accepted");
5264 assert!(
5265 err.to_string().contains("IEEE 754 interchange format"),
5266 "unexpected error: {err}"
5267 );
5268 }
5269 }
5270}
5271
5272#[cfg(test)]
5273mod tests {
5274 use crate::H5File;
5275 use std::path::PathBuf;
5276
5277 fn temp_path(name: &str) -> PathBuf {
5278 use std::sync::atomic::{AtomicU64, Ordering};
5282 static COUNTER: AtomicU64 = AtomicU64::new(0);
5283 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
5284 std::env::temp_dir().join(format!(
5285 "hdf5_dataset_test_{}_{}_{}.h5",
5286 name,
5287 std::process::id(),
5288 n
5289 ))
5290 }
5291
5292 #[test]
5293 fn runtime_compound_via_datatype_override_and_raw_bytes() {
5294 use crate::format::messages::datatype::DatatypeMessage;
5295 use crate::types::{CompoundType, H5Type};
5296
5297 let path = temp_path("compound_raw");
5298 let ct = CompoundType {
5302 members: vec![
5303 ("id".to_string(), i32::hdf5_type(), 0),
5304 ("val".to_string(), f64::hdf5_type(), 4),
5305 ],
5306 total_size: 12,
5307 };
5308 let recs: [(i32, f64); 3] = [(1, 2.5), (2, 3.5), (3, -4.0)];
5309 let mut bytes = Vec::new();
5310 for (id, val) in recs {
5311 bytes.extend_from_slice(&id.to_le_bytes());
5312 bytes.extend_from_slice(&val.to_le_bytes());
5313 }
5314
5315 {
5316 let file = H5File::create(&path).unwrap();
5317 let ds = file
5318 .new_dataset::<u8>()
5319 .datatype(ct.to_datatype())
5320 .shape([recs.len()])
5321 .create("records")
5322 .unwrap();
5323 ds.write_raw_bytes(&bytes).unwrap();
5324 file.close().unwrap();
5325 }
5326 {
5327 let file = H5File::open(&path).unwrap();
5328 let ds = file.dataset("records").unwrap();
5329 match ds.datatype().unwrap() {
5332 DatatypeMessage::Compound { size, members } => {
5333 assert_eq!(size, 12);
5334 assert_eq!(members.len(), 2);
5335 assert_eq!(members[0].name, "id");
5336 assert_eq!(members[0].offset, 0);
5337 assert_eq!(members[1].name, "val");
5338 assert_eq!(members[1].offset, 4);
5339 }
5340 other => panic!("expected compound datatype, got {other:?}"),
5341 }
5342 assert_eq!(ds.read_raw_bytes().unwrap(), bytes);
5343 }
5344 std::fs::remove_file(&path).ok();
5345 }
5346
5347 #[test]
5348 fn builder_requires_shape() {
5349 let path = temp_path("no_shape");
5350 let file = H5File::create(&path).unwrap();
5351 let result = file.new_dataset::<u8>().create("data");
5352 assert!(result.is_err());
5353 std::fs::remove_file(&path).ok();
5354 }
5355
5356 #[test]
5362 fn a_partial_edge_chunk_does_not_grow_past_the_declared_maximum() {
5363 let path = temp_path("edge_chunk_extent");
5364 let file = H5File::create(&path).unwrap();
5367 let ds = file
5368 .new_dataset::<i32>()
5369 .shape([10usize, 4])
5370 .max_shape(&[Some(10), None])
5371 .chunk(&[4, 4])
5372 .create("grid")
5373 .unwrap();
5374 let chunk: Vec<u8> = (0i32..16).flat_map(|v| v.to_le_bytes()).collect();
5375 ds.write_chunk_at(&[2, 0], &chunk).unwrap();
5377 assert_eq!(ds.shape(), vec![10, 4]);
5378 file.close().unwrap();
5379
5380 let file = H5File::open(&path).unwrap();
5381 let back = file.dataset("grid").unwrap().read_raw::<i32>().unwrap();
5382 assert_eq!(back.len(), 40);
5383 assert_eq!(&back[32..40], &[0, 1, 2, 3, 4, 5, 6, 7]);
5384 drop(file);
5385 std::fs::remove_file(&path).ok();
5386 }
5387
5388 #[test]
5397 fn the_edge_write_cap_holds_for_the_v2_btree_index() {
5398 let path = temp_path("edge_chunk_bt2");
5399 let file = H5File::create(&path).unwrap();
5400 let ds = file
5401 .new_dataset::<i32>()
5402 .shape([10usize, 4, 4])
5403 .max_shape(&[Some(10), None, None])
5404 .chunk(&[4, 4, 4])
5405 .create("cube")
5406 .unwrap();
5407 let chunk: Vec<u8> = (0i32..64).flat_map(|v| v.to_le_bytes()).collect();
5408 ds.write_chunk_at(&[2, 0, 0], &chunk).unwrap();
5410 assert_eq!(ds.shape(), vec![10, 4, 4]);
5411 file.close().unwrap();
5412
5413 let file = H5File::open(&path).unwrap();
5414 let back = file.dataset("cube").unwrap().read_raw::<i32>().unwrap();
5415 assert_eq!(back.len(), 160);
5416 assert_eq!(&back[128..160], &(0i32..32).collect::<Vec<_>>()[..]);
5418 drop(file);
5419 std::fs::remove_file(&path).ok();
5420 }
5421
5422 #[test]
5430 fn an_external_or_virtual_dataset_never_reaches_the_edge_write_cap() {
5431 use crate::Selection;
5432 let dir = std::env::temp_dir().join(format!(
5433 "rust_hdf5_edge_cap_{}_{}",
5434 std::process::id(),
5435 temp_path("x").file_name().unwrap().to_string_lossy()
5436 ));
5437 std::fs::create_dir_all(&dir).unwrap();
5438 let path = dir.join("edge_cap.h5");
5439 let file = H5File::create(&path).unwrap();
5440 let payload = dir.join("payload.raw");
5441
5442 for (which, res) in [
5444 (
5445 "external",
5446 file.new_dataset::<i32>()
5447 .shape([10usize])
5448 .external(&[(payload.to_str().unwrap(), 0, 40)])
5449 .chunk(&[4])
5450 .create("a"),
5451 ),
5452 (
5453 "virtual",
5454 file.new_dataset::<i32>()
5455 .shape([10usize])
5456 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
5457 .chunk(&[4])
5458 .create("b"),
5459 ),
5460 ] {
5461 match res {
5462 Ok(_) => panic!("a {which} dataset cannot also be chunked"),
5463 Err(e) => assert!(e.to_string().contains("chunked"), "{which}: {e}"),
5464 }
5465 }
5466
5467 let ext = file
5470 .new_dataset::<i32>()
5471 .shape([10usize])
5472 .external(&[(payload.to_str().unwrap(), 0, 40)])
5473 .create("outside")
5474 .unwrap();
5475 let vds = file
5476 .new_dataset::<i32>()
5477 .shape([10usize])
5478 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
5479 .create("elsewhere")
5480 .unwrap();
5481 let chunk: Vec<u8> = (0i32..4).flat_map(|v| v.to_le_bytes()).collect();
5482 for (which, ds) in [("external", &ext), ("virtual", &vds)] {
5483 let err = ds.write_chunk_at(&[2], &chunk).unwrap_err().to_string();
5484 assert!(err.contains("only for chunked datasets"), "{which}: {err}");
5485 let err = ds
5486 .write_chunk_raw_at(&[2], &chunk, 0)
5487 .unwrap_err()
5488 .to_string();
5489 assert!(err.contains("only for chunked datasets"), "{which}: {err}");
5490 assert_eq!(ds.shape(), vec![10], "{which}");
5491 }
5492 file.close().unwrap();
5493 std::fs::remove_dir_all(&dir).ok();
5494 }
5495
5496 #[test]
5497 fn write_raw_size_mismatch() {
5498 let path = temp_path("size_mismatch");
5499 let file = H5File::create(&path).unwrap();
5500 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
5501 let result = ds.write_raw(&[1u8, 2, 3]);
5503 assert!(result.is_err());
5504 std::fs::remove_file(&path).ok();
5505 }
5506
5507 #[cfg(feature = "deflate")]
5511 #[test]
5512 fn filter_without_chunk_autochunks_and_roundtrips() {
5513 let path = temp_path("autochunk_filter");
5514 let data: Vec<i32> = (0..8).collect();
5515 {
5516 let file = H5File::create(&path).unwrap();
5517 let ds = file
5518 .new_dataset::<i32>()
5519 .deflate(6)
5520 .shape([8])
5521 .create("seq")
5522 .unwrap();
5523 ds.write_raw(&data).unwrap();
5524 file.close().unwrap();
5525 }
5526 {
5527 let file = H5File::open(&path).unwrap();
5528 let ds = file.dataset("seq").unwrap();
5529 assert!(
5531 ds.is_chunked(),
5532 "auto-chunk did not produce chunked storage"
5533 );
5534 assert_eq!(ds.chunk_dims(), Some(vec![8]));
5535 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5536 }
5537 std::fs::remove_file(&path).ok();
5538 }
5539
5540 #[cfg(feature = "deflate")]
5544 #[test]
5545 fn an_edge_chunk_pads_with_zero_not_the_chunk_written_before_it() {
5546 let path = temp_path("edge_chunk_padding");
5552 let data: Vec<i32> = (1..=20).collect(); {
5554 let file = H5File::create(&path).unwrap();
5555 let ds = file
5556 .new_dataset::<i32>()
5557 .shape([4, 5])
5558 .chunk(&[2, 3])
5559 .create("grid")
5560 .unwrap();
5561 ds.write_raw(&data).unwrap();
5562 file.close().unwrap();
5563 }
5564 {
5565 let file = H5File::open(&path).unwrap();
5566 let ds = file.dataset("grid").unwrap();
5567 let as_i32 = |bytes: Vec<u8>| -> Vec<i32> {
5568 bytes
5569 .as_chunks::<4>()
5570 .0
5571 .iter()
5572 .map(|b| i32::from_le_bytes(*b))
5573 .collect()
5574 };
5575 assert_eq!(
5578 as_i32(ds.read_chunk_raw_at(&[0, 0]).unwrap().0),
5579 [1, 2, 3, 6, 7, 8]
5580 );
5581 assert_eq!(
5582 as_i32(ds.read_chunk_raw_at(&[0, 1]).unwrap().0),
5583 [4, 5, 0, 9, 10, 0]
5584 );
5585 assert_eq!(
5586 as_i32(ds.read_chunk_raw_at(&[1, 1]).unwrap().0),
5587 [14, 15, 0, 19, 20, 0]
5588 );
5589 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5590 }
5591 std::fs::remove_file(&path).ok();
5592 }
5593
5594 #[test]
5595 #[cfg(feature = "deflate")]
5596 fn write_raw_multichunk_edge_roundtrips() {
5597 let path = temp_path("multichunk_edge");
5598 let data: Vec<i32> = (0..35).collect(); {
5600 let file = H5File::create(&path).unwrap();
5601 let ds = file
5602 .new_dataset::<i32>()
5603 .shape([7, 5])
5604 .chunk(&[3, 2])
5605 .deflate(4)
5606 .create("grid")
5607 .unwrap();
5608 ds.write_raw(&data).unwrap();
5609 file.close().unwrap();
5610 }
5611 {
5612 let file = H5File::open(&path).unwrap();
5613 let ds = file.dataset("grid").unwrap();
5614 assert_eq!(ds.shape(), vec![7, 5]);
5615 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
5616 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5617 }
5618 std::fs::remove_file(&path).ok();
5619 }
5620
5621 #[test]
5624 fn write_raw_unfiltered_chunked_roundtrips() {
5625 let path = temp_path("chunked_unfiltered");
5626 let data: Vec<f64> = (0..12).map(|i| i as f64 * 1.5).collect(); {
5628 let file = H5File::create(&path).unwrap();
5629 let ds = file
5630 .new_dataset::<f64>()
5631 .shape([4, 3])
5632 .chunk(&[2, 2])
5633 .create("m")
5634 .unwrap();
5635 ds.write_raw(&data).unwrap();
5636 file.close().unwrap();
5637 }
5638 {
5639 let file = H5File::open(&path).unwrap();
5640 let ds = file.dataset("m").unwrap();
5641 assert_eq!(ds.chunk_dims(), Some(vec![2, 2]));
5642 assert_eq!(ds.read_raw::<f64>().unwrap(), data);
5643 }
5644 std::fs::remove_file(&path).ok();
5645 }
5646
5647 #[cfg(feature = "deflate")]
5652 #[test]
5653 fn write_raw_ea_compressed_roundtrips() {
5654 let path = temp_path("write_raw_ea_deflate");
5655 let data: Vec<i32> = (0..20).collect(); {
5657 let file = H5File::create(&path).unwrap();
5658 let ds = file
5659 .new_dataset::<i32>()
5660 .shape([5, 4])
5661 .chunk(&[2, 4])
5662 .max_shape(&[None, Some(4)]) .deflate(5)
5664 .create("stream")
5665 .unwrap();
5666 ds.write_raw(&data).unwrap();
5667 file.close().unwrap();
5668 }
5669 {
5670 let file = H5File::open(&path).unwrap();
5671 let ds = file.dataset("stream").unwrap();
5672 assert_eq!(ds.shape(), vec![5, 4]);
5673 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5674 }
5675 std::fs::remove_file(&path).ok();
5676 }
5677
5678 #[cfg(feature = "deflate")]
5683 #[test]
5684 fn read_full_3d_chunked_edge_roundtrips() {
5685 let path = temp_path("full_3d_chunked_edge");
5686 let total: usize = 5 * 4 * 3;
5689 let data: Vec<i32> = (0..total as i32).collect();
5690 {
5691 let file = H5File::create(&path).unwrap();
5692 let ds = file
5693 .new_dataset::<i32>()
5694 .shape([5, 4, 3])
5695 .chunk(&[2, 3, 2])
5696 .deflate(4)
5697 .create("vol")
5698 .unwrap();
5699 ds.write_raw(&data).unwrap();
5700 file.close().unwrap();
5701 }
5702 {
5703 let file = H5File::open(&path).unwrap();
5704 let ds = file.dataset("vol").unwrap();
5705 assert_eq!(ds.shape(), vec![5, 4, 3]);
5706 assert_eq!(ds.chunk_dims(), Some(vec![2, 3, 2]));
5707 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5708 }
5709 std::fs::remove_file(&path).ok();
5710 }
5711
5712 #[test]
5713 fn roundtrip_u8_1d() {
5714 let path = temp_path("rt_u8_1d");
5715 let data: Vec<u8> = (0..10).collect();
5716
5717 {
5718 let file = H5File::create(&path).unwrap();
5719 let ds = file.new_dataset::<u8>().shape([10]).create("seq").unwrap();
5720 ds.write_raw(&data).unwrap();
5721 file.close().unwrap();
5722 }
5723
5724 {
5725 let file = H5File::open(&path).unwrap();
5726 let ds = file.dataset("seq").unwrap();
5727 assert_eq!(ds.shape(), vec![10]);
5728 let readback = ds.read_raw::<u8>().unwrap();
5729 assert_eq!(readback, data);
5730 }
5731
5732 std::fs::remove_file(&path).ok();
5733 }
5734
5735 #[test]
5736 fn roundtrip_i32_2d() {
5737 let path = temp_path("rt_i32_2d");
5738 let data: Vec<i32> = vec![-1, 0, 1, 2, 3, 4];
5739
5740 {
5741 let file = H5File::create(&path).unwrap();
5742 let ds = file
5743 .new_dataset::<i32>()
5744 .shape([2, 3])
5745 .create("matrix")
5746 .unwrap();
5747 ds.write_raw(&data).unwrap();
5748 file.close().unwrap();
5749 }
5750
5751 {
5752 let file = H5File::open(&path).unwrap();
5753 let ds = file.dataset("matrix").unwrap();
5754 assert_eq!(ds.shape(), vec![2, 3]);
5755 let readback = ds.read_raw::<i32>().unwrap();
5756 assert_eq!(readback, data);
5757 }
5758
5759 std::fs::remove_file(&path).ok();
5760 }
5761
5762 #[test]
5763 fn roundtrip_f64_3d() {
5764 let path = temp_path("rt_f64_3d");
5765 let data: Vec<f64> = (0..24).map(|i| i as f64 * 0.5).collect();
5766
5767 {
5768 let file = H5File::create(&path).unwrap();
5769 let ds = file
5770 .new_dataset::<f64>()
5771 .shape([2, 3, 4])
5772 .create("cube")
5773 .unwrap();
5774 ds.write_raw(&data).unwrap();
5775 file.close().unwrap();
5776 }
5777
5778 {
5779 let file = H5File::open(&path).unwrap();
5780 let ds = file.dataset("cube").unwrap();
5781 assert_eq!(ds.shape(), vec![2, 3, 4]);
5782 let readback = ds.read_raw::<f64>().unwrap();
5783 assert_eq!(readback, data);
5784 }
5785
5786 std::fs::remove_file(&path).ok();
5787 }
5788
5789 #[test]
5790 fn cannot_read_in_write_mode() {
5791 let path = temp_path("no_read_write");
5792 let file = H5File::create(&path).unwrap();
5793 let ds = file.new_dataset::<u8>().shape([4]).create("x").unwrap();
5794 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
5795 let result = ds.read_raw::<u8>();
5796 assert!(result.is_err());
5797 std::fs::remove_file(&path).ok();
5798 }
5799
5800 #[test]
5801 fn cannot_write_in_read_mode() {
5802 let path = temp_path("no_write_read");
5803
5804 {
5805 let file = H5File::create(&path).unwrap();
5806 let ds = file.new_dataset::<u8>().shape([4]).create("x").unwrap();
5807 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
5808 file.close().unwrap();
5809 }
5810
5811 {
5812 let file = H5File::open(&path).unwrap();
5813 let ds = file.dataset("x").unwrap();
5814 let result = ds.write_raw(&[5u8, 6, 7, 8]);
5815 assert!(result.is_err());
5816 }
5817
5818 std::fs::remove_file(&path).ok();
5819 }
5820
5821 #[test]
5822 fn numeric_attr_roundtrip() {
5823 let path = temp_path("num_attr");
5824 {
5825 let file = H5File::create(&path).unwrap();
5826 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
5827 ds.write_raw(&[1.0f32; 4]).unwrap();
5828
5829 let a1 = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
5830 a1.write_numeric(&1.2345f64).unwrap();
5831
5832 let a2 = ds.new_attr::<i32>().shape(()).create("count").unwrap();
5833 a2.write_numeric(&42i32).unwrap();
5834
5835 file.close().unwrap();
5836 }
5837 {
5838 let file = H5File::open(&path).unwrap();
5839 let ds = file.dataset("data").unwrap();
5840
5841 let scale = ds.attr("scale").unwrap();
5842 let val: f64 = scale.read_numeric().unwrap();
5843 assert!((val - 1.2345).abs() < 1e-10);
5844
5845 let count = ds.attr("count").unwrap();
5846 let val: i32 = count.read_numeric().unwrap();
5847 assert_eq!(val, 42);
5848 }
5849 std::fs::remove_file(&path).ok();
5850 }
5851
5852 #[test]
5853 fn array_attr_roundtrip() {
5854 let path = temp_path("array_attr");
5855 let offsets = [10i32, -20, 30];
5856 {
5857 let file = H5File::create(&path).unwrap();
5858 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
5859 ds.write_raw(&[1.0f32; 4]).unwrap();
5860
5861 let a = ds
5863 .new_attr::<i32>()
5864 .shape([3])
5865 .create("dim_offset")
5866 .unwrap();
5867 a.write_array(&offsets).unwrap();
5868
5869 let bad = ds.new_attr::<i32>().shape([3]).create("bad").unwrap();
5871 assert!(bad.write_array(&[1i32, 2]).is_err());
5872
5873 file.close().unwrap();
5874 }
5875 {
5876 let file = H5File::open(&path).unwrap();
5877 let ds = file.dataset("data").unwrap();
5878 let a = ds.attr("dim_offset").unwrap();
5879 let raw = a.read_raw().unwrap();
5880 assert_eq!(raw.len(), 3 * 4);
5881 let got: Vec<i32> = raw
5882 .as_chunks::<4>()
5883 .0
5884 .iter()
5885 .map(|b| i32::from_le_bytes(*b))
5886 .collect();
5887 assert_eq!(got, offsets);
5888 }
5889 std::fs::remove_file(&path).ok();
5890 }
5891
5892 #[test]
5893 fn attr_datatype_exposes_class_and_sign() {
5894 use crate::format::messages::datatype::DatatypeMessage;
5898
5899 let path = temp_path("attr_datatype");
5900 {
5901 let file = H5File::create(&path).unwrap();
5902 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
5903 ds.new_attr::<f64>()
5904 .shape(())
5905 .create("scale")
5906 .unwrap()
5907 .write_numeric(&1.5f64)
5908 .unwrap();
5909 ds.new_attr::<i32>()
5910 .shape(())
5911 .create("count")
5912 .unwrap()
5913 .write_numeric(&7i32)
5914 .unwrap();
5915 file.close().unwrap();
5916 }
5917 {
5918 let file = H5File::open(&path).unwrap();
5919 let ds = file.dataset("data").unwrap();
5920
5921 match ds.attr("scale").unwrap().datatype().unwrap() {
5922 DatatypeMessage::FloatingPoint { size, .. } => assert_eq!(size, 8),
5923 other => panic!("expected FloatingPoint for f64 attr, got {other:?}"),
5924 }
5925
5926 match ds.attr("count").unwrap().datatype().unwrap() {
5927 DatatypeMessage::FixedPoint { size, signed, .. } => {
5928 assert_eq!(size, 4);
5929 assert!(signed, "i32 attr must be signed");
5930 }
5931 other => panic!("expected FixedPoint for i32 attr, got {other:?}"),
5932 }
5933 }
5934 std::fs::remove_file(&path).ok();
5935 }
5936
5937 #[test]
5938 fn attr_datatype_in_write_mode_errors() {
5939 let path = temp_path("attr_datatype_write_mode");
5940 let file = H5File::create(&path).unwrap();
5941 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
5942 let attr = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
5943 assert!(attr.datatype().is_err());
5944 std::fs::remove_file(&path).ok();
5945 }
5946
5947 #[test]
5948 fn cannot_create_dataset_in_read_mode() {
5949 let path = temp_path("no_create_read");
5950
5951 {
5952 let _file = H5File::create(&path).unwrap();
5953 }
5954
5955 {
5956 let file = H5File::open(&path).unwrap();
5957 let result = file.new_dataset::<u8>().shape([4]).create("x");
5958 assert!(result.is_err());
5959 }
5960
5961 std::fs::remove_file(&path).ok();
5962 }
5963
5964 #[test]
5965 fn shape_accessor() {
5966 let path = temp_path("shape_acc");
5967
5968 let file = H5File::create(&path).unwrap();
5969 let ds = file
5970 .new_dataset::<f32>()
5971 .shape([5, 10, 3])
5972 .create("tensor")
5973 .unwrap();
5974 assert_eq!(ds.shape(), vec![5, 10, 3]);
5975
5976 std::fs::remove_file(&path).ok();
5977 }
5978
5979 #[test]
5980 fn slice_roundtrip_2d() {
5981 let path = temp_path("slice_2d");
5982
5983 let data: Vec<i32> = (0..20).collect();
5985 {
5986 let file = H5File::create(&path).unwrap();
5987 let ds = file
5988 .new_dataset::<i32>()
5989 .shape([4, 5])
5990 .create("mat")
5991 .unwrap();
5992 ds.write_raw(&data).unwrap();
5993 file.close().unwrap();
5994 }
5995 {
5996 let file = H5File::open(&path).unwrap();
5997 let ds = file.dataset("mat").unwrap();
5998 let slice = ds.read_slice::<i32>(&[1, 2], &[2, 2]).unwrap();
6000 assert_eq!(slice, vec![7, 8, 12, 13]);
6003 }
6004
6005 std::fs::remove_file(&path).ok();
6006 }
6007
6008 fn assert_into_matches<T>(ds: &super::H5Dataset, starts: &[usize], counts: &[usize])
6013 where
6014 T: crate::types::H5Type + Copy + std::fmt::Debug + PartialEq + Default,
6015 {
6016 let n: usize = ds.shape().iter().product();
6017 let want_full = ds.read_raw::<T>().unwrap();
6018 let mut got_full = vec![T::default(); n];
6019 ds.read_raw_into::<T>(&mut got_full).unwrap();
6020 assert_eq!(got_full, want_full, "read_raw_into != read_raw");
6021
6022 let want_slice = ds.read_slice::<T>(starts, counts).unwrap();
6023 let sn: usize = counts.iter().product();
6024 let mut got_slice = vec![T::default(); sn];
6025 ds.read_slice_into::<T>(&mut got_slice, starts, counts)
6026 .unwrap();
6027 assert_eq!(got_slice, want_slice, "read_slice_into != read_slice");
6028 }
6029
6030 #[test]
6031 fn read_into_matches_vec_contiguous() {
6032 let path = temp_path("into_contig");
6033 let data: Vec<i32> = (0..20).collect(); {
6035 let file = H5File::create(&path).unwrap();
6036 let ds = file
6037 .new_dataset::<i32>()
6038 .shape([4, 5])
6039 .create("mat")
6040 .unwrap();
6041 ds.write_raw(&data).unwrap();
6042 file.close().unwrap();
6043 }
6044 {
6045 let file = H5File::open(&path).unwrap();
6046 let ds = file.dataset("mat").unwrap();
6047 assert_eq!(ds.chunk_dims(), None);
6048 assert_into_matches::<i32>(&ds, &[1, 2], &[2, 2]);
6049 }
6050 std::fs::remove_file(&path).ok();
6051 }
6052
6053 #[test]
6054 fn read_into_matches_vec_chunked_unfiltered() {
6055 let path = temp_path("into_chunk");
6056 let data: Vec<f64> = (0..35).map(|i| i as f64 * 1.5).collect(); {
6058 let file = H5File::create(&path).unwrap();
6059 let ds = file
6060 .new_dataset::<f64>()
6061 .shape([7, 5])
6062 .chunk(&[3, 2]) .create("grid")
6064 .unwrap();
6065 ds.write_raw(&data).unwrap();
6066 file.close().unwrap();
6067 }
6068 {
6069 let file = H5File::open(&path).unwrap();
6070 let ds = file.dataset("grid").unwrap();
6071 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
6072 assert_into_matches::<f64>(&ds, &[2, 1], &[3, 3]);
6074 }
6075 std::fs::remove_file(&path).ok();
6076 }
6077
6078 #[test]
6079 fn read_into_matches_vec_single_chunk() {
6080 let path = temp_path("into_single_chunk");
6081 let data: Vec<i32> = (0..12).collect(); {
6083 let file = H5File::create(&path).unwrap();
6084 let ds = file
6085 .new_dataset::<i32>()
6086 .shape([3, 4])
6087 .chunk(&[3, 4]) .create("g")
6089 .unwrap();
6090 ds.write_raw(&data).unwrap();
6091 file.close().unwrap();
6092 }
6093 {
6094 let file = H5File::open(&path).unwrap();
6095 let ds = file.dataset("g").unwrap();
6096 assert_eq!(ds.chunk_dims(), Some(vec![3, 4]));
6097 assert_into_matches::<i32>(&ds, &[1, 1], &[2, 2]);
6098 }
6099 std::fs::remove_file(&path).ok();
6100 }
6101
6102 #[cfg(feature = "deflate")]
6103 #[test]
6104 fn read_into_matches_vec_chunked_deflate() {
6105 let path = temp_path("into_chunk_deflate");
6106 let data: Vec<i32> = (0..35).collect(); {
6108 let file = H5File::create(&path).unwrap();
6109 let ds = file
6110 .new_dataset::<i32>()
6111 .shape([7, 5])
6112 .chunk(&[3, 2])
6113 .deflate(4)
6114 .create("grid")
6115 .unwrap();
6116 ds.write_raw(&data).unwrap();
6117 file.close().unwrap();
6118 }
6119 {
6120 let file = H5File::open(&path).unwrap();
6121 let ds = file.dataset("grid").unwrap();
6122 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
6123 assert_into_matches::<i32>(&ds, &[2, 1], &[3, 3]);
6124 }
6125 std::fs::remove_file(&path).ok();
6126 }
6127
6128 #[test]
6129 fn read_into_wrong_buffer_size_rejected() {
6130 let path = temp_path("into_badlen");
6131 let data: Vec<i32> = (0..20).collect(); {
6133 let file = H5File::create(&path).unwrap();
6134 let ds = file
6135 .new_dataset::<i32>()
6136 .shape([4, 5])
6137 .create("mat")
6138 .unwrap();
6139 ds.write_raw(&data).unwrap();
6140 file.close().unwrap();
6141 }
6142 {
6143 let file = H5File::open(&path).unwrap();
6144 let ds = file.dataset("mat").unwrap();
6145
6146 let mut small = vec![0i32; 19];
6148 assert!(ds.read_raw_into::<i32>(&mut small).is_err());
6149 let mut large = vec![0i32; 21];
6150 assert!(ds.read_raw_into::<i32>(&mut large).is_err());
6151
6152 let mut bad_slice = vec![0i32; 3];
6154 assert!(ds
6155 .read_slice_into::<i32>(&mut bad_slice, &[1, 2], &[2, 2])
6156 .is_err());
6157 let mut ok_slice = vec![0i32; 4];
6159 assert!(ds
6160 .read_slice_into::<i32>(&mut ok_slice, &[1, 2], &[2, 2])
6161 .is_ok());
6162 }
6163 std::fs::remove_file(&path).ok();
6164 }
6165
6166 #[test]
6167 fn read_into_wrong_element_size_rejected() {
6168 let path = temp_path("into_badtype");
6169 let data: Vec<i32> = (0..20).collect(); {
6171 let file = H5File::create(&path).unwrap();
6172 let ds = file
6173 .new_dataset::<i32>()
6174 .shape([4, 5])
6175 .create("mat")
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("mat").unwrap();
6183 let mut as_u8 = vec![0u8; 20];
6186 assert!(matches!(
6187 ds.read_raw_into::<u8>(&mut as_u8),
6188 Err(crate::Hdf5Error::TypeMismatch(_))
6189 ));
6190 let mut as_i64 = vec![0i64; 20];
6191 assert!(matches!(
6192 ds.read_slice_into::<i64>(&mut as_i64, &[0, 0], &[4, 5]),
6193 Err(crate::Hdf5Error::TypeMismatch(_))
6194 ));
6195 }
6196 std::fs::remove_file(&path).ok();
6197 }
6198
6199 #[test]
6200 fn write_slice_2d() {
6201 let path = temp_path("write_slice_2d");
6202
6203 {
6204 let file = H5File::create(&path).unwrap();
6205 let ds = file
6206 .new_dataset::<f32>()
6207 .shape([3, 4])
6208 .create("data")
6209 .unwrap();
6210 ds.write_raw(&[0.0f32; 12]).unwrap();
6211 ds.write_slice(&[1, 1], &[2, 2], &[10.0f32, 20.0, 30.0, 40.0])
6213 .unwrap();
6214 file.close().unwrap();
6215 }
6216 {
6217 let file = H5File::open(&path).unwrap();
6218 let ds = file.dataset("data").unwrap();
6219 let full = ds.read_raw::<f32>().unwrap();
6220 assert_eq!(
6224 full,
6225 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,]
6226 );
6227 }
6228
6229 std::fs::remove_file(&path).ok();
6230 }
6231
6232 fn chunk_of(v: i32) -> Vec<u8> {
6234 (0..8).flat_map(|_| v.to_le_bytes()).collect()
6235 }
6236
6237 fn rewrite_chunk(
6241 tag: &str,
6242 rewrites: i32,
6243 build: impl Fn(&H5File) -> crate::H5Dataset,
6244 ) -> (u64, i32) {
6245 let path = temp_path(tag);
6246 {
6247 let file = H5File::create(&path).unwrap();
6248 let ds = build(&file);
6249 for v in 1..=rewrites {
6250 ds.write_chunk_at(&[0, 0], &chunk_of(v)).unwrap();
6251 }
6252 file.close().unwrap();
6253 }
6254 let size = std::fs::metadata(&path).unwrap().len();
6255 let first = {
6256 let file = H5File::open(&path).unwrap();
6257 file.dataset("d").unwrap().read_raw::<i32>().unwrap()[0]
6258 };
6259 std::fs::remove_file(&path).ok();
6260 (size, first)
6261 }
6262
6263 #[test]
6269 fn rewriting_an_unfiltered_extensible_array_chunk_stays_in_place() {
6270 let build = |f: &H5File| {
6271 f.new_dataset::<i32>()
6272 .shape([2, 4])
6273 .chunk(&[2, 4])
6274 .max_shape(&[None, Some(4)])
6275 .create("d")
6276 .unwrap()
6277 };
6278 let (once, _) = rewrite_chunk("rewrite_ea_1", 1, build);
6279 let (many, last) = rewrite_chunk("rewrite_ea_8", 8, build);
6280 assert_eq!(many, once, "8 rewrites grew the file past a single write");
6281 assert_eq!(last, 8, "the last write must be the one that survives");
6282 }
6283
6284 #[test]
6285 fn rewriting_an_unfiltered_fixed_array_chunk_stays_in_place() {
6286 let build = |f: &H5File| {
6287 f.new_dataset::<i32>()
6288 .shape([2, 4])
6289 .chunk(&[2, 4])
6290 .create("d")
6291 .unwrap()
6292 };
6293 let (once, _) = rewrite_chunk("rewrite_fa_1", 1, build);
6294 let (many, last) = rewrite_chunk("rewrite_fa_8", 8, build);
6295 assert_eq!(many, once, "8 rewrites grew the file past a single write");
6296 assert_eq!(last, 8);
6297 }
6298
6299 #[test]
6300 fn rewriting_an_unfiltered_btree_v2_chunk_stays_in_place() {
6301 let build = |f: &H5File| {
6302 f.new_dataset::<i32>()
6303 .shape([2, 4])
6304 .chunk(&[2, 4])
6305 .max_shape(&[None, None])
6306 .create("d")
6307 .unwrap()
6308 };
6309 let (once, _) = rewrite_chunk("rewrite_bt2_1", 1, build);
6310 let (many, last) = rewrite_chunk("rewrite_bt2_8", 8, build);
6311 assert_eq!(many, once, "8 rewrites grew the file past a single write");
6312 assert_eq!(last, 8);
6313 }
6314
6315 #[test]
6320 fn repeated_flushes_do_not_grow_a_btree_v2_index() {
6321 let flush_n = |label: &str, flushes: usize| -> u64 {
6322 let path = temp_path(label);
6323 {
6324 let file = H5File::create(&path).unwrap();
6325 let ds = file
6326 .new_dataset::<i32>()
6327 .shape([2, 4])
6328 .chunk(&[2, 4])
6329 .max_shape(&[None, None])
6330 .create("d")
6331 .unwrap();
6332 let bytes: Vec<u8> = (0..8i32).flat_map(|v| v.to_le_bytes()).collect();
6333 ds.write_chunk_at(&[0, 0], &bytes).unwrap();
6334 for _ in 0..flushes {
6335 ds.flush().unwrap();
6336 }
6337 file.close().unwrap();
6338 }
6339 let size = std::fs::metadata(&path).unwrap().len();
6340 {
6342 let file = H5File::open(&path).unwrap();
6343 let ds = file.dataset("d").unwrap();
6344 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..8).collect::<Vec<i32>>());
6345 }
6346 std::fs::remove_file(&path).ok();
6347 size
6348 };
6349 assert_eq!(
6350 flush_n("bt2_flush_8", 8),
6351 flush_n("bt2_flush_1", 1),
6352 "8 index flushes grew the file past a single one"
6353 );
6354 }
6355
6356 #[cfg(feature = "deflate")]
6362 #[test]
6363 fn rewriting_a_filtered_chunk_recycles_the_released_block() {
6364 let flat: Vec<u8> = (0..8).flat_map(|_| 7i32.to_le_bytes()).collect();
6367 let varied: Vec<u8> = (0..8i32)
6368 .flat_map(|i| i.wrapping_mul(0x5bd1_e995).to_le_bytes())
6369 .collect();
6370
6371 let sizes: Vec<u64> = [1usize, 8]
6372 .iter()
6373 .map(|&rounds| {
6374 let path = temp_path(&format!("rewrite_filtered_{rounds}"));
6375 {
6376 let file = H5File::create(&path).unwrap();
6377 let ds = file
6378 .new_dataset::<i32>()
6379 .shape([2, 4])
6380 .chunk(&[2, 4])
6381 .max_shape(&[None, Some(4)])
6382 .deflate(6)
6383 .create("d")
6384 .unwrap();
6385 for _ in 0..rounds {
6386 ds.write_chunk_at(&[0, 0], &flat).unwrap();
6387 ds.write_chunk_at(&[0, 0], &varied).unwrap();
6388 }
6389 file.close().unwrap();
6390 }
6391 let size = std::fs::metadata(&path).unwrap().len();
6392 {
6393 let file = H5File::open(&path).unwrap();
6394 let got = file.dataset("d").unwrap().read_raw::<i32>().unwrap();
6395 let want: Vec<i32> = (0..8i32).map(|i| i.wrapping_mul(0x5bd1_e995)).collect();
6396 assert_eq!(got, want, "the last write must survive the round trip");
6397 }
6398 std::fs::remove_file(&path).ok();
6399 size
6400 })
6401 .collect();
6402
6403 assert_eq!(
6404 sizes[1], sizes[0],
6405 "8 alternating rewrites grew the file past a single pair"
6406 );
6407 }
6408
6409 #[test]
6410 fn write_slice_out_of_bounds_rejected() {
6411 let path = temp_path("write_slice_oob");
6412 let file = H5File::create(&path).unwrap();
6413 let ds = file.new_dataset::<i32>().shape([4]).create("d").unwrap();
6414 ds.write_raw(&[0i32; 4]).unwrap();
6415 assert!(ds.write_slice(&[2], &[6], &[9i32; 6]).is_err());
6417 assert!(ds.write_slice(&[1], &[2], &[7i32, 8]).is_ok());
6419 std::fs::remove_file(&path).ok();
6420 }
6421
6422 #[test]
6423 fn duplicate_dataset_name_rejected() {
6424 let path = temp_path("dup_name");
6425 let file = H5File::create(&path).unwrap();
6426 let _ = file.new_dataset::<i32>().shape([2]).create("d").unwrap();
6427 assert!(file.new_dataset::<i32>().shape([2]).create("d").is_err());
6428 std::fs::remove_file(&path).ok();
6429 }
6430
6431 #[test]
6432 fn extend_cannot_shrink() {
6433 let path = temp_path("extend_shrink");
6434 let file = H5File::create(&path).unwrap();
6435 let ds = file
6436 .new_dataset::<i32>()
6437 .shape([0])
6438 .chunk(&[2])
6439 .max_shape(&[None])
6440 .create("d")
6441 .unwrap();
6442 ds.append(&[1i32, 2, 3, 4]).unwrap();
6443 assert!(ds.extend(&[2]).is_err());
6445 assert!(ds.extend(&[6]).is_ok());
6447 std::fs::remove_file(&path).ok();
6448 }
6449
6450 #[test]
6451 fn attr_read_roundtrip() {
6452 use crate::types::VarLenUnicode;
6453 let path = temp_path("attr_read");
6454
6455 {
6456 let file = H5File::create(&path).unwrap();
6457 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
6458 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
6459 let a1 = ds
6460 .new_attr::<VarLenUnicode>()
6461 .shape(())
6462 .create("units")
6463 .unwrap();
6464 a1.write_string("meters").unwrap();
6465 let a2 = ds
6466 .new_attr::<VarLenUnicode>()
6467 .shape(())
6468 .create("desc")
6469 .unwrap();
6470 a2.write_string("test data").unwrap();
6471 file.close().unwrap();
6472 }
6473 {
6474 let file = H5File::open(&path).unwrap();
6475 let ds = file.dataset("data").unwrap();
6476
6477 let names = ds.attr_names().unwrap();
6478 assert!(names.contains(&"units".to_string()));
6479 assert!(names.contains(&"desc".to_string()));
6480
6481 let units = ds.attr("units").unwrap();
6482 assert_eq!(units.read_string().unwrap(), "meters");
6483
6484 let desc = ds.attr("desc").unwrap();
6485 assert_eq!(desc.read_string().unwrap(), "test data");
6486 }
6487
6488 std::fs::remove_file(&path).ok();
6489 }
6490
6491 #[test]
6492 fn type_mismatch_element_size() {
6493 let path = temp_path("type_mismatch");
6494
6495 {
6496 let file = H5File::create(&path).unwrap();
6497 let ds = file.new_dataset::<f64>().shape([4]).create("data").unwrap();
6498 ds.write_raw(&[1.0f64, 2.0, 3.0, 4.0]).unwrap();
6499 file.close().unwrap();
6500 }
6501
6502 {
6503 let file = H5File::open(&path).unwrap();
6504 let ds = file.dataset("data").unwrap();
6505 let result = ds.read_raw::<u8>();
6507 assert!(result.is_err());
6508 }
6509
6510 std::fs::remove_file(&path).ok();
6511 }
6512
6513 #[test]
6514 fn dataset_survives_file_move() {
6515 let path = temp_path("ds_survives");
6516
6517 let ds = {
6518 let file = H5File::create(&path).unwrap();
6519 file.new_dataset::<u8>().shape([4]).create("x").unwrap()
6520 };
6521 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
6523 std::fs::remove_file(&path).ok();
6526 }
6527
6528 #[test]
6529 fn new_attr_scalar_string() {
6530 use crate::types::VarLenUnicode;
6531
6532 let path = temp_path("attr_scalar_string");
6533 {
6534 let file = H5File::create(&path).unwrap();
6535 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
6536 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
6537
6538 let attr = ds
6539 .new_attr::<VarLenUnicode>()
6540 .shape(())
6541 .create("name")
6542 .unwrap();
6543 attr.write_scalar(&VarLenUnicode("test_value".to_string()))
6544 .unwrap();
6545
6546 file.close().unwrap();
6547 }
6548
6549 {
6551 use crate::format::messages::datatype::DatatypeMessage;
6552 let file = H5File::open(&path).unwrap();
6553 let ds = file.dataset("data").unwrap();
6554 assert_eq!(ds.shape(), vec![4]);
6555 let readback = ds.read_raw::<u8>().unwrap();
6556 assert_eq!(readback, vec![1u8, 2, 3, 4]);
6557
6558 let attr = ds.attr("name").unwrap();
6561 assert!(
6562 matches!(
6563 attr.datatype().unwrap(),
6564 DatatypeMessage::VarLenString { .. }
6565 ),
6566 "string attribute should have a variable-length string datatype"
6567 );
6568 assert_eq!(attr.read_string().unwrap(), "test_value");
6569 }
6570
6571 std::fs::remove_file(&path).ok();
6572 }
6573
6574 #[test]
6575 fn all_numeric_types_roundtrip() {
6576 let path = temp_path("all_types");
6577
6578 {
6579 let file = H5File::create(&path).unwrap();
6580
6581 let ds = file.new_dataset::<u8>().shape([2]).create("u8").unwrap();
6582 ds.write_raw(&[1u8, 2]).unwrap();
6583
6584 let ds = file.new_dataset::<i8>().shape([2]).create("i8").unwrap();
6585 ds.write_raw(&[-1i8, 1]).unwrap();
6586
6587 let ds = file.new_dataset::<u16>().shape([2]).create("u16").unwrap();
6588 ds.write_raw(&[100u16, 200]).unwrap();
6589
6590 let ds = file.new_dataset::<i16>().shape([2]).create("i16").unwrap();
6591 ds.write_raw(&[-100i16, 100]).unwrap();
6592
6593 let ds = file.new_dataset::<u32>().shape([2]).create("u32").unwrap();
6594 ds.write_raw(&[1000u32, 2000]).unwrap();
6595
6596 let ds = file.new_dataset::<i32>().shape([2]).create("i32").unwrap();
6597 ds.write_raw(&[-1000i32, 1000]).unwrap();
6598
6599 let ds = file.new_dataset::<u64>().shape([2]).create("u64").unwrap();
6600 ds.write_raw(&[10000u64, 20000]).unwrap();
6601
6602 let ds = file.new_dataset::<i64>().shape([2]).create("i64").unwrap();
6603 ds.write_raw(&[-10000i64, 10000]).unwrap();
6604
6605 let ds = file.new_dataset::<f32>().shape([2]).create("f32").unwrap();
6606 ds.write_raw(&[1.5f32, 2.5]).unwrap();
6607
6608 let ds = file.new_dataset::<f64>().shape([2]).create("f64").unwrap();
6609 ds.write_raw(&[1.23456f64, 7.89012]).unwrap();
6610
6611 file.close().unwrap();
6612 }
6613
6614 {
6615 let file = H5File::open(&path).unwrap();
6616
6617 assert_eq!(
6618 file.dataset("u8").unwrap().read_raw::<u8>().unwrap(),
6619 vec![1u8, 2]
6620 );
6621 assert_eq!(
6622 file.dataset("i8").unwrap().read_raw::<i8>().unwrap(),
6623 vec![-1i8, 1]
6624 );
6625 assert_eq!(
6626 file.dataset("u16").unwrap().read_raw::<u16>().unwrap(),
6627 vec![100u16, 200]
6628 );
6629 assert_eq!(
6630 file.dataset("i16").unwrap().read_raw::<i16>().unwrap(),
6631 vec![-100i16, 100]
6632 );
6633 assert_eq!(
6634 file.dataset("u32").unwrap().read_raw::<u32>().unwrap(),
6635 vec![1000u32, 2000]
6636 );
6637 assert_eq!(
6638 file.dataset("i32").unwrap().read_raw::<i32>().unwrap(),
6639 vec![-1000i32, 1000]
6640 );
6641 assert_eq!(
6642 file.dataset("u64").unwrap().read_raw::<u64>().unwrap(),
6643 vec![10000u64, 20000]
6644 );
6645 assert_eq!(
6646 file.dataset("i64").unwrap().read_raw::<i64>().unwrap(),
6647 vec![-10000i64, 10000]
6648 );
6649 assert_eq!(
6650 file.dataset("f32").unwrap().read_raw::<f32>().unwrap(),
6651 vec![1.5f32, 2.5]
6652 );
6653 assert_eq!(
6654 file.dataset("f64").unwrap().read_raw::<f64>().unwrap(),
6655 vec![1.23456f64, 7.89012]
6656 );
6657 }
6658
6659 std::fs::remove_file(&path).ok();
6660 }
6661
6662 #[test]
6663 fn append_chunked_roundtrip() {
6664 let path = temp_path("append_chunked");
6665
6666 {
6667 let file = H5File::create(&path).unwrap();
6668 let ds = file
6669 .new_dataset::<f64>()
6670 .shape([0, 3])
6671 .chunk(&[1, 3])
6672 .max_shape(&[None, Some(3)])
6673 .create("data")
6674 .unwrap();
6675
6676 ds.append(&[1.0f64, 2.0, 3.0]).unwrap();
6678 ds.append(&[4.0f64, 5.0, 6.0, 7.0, 8.0, 9.0]).unwrap();
6680
6681 file.close().unwrap();
6682 }
6683
6684 {
6685 let file = H5File::open(&path).unwrap();
6686 let ds = file.dataset("data").unwrap();
6687 assert_eq!(ds.shape(), vec![3, 3]);
6688 let all = ds.read_raw::<f64>().unwrap();
6689 assert_eq!(all, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
6690 }
6691
6692 std::fs::remove_file(&path).ok();
6693 }
6694
6695 #[test]
6696 fn append_1d_chunked() {
6697 let path = temp_path("append_1d");
6698
6699 {
6700 let file = H5File::create(&path).unwrap();
6701 let ds = file
6702 .new_dataset::<i32>()
6703 .shape([0])
6704 .chunk(&[4])
6705 .max_shape(&[None])
6706 .create("values")
6707 .unwrap();
6708
6709 ds.append(&[10i32, 20, 30]).unwrap(); ds.append(&[40i32]).unwrap(); ds.append(&[50i32, 60, 70, 80]).unwrap(); file.close().unwrap();
6714 }
6715
6716 {
6717 let file = H5File::open(&path).unwrap();
6718 let ds = file.dataset("values").unwrap();
6719 assert_eq!(ds.shape(), vec![8]);
6720 let all = ds.read_raw::<i32>().unwrap();
6721 assert_eq!(all, vec![10, 20, 30, 40, 50, 60, 70, 80]);
6722 }
6723
6724 std::fs::remove_file(&path).ok();
6725 }
6726
6727 #[test]
6728 fn append_partial_chunk_flushed_on_close() {
6729 let path = temp_path("append_partial_close");
6730
6731 {
6732 let file = H5File::create(&path).unwrap();
6733 let ds = file
6734 .new_dataset::<f64>()
6735 .shape([0])
6736 .chunk(&[4])
6737 .max_shape(&[None])
6738 .create("vals")
6739 .unwrap();
6740
6741 ds.append(&[1.0f64, 2.0, 3.0, 4.0, 5.0]).unwrap();
6743 file.close().unwrap();
6744 }
6745
6746 {
6747 let file = H5File::open(&path).unwrap();
6748 let ds = file.dataset("vals").unwrap();
6749 assert_eq!(ds.shape(), vec![5]);
6750 let all = ds.read_raw::<f64>().unwrap();
6751 assert_eq!(all.len(), 5);
6754 assert_eq!(all, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
6755 }
6756
6757 std::fs::remove_file(&path).ok();
6758 }
6759
6760 #[test]
6768 fn append_after_reopen_keeps_the_partial_chunk_it_lands_in() {
6769 let path = temp_path("append_reopen_partial");
6770
6771 {
6772 let file = H5File::create(&path).unwrap();
6773 let ds = file
6774 .new_dataset::<i32>()
6775 .shape([0, 3])
6776 .chunk(&[4, 3])
6777 .max_shape(&[None, Some(3)])
6778 .create("values")
6779 .unwrap();
6780 ds.append(&[1, 2, 3]).unwrap();
6781 file.close().unwrap();
6782 }
6783 for rows in [
6784 vec![4, 5, 6],
6785 vec![7, 8, 9, 10, 11, 12, 13, 14, 15],
6786 vec![16, 17, 18],
6787 ] {
6788 let file = H5File::open_rw(&path).unwrap();
6789 file.dataset_writer("values")
6790 .unwrap()
6791 .append(&rows)
6792 .unwrap();
6793 file.close().unwrap();
6794 }
6795
6796 let file = H5File::open(&path).unwrap();
6797 let ds = file.dataset("values").unwrap();
6798 assert_eq!(ds.shape(), vec![6, 3]);
6799 assert_eq!(
6800 ds.read_raw::<i32>().unwrap(),
6801 (1..=18).collect::<Vec<i32>>()
6802 );
6803 std::fs::remove_file(&path).ok();
6804 }
6805
6806 #[cfg(feature = "deflate")]
6807 #[test]
6808 fn vlen_append_after_reopen_filtered() {
6809 let path = temp_path("vlen_reopen_filtered");
6813 {
6814 let file = H5File::create(&path).unwrap();
6815 file.create_appendable_vlen_dataset(
6816 "strs",
6817 4,
6818 Some(crate::format::messages::filter::FilterPipeline::deflate(6)),
6819 )
6820 .unwrap();
6821 file.append_vlen_strings("strs", &["alpha", "beta", "gamma"])
6822 .unwrap();
6823 file.close().unwrap();
6824 }
6825 {
6826 let file = H5File::open_rw(&path).unwrap();
6827 file.append_vlen_strings("strs", &["delta"]).unwrap();
6828 file.close().unwrap();
6829 }
6830 {
6831 let file = H5File::open(&path).unwrap();
6832 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
6833 assert_eq!(
6834 got.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
6835 vec!["alpha", "beta", "gamma", "delta"]
6836 );
6837 }
6838 std::fs::remove_file(&path).ok();
6839 }
6840
6841 #[test]
6842 fn vlen_append_after_reopen_data_block() {
6843 let path = temp_path("vlen_reopen_datablk");
6847 let labels: Vec<String> = (0..9).map(|i| format!("s{i}")).collect();
6848 {
6849 let file = H5File::create(&path).unwrap();
6850 file.create_appendable_vlen_dataset("strs", 2, None)
6851 .unwrap();
6852 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
6853 file.append_vlen_strings("strs", &refs).unwrap();
6854 file.close().unwrap();
6855 }
6856 {
6857 let file = H5File::open_rw(&path).unwrap();
6858 file.append_vlen_strings("strs", &["s9"]).unwrap();
6859 file.close().unwrap();
6860 }
6861 {
6862 let file = H5File::open(&path).unwrap();
6863 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
6864 let want: Vec<String> = (0..10).map(|i| format!("s{i}")).collect();
6865 assert_eq!(got, want);
6866 }
6867 std::fs::remove_file(&path).ok();
6868 }
6869
6870 #[test]
6871 fn vlen_append_after_reopen_super_block() {
6872 let path = temp_path("vlen_reopen_super");
6880 let labels: Vec<String> = (0..489).map(|i| format!("v{i}")).collect();
6883 {
6884 let file = H5File::create(&path).unwrap();
6885 file.create_appendable_vlen_dataset("strs", 2, None)
6886 .unwrap();
6887 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
6888 file.append_vlen_strings("strs", &refs).unwrap();
6889 file.close().unwrap();
6890 }
6891 {
6892 let file = H5File::open_rw(&path).unwrap();
6893 file.append_vlen_strings("strs", &["v489"]).unwrap();
6894 file.close().unwrap();
6895 }
6896 {
6897 let file = H5File::open(&path).unwrap();
6898 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
6899 let want: Vec<String> = (0..490).map(|i| format!("v{i}")).collect();
6900 assert_eq!(got, want);
6901 }
6902 std::fs::remove_file(&path).ok();
6903 }
6904
6905 #[cfg(feature = "deflate")]
6906 #[test]
6907 fn vlen_append_after_reopen_filtered_data_block() {
6908 let path = temp_path("vlen_reopen_filt_datablk");
6911 let labels: Vec<String> = (0..9).map(|i| format!("item{i:02}")).collect();
6912 {
6913 let file = H5File::create(&path).unwrap();
6914 file.create_appendable_vlen_dataset(
6915 "strs",
6916 2,
6917 Some(crate::format::messages::filter::FilterPipeline::deflate(6)),
6918 )
6919 .unwrap();
6920 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
6921 file.append_vlen_strings("strs", &refs).unwrap();
6922 file.close().unwrap();
6923 }
6924 {
6925 let file = H5File::open_rw(&path).unwrap();
6926 file.append_vlen_strings("strs", &["item09"]).unwrap();
6927 file.close().unwrap();
6928 }
6929 {
6930 let file = H5File::open(&path).unwrap();
6931 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
6932 let want: Vec<String> = (0..10).map(|i| format!("item{i:02}")).collect();
6933 assert_eq!(got, want);
6934 }
6935 std::fs::remove_file(&path).ok();
6936 }
6937
6938 #[test]
6939 fn group_nx_class_attribute_roundtrip() {
6940 let path = temp_path("group_nx_class");
6943 {
6944 let file = H5File::create(&path).unwrap();
6945 let entry = file.create_group("entry").unwrap();
6946 entry.set_attr_string("NX_class", "NXentry").unwrap();
6947 let det = entry.create_group("detector").unwrap();
6948 det.set_attr_string("NX_class", "NXdetector").unwrap();
6949 det.set_attr_numeric("frame_count", &7i32).unwrap();
6950 det.new_dataset::<f32>()
6951 .shape([4])
6952 .create("data")
6953 .unwrap()
6954 .write_raw(&[1.0f32; 4])
6955 .unwrap();
6956 file.close().unwrap();
6957 }
6958 {
6959 let file = H5File::open(&path).unwrap();
6960 let entry = file.root_group().group("entry").unwrap();
6961 assert_eq!(entry.attr_string("NX_class").unwrap(), "NXentry");
6962 let det = entry.group("detector").unwrap();
6963 assert_eq!(det.attr_string("NX_class").unwrap(), "NXdetector");
6964 let names = det.attr_names().unwrap();
6965 assert!(names.contains(&"NX_class".to_string()));
6966 assert!(names.contains(&"frame_count".to_string()));
6967 }
6968 std::fs::remove_file(&path).ok();
6969 }
6970
6971 #[test]
6972 fn ea_super_block_roundtrip() {
6973 let path = temp_path("ea_super_rt");
6976 {
6977 let file = H5File::create(&path).unwrap();
6978 let ds = file
6979 .new_dataset::<i32>()
6980 .shape([0])
6981 .chunk(&[1])
6982 .max_shape(&[None])
6983 .create("v")
6984 .unwrap();
6985 ds.append(&(0..2000).collect::<Vec<i32>>()).unwrap();
6986 file.close().unwrap();
6987 }
6988 {
6989 let file = H5File::open(&path).unwrap();
6990 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
6991 assert_eq!(v.len(), 2000);
6992 assert!(v.iter().enumerate().all(|(i, &x)| x == i as i32));
6993 }
6994 std::fs::remove_file(&path).ok();
6995 }
6996
6997 #[cfg(feature = "deflate")]
6998 #[test]
6999 fn ea_filtered_super_block_roundtrip() {
7000 let path = temp_path("ea_filt_super");
7002 {
7003 let file = H5File::create(&path).unwrap();
7004 let ds = file
7005 .new_dataset::<i32>()
7006 .shape([0])
7007 .chunk(&[1])
7008 .max_shape(&[None])
7009 .deflate(4)
7010 .create("v")
7011 .unwrap();
7012 ds.append(&(0..600).collect::<Vec<i32>>()).unwrap();
7013 file.close().unwrap();
7014 }
7015 {
7016 let file = H5File::open(&path).unwrap();
7017 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7018 assert_eq!(v, (0..600).collect::<Vec<i32>>());
7019 }
7020 std::fs::remove_file(&path).ok();
7021 }
7022
7023 #[test]
7024 fn ea_super_block_open_append() {
7025 let path = temp_path("ea_super_append");
7027 {
7028 let file = H5File::create(&path).unwrap();
7029 let ds = file
7030 .new_dataset::<i32>()
7031 .shape([0])
7032 .chunk(&[1])
7033 .max_shape(&[None])
7034 .create("v")
7035 .unwrap();
7036 ds.append(&(0..300).collect::<Vec<i32>>()).unwrap();
7037 file.close().unwrap();
7038 }
7039 {
7040 let w = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7041 let idx = w.dataset_index("v").unwrap();
7042 for c in 300..900u64 {
7043 w.write_chunk(idx, c, &(c as i32).to_le_bytes()).unwrap();
7044 }
7045 w.extend_dataset(idx, &[900]).unwrap();
7046 w.close().unwrap();
7047 }
7048 {
7049 let file = H5File::open(&path).unwrap();
7050 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7051 assert_eq!(v.len(), 900);
7052 assert!(v.iter().enumerate().all(|(i, &x)| x == i as i32));
7053 }
7054 std::fs::remove_file(&path).ok();
7055 }
7056
7057 #[cfg(feature = "deflate")]
7063 #[test]
7064 fn compressed_multi_unlimited_dataset_roundtrips() {
7065 let path = temp_path("bt2_filtered");
7066 {
7067 let file = H5File::create(&path).unwrap();
7068 let ds = file
7069 .new_dataset::<i32>()
7070 .shape([6, 8])
7071 .chunk(&[2, 4])
7072 .max_shape(&[None, None])
7073 .deflate(6)
7074 .create("d")
7075 .unwrap();
7076 ds.write_slice(&[0, 0], &[6, 8], &[7i32; 48]).unwrap();
7077 ds.write_slice(&[1, 1], &[2, 2], &[1i32, 2, 3, 4]).unwrap();
7080 file.close().unwrap();
7081 }
7082 {
7083 let file = H5File::open(&path).unwrap();
7084 let ds = file.dataset("d").unwrap();
7085 assert_eq!(ds.shape(), vec![6, 8]);
7086 let mut want = vec![7i32; 48];
7087 want[9] = 1;
7088 want[10] = 2;
7089 want[17] = 3;
7090 want[18] = 4;
7091 assert_eq!(ds.read_raw::<i32>().unwrap(), want);
7092 }
7093 std::fs::remove_file(&path).ok();
7094 }
7095
7096 #[test]
7097 fn btree_v2_multi_unlimited_roundtrip() {
7098 let path = temp_path("bt2_multi");
7101 {
7102 let file = H5File::create(&path).unwrap();
7103 let ds = file
7104 .new_dataset::<i32>()
7105 .shape([0, 0])
7106 .chunk(&[2, 2])
7107 .max_shape(&[None, None])
7108 .create("grid")
7109 .unwrap();
7110 assert!(ds.is_chunked());
7111 for cr in 0..2usize {
7113 for cc in 0..2usize {
7114 let mut bytes = Vec::new();
7115 for i in 0..2usize {
7116 for j in 0..2usize {
7117 let v = ((cr * 2 + i) * 4 + (cc * 2 + j)) as i32;
7118 bytes.extend_from_slice(&v.to_le_bytes());
7119 }
7120 }
7121 ds.write_chunk_at(&[cr, cc], &bytes).unwrap();
7122 }
7123 }
7124 file.close().unwrap();
7125 }
7126 {
7127 let file = H5File::open(&path).unwrap();
7128 let ds = file.dataset("grid").unwrap();
7129 assert_eq!(ds.shape(), vec![4, 4]);
7130 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..16).collect::<Vec<i32>>());
7131 }
7132 std::fs::remove_file(&path).ok();
7133 }
7134
7135 #[test]
7136 fn subframe_chunking_roundtrip() {
7137 let path = temp_path("subframe");
7141 {
7142 let file = H5File::create(&path).unwrap();
7143 let ds = file
7144 .new_dataset::<i32>()
7145 .shape([0, 8, 8])
7146 .chunk(&[1, 4, 4])
7147 .max_shape(&[None, Some(8), Some(8)])
7148 .create("v")
7149 .unwrap();
7150 for f in 0..3usize {
7151 for cr in 0..2usize {
7152 for cc in 0..2usize {
7153 let mut bytes = Vec::new();
7154 for i in 0..4usize {
7155 for j in 0..4usize {
7156 let v = (f * 64 + (cr * 4 + i) * 8 + (cc * 4 + j)) as i32;
7157 bytes.extend_from_slice(&v.to_le_bytes());
7158 }
7159 }
7160 ds.write_chunk_at(&[f, cr, cc], &bytes).unwrap();
7161 }
7162 }
7163 }
7164 file.close().unwrap();
7165 }
7166 {
7167 let file = H5File::open(&path).unwrap();
7168 let ds = file.dataset("v").unwrap();
7169 assert_eq!(ds.shape(), vec![3, 8, 8]);
7170 assert_eq!(
7171 ds.read_raw::<i32>().unwrap(),
7172 (0..192).collect::<Vec<i32>>()
7173 );
7174 }
7175 std::fs::remove_file(&path).ok();
7176 }
7177
7178 #[test]
7179 fn fill_value_contiguous_roundtrip() {
7180 let path = temp_path("fill_value_contig");
7181 {
7182 let file = H5File::create(&path).unwrap();
7183 let ds = file
7184 .new_dataset::<f32>()
7185 .shape([4])
7186 .fill_value(2.5f32)
7187 .create("data")
7188 .unwrap();
7189 ds.write_raw(&[1.0f32, 2.0, 3.0, 4.0]).unwrap();
7190 file.close().unwrap();
7191 }
7192 {
7194 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7195 let idx = writer.dataset_index("data").unwrap();
7196 assert_eq!(
7197 writer.ds(idx).lock().fill_value,
7198 Some(2.5f32.to_le_bytes().to_vec())
7199 );
7200 }
7201 {
7203 let file = H5File::open(&path).unwrap();
7204 let ds = file.dataset("data").unwrap();
7205 assert_eq!(ds.read_raw::<f32>().unwrap(), vec![1.0, 2.0, 3.0, 4.0]);
7206 }
7207 std::fs::remove_file(&path).ok();
7208 }
7209
7210 #[test]
7215 fn early_allocation_writes_the_implicit_index() {
7216 let path = temp_path("implicit_index");
7217 {
7218 let file = H5File::create(&path).unwrap();
7219 let ds = file
7220 .new_dataset::<i32>()
7221 .shape([16])
7222 .chunk(&[4])
7223 .early_allocation()
7224 .create("data")
7225 .unwrap();
7226 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap();
7227 file.close().unwrap();
7228 }
7229 let bytes = std::fs::read(&path).unwrap();
7230 for magic in [b"EAHD", b"EAIB", b"FAHD", b"FADB", b"BTHD", b"TREE"] {
7231 assert!(
7232 !bytes.windows(4).any(|w| w == magic),
7233 "{} appears in a file whose chunk index is supposed to be no \
7234 structure at all",
7235 String::from_utf8_lossy(magic)
7236 );
7237 }
7238 {
7239 let file = H5File::open(&path).unwrap();
7240 let ds = file.dataset("data").unwrap();
7241 assert_eq!(
7242 ds.read_raw::<i32>().unwrap(),
7243 (0..16i32).collect::<Vec<_>>()
7244 );
7245 }
7246 std::fs::remove_file(&path).ok();
7247 }
7248
7249 #[test]
7255 #[cfg(feature = "deflate")]
7256 fn early_allocation_only_picks_implicit_where_libhdf5_does() {
7257 for (which, magic) in [("unlimited", b"EAHD"), ("filtered", b"FAHD")] {
7260 let path = temp_path("implicit_not");
7261 {
7262 let file = H5File::create(&path).unwrap();
7263 let builder = file.new_dataset::<i32>().shape([16]);
7264 let builder = match which {
7265 "unlimited" => builder.chunk(&[4]).max_shape(&[None]),
7266 _ => builder.chunk(&[4]).deflate(6),
7267 };
7268 let ds = builder.early_allocation().create("data").unwrap();
7269 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap();
7270 file.close().unwrap();
7271 }
7272 let bytes = std::fs::read(&path).unwrap();
7273 assert!(
7274 bytes.windows(4).any(|w| w == magic),
7275 "{which}: expected a {} index",
7276 String::from_utf8_lossy(magic)
7277 );
7278 let file = H5File::open(&path).unwrap();
7279 assert_eq!(
7280 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
7281 (0..16i32).collect::<Vec<_>>(),
7282 "{which}"
7283 );
7284 std::fs::remove_file(&path).ok();
7285 }
7286 }
7287
7288 #[test]
7296 fn one_whole_dataset_chunk_writes_the_single_chunk_index() {
7297 for early in [false, true] {
7298 let path = temp_path("single_chunk_index");
7299 {
7300 let file = H5File::create(&path).unwrap();
7301 let builder = file.new_dataset::<i32>().shape([16]).chunk(&[16]);
7302 let builder = if early {
7303 builder.early_allocation()
7304 } else {
7305 builder
7306 };
7307 let ds = builder.create("data").unwrap();
7308 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap();
7309 file.close().unwrap();
7310 }
7311 let bytes = std::fs::read(&path).unwrap();
7312 for magic in [b"EAHD", b"EAIB", b"FAHD", b"FADB", b"BTHD", b"TREE"] {
7313 assert!(
7314 !bytes.windows(4).any(|w| w == magic),
7315 "early={early}: {} appears in a file whose chunk index is \
7316 supposed to be no structure at all",
7317 String::from_utf8_lossy(magic)
7318 );
7319 }
7320 let file = H5File::open(&path).unwrap();
7321 assert_eq!(
7322 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
7323 (0..16i32).collect::<Vec<_>>(),
7324 "early={early}"
7325 );
7326 std::fs::remove_file(&path).ok();
7327 }
7328 }
7329
7330 #[test]
7334 fn implicit_index_fills_every_chunk_at_create() {
7335 let path = temp_path("implicit_fill");
7336 {
7337 let file = H5File::create(&path).unwrap();
7338 let ds = file
7339 .new_dataset::<i32>()
7340 .shape([8])
7341 .chunk(&[4])
7342 .early_allocation()
7343 .fill_value(-3i32)
7344 .create("data")
7345 .unwrap();
7346 let chunk: Vec<u8> = [1i32, 2, 3, 4]
7348 .iter()
7349 .flat_map(|v| v.to_le_bytes())
7350 .collect();
7351 ds.write_chunk(0, &chunk).unwrap();
7352 file.close().unwrap();
7353 }
7354 let file = H5File::open(&path).unwrap();
7355 let ds = file.dataset("data").unwrap();
7356 assert_eq!(
7357 ds.read_raw::<i32>().unwrap(),
7358 vec![1, 2, 3, 4, -3, -3, -3, -3]
7359 );
7360 std::fs::remove_file(&path).ok();
7361 }
7362
7363 #[test]
7368 fn implicit_index_survives_a_reopen() {
7369 let path = temp_path("implicit_reopen");
7370 {
7371 let file = H5File::create(&path).unwrap();
7372 file.new_dataset::<i32>()
7373 .shape([8])
7374 .chunk(&[4])
7375 .early_allocation()
7376 .create("data")
7377 .unwrap()
7378 .write_raw(&[0i32, 1, 2, 3, 4, 5, 6, 7])
7379 .unwrap();
7380 file.close().unwrap();
7381 }
7382 {
7383 let file = H5File::open_rw(&path).unwrap();
7384 let ds = file.dataset_writer("data").unwrap();
7385 let chunk: Vec<u8> = [10i32, 11, 12, 13]
7386 .iter()
7387 .flat_map(|v| v.to_le_bytes())
7388 .collect();
7389 ds.write_chunk(1, &chunk).unwrap();
7390 file.close().unwrap();
7391 }
7392 let file = H5File::open(&path).unwrap();
7393 assert_eq!(
7394 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
7395 vec![0, 1, 2, 3, 10, 11, 12, 13]
7396 );
7397 std::fs::remove_file(&path).ok();
7398 }
7399
7400 #[test]
7401 fn fill_value_chunked_roundtrip() {
7402 let path = temp_path("fill_value_chunked");
7403 {
7404 let file = H5File::create(&path).unwrap();
7405 let ds = file
7406 .new_dataset::<i32>()
7407 .shape([0])
7408 .chunk(&[4])
7409 .max_shape(&[None])
7410 .fill_value(-7i32)
7411 .create("vals")
7412 .unwrap();
7413 ds.append(&[1i32, 2, 3, 4]).unwrap();
7414 file.close().unwrap();
7415 }
7416 {
7417 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7418 let idx = writer.dataset_index("vals").unwrap();
7419 assert_eq!(
7420 writer.ds(idx).lock().fill_value,
7421 Some((-7i32).to_le_bytes().to_vec())
7422 );
7423 }
7424 std::fs::remove_file(&path).ok();
7425 }
7426
7427 #[test]
7428 fn fill_value_bytes_takes_the_stored_type_in_its_declared_order() {
7429 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
7430 let be12 = DatatypeMessage::FixedPoint {
7431 size: 2,
7432 byte_order: ByteOrder::BigEndian,
7433 signed: false,
7434 bit_offset: 0,
7435 bit_precision: 12,
7436 };
7437 let path = temp_path("fill_value_bytes");
7438 {
7439 let file = H5File::create(&path).unwrap();
7440 file.new_dataset::<u16>()
7441 .shape([0])
7442 .chunk(&[4])
7443 .max_shape(&[None])
7444 .datatype(be12.clone())
7445 .fill_value_bytes(&0x0abcu16.to_ne_bytes())
7446 .create("vals")
7447 .unwrap();
7448 let err = file
7449 .new_dataset::<u16>()
7450 .shape([4])
7451 .datatype(be12)
7452 .fill_value_bytes(&[1, 2, 3])
7453 .create("short");
7454 assert!(matches!(err, Err(crate::Hdf5Error::TypeMismatch(_))));
7455 file.close().unwrap();
7456 }
7457 {
7458 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7459 let idx = writer.dataset_index("vals").unwrap();
7460 assert_eq!(writer.ds(idx).lock().fill_value, Some(vec![0x0a, 0xbc]));
7461 assert!(writer.dataset_index("short").is_none());
7462 }
7463 std::fs::remove_file(&path).ok();
7464 }
7465
7466 #[test]
7467 fn fill_value_read_missing_chunks() {
7468 fn i32_bytes(vals: &[i32]) -> Vec<u8> {
7471 vals.iter().flat_map(|v| v.to_le_bytes()).collect()
7472 }
7473 let path = temp_path("fill_value_read_missing");
7474 {
7475 let file = H5File::create(&path).unwrap();
7476 let ds = file
7477 .new_dataset::<i32>()
7478 .shape([0])
7479 .chunk(&[2])
7480 .max_shape(&[None])
7481 .fill_value(-1i32)
7482 .create("vals")
7483 .unwrap();
7484 ds.write_chunk(0, &i32_bytes(&[10, 20])).unwrap();
7486 ds.write_chunk(2, &i32_bytes(&[50, 60])).unwrap();
7487 ds.extend(&[6]).unwrap();
7488 file.close().unwrap();
7489 }
7490 {
7491 let file = H5File::open(&path).unwrap();
7492 let ds = file.dataset("vals").unwrap();
7493 let all = ds.read_raw::<i32>().unwrap();
7494 assert_eq!(all, vec![10, 20, -1, -1, 50, 60]);
7495 }
7496 std::fs::remove_file(&path).ok();
7497 }
7498
7499 #[test]
7500 fn fill_value_partial_chunk_padded_with_fill() {
7501 let path = temp_path("fill_value_partial_pad");
7505 {
7506 let file = H5File::create(&path).unwrap();
7507 let ds = file
7508 .new_dataset::<i32>()
7509 .shape([0])
7510 .chunk(&[4])
7511 .max_shape(&[None])
7512 .fill_value(-9i32)
7513 .create("vals")
7514 .unwrap();
7515 ds.append(&[1i32, 2, 3]).unwrap();
7517 file.close().unwrap();
7518 }
7519 let bytes = std::fs::read(&path).unwrap();
7520 let needle: Vec<u8> = [1i32, 2, 3].iter().flat_map(|v| v.to_le_bytes()).collect();
7522 let pos = bytes
7523 .windows(needle.len())
7524 .position(|w| w == needle)
7525 .expect("chunk data [1,2,3] not found in file");
7526 let pad = &bytes[pos + needle.len()..pos + needle.len() + 4];
7527 assert_eq!(
7528 pad,
7529 &(-9i32).to_le_bytes(),
7530 "partial chunk tail must be padded with fill value -9, got {:?}",
7531 pad
7532 );
7533 std::fs::remove_file(&path).ok();
7534 }
7535
7536 #[test]
7537 fn vlen_append_after_reopen_preserves_existing() {
7538 let path = temp_path("vlen_append_reopen");
7541 {
7542 let file = H5File::create(&path).unwrap();
7543 file.create_appendable_vlen_dataset("strs", 4, None)
7544 .unwrap();
7545 file.append_vlen_strings("strs", &["a", "b", "c"]).unwrap();
7547 file.close().unwrap();
7548 }
7549 {
7550 let file = H5File::open_rw(&path).unwrap();
7552 file.append_vlen_strings("strs", &["d"]).unwrap();
7553 file.close().unwrap();
7554 }
7555 {
7556 let file = H5File::open(&path).unwrap();
7557 let ds = file.dataset("strs").unwrap();
7558 let got = ds.read_vlen_strings().unwrap();
7559 assert_eq!(
7560 got.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
7561 vec!["a", "b", "c", "d"]
7562 );
7563 }
7564 std::fs::remove_file(&path).ok();
7565 }
7566
7567 #[test]
7568 fn fill_value_size_mismatch_errors() {
7569 let path = temp_path("fill_value_mismatch");
7570 let writer = crate::io::writer::Hdf5Writer::create(&path).unwrap();
7571 let dt = <f64 as crate::types::H5Type>::hdf5_type();
7572 let idx = writer.create_dataset("d", dt, &[4u64]).unwrap();
7573 assert!(writer.set_dataset_fill_value(idx, vec![0u8; 4]).is_err());
7575 writer.set_dataset_fill_value(idx, vec![0u8; 8]).unwrap();
7577 writer.close().unwrap();
7578 std::fs::remove_file(&path).ok();
7579 }
7580
7581 #[test]
7582 fn datatype_exposes_class_sign_and_byteorder() {
7583 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
7587
7588 let path = temp_path("datatype_accessor");
7589 {
7590 let file = H5File::create(&path).unwrap();
7591 file.new_dataset::<u8>().shape([3]).create("u8d").unwrap();
7592 file.new_dataset::<i8>().shape([3]).create("i8d").unwrap();
7593 file.new_dataset::<i32>().shape([3]).create("i32d").unwrap();
7594 file.new_dataset::<f32>().shape([3]).create("f32d").unwrap();
7595 file.close().unwrap();
7596 }
7597
7598 let file = H5File::open(&path).unwrap();
7599
7600 match file.dataset("u8d").unwrap().datatype().unwrap() {
7601 DatatypeMessage::FixedPoint {
7602 size,
7603 signed,
7604 byte_order,
7605 ..
7606 } => {
7607 assert_eq!(size, 1);
7608 assert!(!signed, "u8 must be unsigned");
7609 assert_eq!(byte_order, ByteOrder::LittleEndian);
7610 }
7611 other => panic!("expected FixedPoint for u8, got {other:?}"),
7612 }
7613
7614 match file.dataset("i8d").unwrap().datatype().unwrap() {
7615 DatatypeMessage::FixedPoint { size, signed, .. } => {
7616 assert_eq!(size, 1);
7617 assert!(signed, "i8 must be signed");
7618 }
7619 other => panic!("expected FixedPoint for i8, got {other:?}"),
7620 }
7621
7622 match file.dataset("i32d").unwrap().datatype().unwrap() {
7623 DatatypeMessage::FixedPoint { size, signed, .. } => {
7624 assert_eq!(size, 4);
7625 assert!(signed, "i32 must be signed");
7626 }
7627 other => panic!("expected FixedPoint for i32, got {other:?}"),
7628 }
7629
7630 match file.dataset("f32d").unwrap().datatype().unwrap() {
7631 DatatypeMessage::FloatingPoint { size, .. } => assert_eq!(size, 4),
7632 other => panic!("expected FloatingPoint for f32, got {other:?}"),
7633 }
7634
7635 std::fs::remove_file(&path).ok();
7636 }
7637
7638 #[test]
7639 fn datatype_in_write_mode_errors() {
7640 let path = temp_path("datatype_write_mode");
7641 let file = H5File::create(&path).unwrap();
7642 let ds = file.new_dataset::<f32>().shape([4]).create("d").unwrap();
7643 assert!(ds.datatype().is_err());
7644 std::fs::remove_file(&path).ok();
7645 }
7646
7647 #[cfg(feature = "deflate")]
7653 #[test]
7654 fn write_chunk_raw_ea_roundtrip_mask0() {
7655 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7656 let path = temp_path("wcr_ea_mask0");
7657 let original: Vec<i32> = (0..12).collect();
7658 {
7659 let file = H5File::create(&path).unwrap();
7660 let ds = file
7661 .new_dataset::<i32>()
7662 .shape([0])
7663 .chunk(&[4])
7664 .max_shape(&[None])
7665 .deflate(4)
7666 .create("v")
7667 .unwrap();
7668 assert!(ds.is_chunked());
7669 let pipeline = FilterPipeline::deflate(4);
7670 for c in 0..3usize {
7671 let raw: Vec<u8> = original[c * 4..c * 4 + 4]
7672 .iter()
7673 .flat_map(|v| v.to_le_bytes())
7674 .collect();
7675 let compressed = apply_filters(&pipeline, &raw).unwrap();
7676 ds.write_chunk_raw(c, &compressed, 0).unwrap();
7677 }
7678 ds.set_extent(&[12]).unwrap();
7679 file.close().unwrap();
7680 }
7681 {
7682 let file = H5File::open(&path).unwrap();
7683 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7684 assert_eq!(v, original);
7685 }
7686 std::fs::remove_file(&path).ok();
7687 }
7688
7689 #[cfg(feature = "deflate")]
7692 #[test]
7693 fn write_chunk_raw_fixed_array_roundtrip_mask0() {
7694 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7695 let path = temp_path("wcr_fa_mask0");
7696 let original: Vec<i32> = (0..12).collect();
7697 {
7698 let file = H5File::create(&path).unwrap();
7699 let ds = file
7700 .new_dataset::<i32>()
7701 .shape([12])
7702 .chunk(&[4])
7703 .deflate(4)
7704 .create("v")
7705 .unwrap();
7706 assert!(ds.is_chunked());
7707 let pipeline = FilterPipeline::deflate(4);
7708 for c in 0..3usize {
7709 let raw: Vec<u8> = original[c * 4..c * 4 + 4]
7710 .iter()
7711 .flat_map(|v| v.to_le_bytes())
7712 .collect();
7713 let compressed = apply_filters(&pipeline, &raw).unwrap();
7714 ds.write_chunk_raw(c, &compressed, 0).unwrap();
7715 }
7716 file.close().unwrap();
7717 }
7718 {
7719 let file = H5File::open(&path).unwrap();
7720 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7721 assert_eq!(v, original);
7722 }
7723 std::fs::remove_file(&path).ok();
7724 }
7725
7726 #[cfg(feature = "deflate")]
7732 #[test]
7733 fn write_chunk_raw_records_filter_mask() {
7734 let path = temp_path("wcr_records_mask");
7735 let raw: Vec<u8> = [10i32, 20, 30, 40]
7736 .iter()
7737 .flat_map(|v| v.to_le_bytes())
7738 .collect();
7739 assert_eq!(raw.len(), 16);
7740 {
7741 let file = H5File::create(&path).unwrap();
7742 let ds = file
7743 .new_dataset::<i32>()
7744 .shape([0])
7745 .chunk(&[4])
7746 .max_shape(&[None])
7747 .deflate(4)
7748 .create("v")
7749 .unwrap();
7750 ds.write_chunk_raw(0, &raw, 1).unwrap();
7753 ds.set_extent(&[4]).unwrap();
7754 file.close().unwrap();
7755 }
7756 {
7759 let w = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7760 let idx = w.dataset_index("v").unwrap();
7761 let ds = w.ds(idx);
7762 let m = ds.lock();
7763 let entry = &m
7764 .chunked
7765 .as_ref()
7766 .unwrap()
7767 .filt_iblk
7768 .as_ref()
7769 .unwrap()
7770 .elements[0];
7771 assert_eq!(entry.filter_mask, 1, "filter_mask must round-trip to disk");
7772 assert_eq!(entry.nbytes, 16, "uncompressed chunk stored verbatim");
7773 }
7774 std::fs::remove_file(&path).ok();
7775 }
7776
7777 #[cfg(feature = "deflate")]
7788 #[test]
7789 fn a_chunk_that_decodes_short_of_its_image_reads_as_fill() {
7790 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7791 let path = temp_path("short_chunk_image_is_fill");
7792 let pipeline = FilterPipeline::deflate(4);
7793 {
7794 let file = H5File::create(&path).unwrap();
7795 let ds = file
7796 .new_dataset::<i32>()
7797 .shape([0])
7798 .chunk(&[4])
7799 .max_shape(&[None])
7800 .fill_value(-1i32)
7801 .deflate(4)
7802 .create("v")
7803 .unwrap();
7804 let short: Vec<u8> = [7i32, 8].iter().flat_map(|v| v.to_le_bytes()).collect();
7806 ds.write_chunk_raw(0, &apply_filters(&pipeline, &short).unwrap(), 0)
7807 .unwrap();
7808 let whole: Vec<u8> = [9i32, 10, 11, 12]
7810 .iter()
7811 .flat_map(|v| v.to_le_bytes())
7812 .collect();
7813 ds.write_chunk_raw(2, &apply_filters(&pipeline, &whole).unwrap(), 0)
7814 .unwrap();
7815 ds.set_extent(&[12]).unwrap();
7816 file.close().unwrap();
7817 }
7818 {
7819 let file = H5File::open(&path).unwrap();
7820 let ds = file.dataset("v").unwrap();
7821 assert_eq!(
7822 ds.read_raw::<i32>().unwrap(),
7823 vec![-1, -1, -1, -1, -1, -1, -1, -1, 9, 10, 11, 12]
7824 );
7825 assert_eq!(ds.read_slice::<i32>(&[0], &[4]).unwrap(), vec![-1; 4]);
7828 assert_eq!(
7829 ds.read_slice::<i32>(&[8], &[4]).unwrap(),
7830 vec![9, 10, 11, 12]
7831 );
7832 }
7833 std::fs::remove_file(&path).ok();
7834 }
7835
7836 #[cfg(feature = "deflate")]
7842 #[test]
7843 fn a_staged_chunk_carries_only_what_its_stream_decoded() {
7844 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7845 let path = temp_path("short_chunk_image_staged");
7846 let pipeline = FilterPipeline::deflate(4);
7847 {
7848 let file = H5File::create(&path).unwrap();
7849 let ds = file
7850 .new_dataset::<i32>()
7851 .shape([0])
7852 .chunk(&[4])
7853 .max_shape(&[None])
7854 .fill_value(-1i32)
7855 .deflate(4)
7856 .create("v")
7857 .unwrap();
7858 let short: Vec<u8> = [7i32, 8].iter().flat_map(|v| v.to_le_bytes()).collect();
7859 ds.write_chunk_raw(0, &apply_filters(&pipeline, &short).unwrap(), 0)
7860 .unwrap();
7861 ds.set_extent(&[4]).unwrap();
7862 file.close().unwrap();
7863 }
7864 {
7865 let file = H5File::open(&path).unwrap();
7866 let ds = file.dataset("v").unwrap();
7867 assert_eq!(ds.read_slice::<i32>(&[0], &[2]).unwrap(), vec![7, 8]);
7869 assert_eq!(ds.read_slice::<i32>(&[1], &[2]).unwrap(), vec![-1, -1]);
7871 }
7872 std::fs::remove_file(&path).ok();
7873 }
7874
7875 #[cfg(feature = "deflate")]
7876 #[test]
7877 fn write_chunk_raw_ea_per_chunk_mask_roundtrip() {
7878 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7879 let path = temp_path("wcr_ea_per_chunk_mask");
7880 let original: Vec<i32> = (0..8).collect();
7881 let pipeline = FilterPipeline::deflate(4);
7882 {
7883 let file = H5File::create(&path).unwrap();
7884 let ds = file
7885 .new_dataset::<i32>()
7886 .shape([0])
7887 .chunk(&[4])
7888 .max_shape(&[None])
7889 .deflate(4)
7890 .create("v")
7891 .unwrap();
7892 let raw0: Vec<u8> = original[0..4]
7893 .iter()
7894 .flat_map(|v| v.to_le_bytes())
7895 .collect();
7896 ds.write_chunk_raw(0, &apply_filters(&pipeline, &raw0).unwrap(), 0)
7898 .unwrap();
7899 let raw1: Vec<u8> = original[4..8]
7900 .iter()
7901 .flat_map(|v| v.to_le_bytes())
7902 .collect();
7903 ds.write_chunk_raw(1, &raw1, 1).unwrap();
7905 ds.set_extent(&[8]).unwrap();
7906 file.close().unwrap();
7907 }
7908 {
7909 let file = H5File::open(&path).unwrap();
7910 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7911 assert_eq!(v, original);
7912 }
7913 std::fs::remove_file(&path).ok();
7914 }
7915
7916 #[cfg(feature = "deflate")]
7919 #[test]
7920 fn write_chunk_raw_fixed_array_per_chunk_mask_roundtrip() {
7921 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7922 let path = temp_path("wcr_fa_per_chunk_mask");
7923 let original: Vec<i32> = (0..8).collect();
7924 let pipeline = FilterPipeline::deflate(4);
7925 {
7926 let file = H5File::create(&path).unwrap();
7927 let ds = file
7928 .new_dataset::<i32>()
7929 .shape([8])
7930 .chunk(&[4])
7931 .deflate(4)
7932 .create("v")
7933 .unwrap();
7934 let raw0: Vec<u8> = original[0..4]
7935 .iter()
7936 .flat_map(|v| v.to_le_bytes())
7937 .collect();
7938 ds.write_chunk_raw(0, &apply_filters(&pipeline, &raw0).unwrap(), 0)
7939 .unwrap();
7940 let raw1: Vec<u8> = original[4..8]
7941 .iter()
7942 .flat_map(|v| v.to_le_bytes())
7943 .collect();
7944 ds.write_chunk_raw(1, &raw1, 1).unwrap();
7945 file.close().unwrap();
7946 }
7947 {
7948 let file = H5File::open(&path).unwrap();
7949 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7950 assert_eq!(v, original);
7951 }
7952 std::fs::remove_file(&path).ok();
7953 }
7954
7955 #[test]
7958 fn write_chunk_raw_rejects_unfiltered() {
7959 let path = temp_path("wcr_unfiltered");
7960 let file = H5File::create(&path).unwrap();
7961 let ds = file
7962 .new_dataset::<i32>()
7963 .shape([0])
7964 .chunk(&[4])
7965 .max_shape(&[None])
7966 .create("v")
7967 .unwrap();
7968 let err = ds.write_chunk_raw(0, &[0u8; 16], 0).unwrap_err();
7969 assert!(
7970 err.to_string().contains("filtered dataset"),
7971 "expected a filtered-dataset error, got: {err}"
7972 );
7973 std::fs::remove_file(&path).ok();
7974 }
7975
7976 #[test]
7980 fn write_chunk_raw_sends_btree_v2_to_the_coordinate_form() {
7981 let path = temp_path("wcr_btree2");
7982 let file = H5File::create(&path).unwrap();
7983 let ds = file
7984 .new_dataset::<i32>()
7985 .shape([0, 0])
7986 .chunk(&[2, 2])
7987 .max_shape(&[None, None])
7988 .create("grid")
7989 .unwrap();
7990 let err = ds.write_chunk_raw(0, &[0u8; 16], 0).unwrap_err();
7991 assert!(
7992 err.to_string().contains("write_chunk_raw_at"),
7993 "expected a pointer to the coordinate form, got: {err}"
7994 );
7995 std::fs::remove_file(&path).ok();
7996 }
7997
7998 #[cfg(feature = "deflate")]
8003 #[test]
8004 fn write_chunk_raw_at_round_trips_on_btree_v2() {
8005 use crate::format::messages::filter::{apply_filters, FilterPipeline};
8006
8007 let path = temp_path("wcr_at_btree2");
8008 let raw0: Vec<u8> = (0..4i32).flat_map(|v| v.to_le_bytes()).collect();
8009 let raw1: Vec<u8> = (100..104i32).flat_map(|v| v.to_le_bytes()).collect();
8010 {
8011 let file = H5File::create(&path).unwrap();
8012 let ds = file
8013 .new_dataset::<i32>()
8014 .shape([0, 0])
8015 .chunk(&[2, 2])
8016 .max_shape(&[None, None])
8017 .deflate(6)
8018 .create("grid")
8019 .unwrap();
8020 let pipeline = FilterPipeline::deflate(6);
8021 ds.write_chunk_raw_at(&[0, 0], &apply_filters(&pipeline, &raw0).unwrap(), 0)
8023 .unwrap();
8024 ds.write_chunk_raw_at(&[1, 1], &raw1, 1).unwrap();
8026 file.close().unwrap();
8027 }
8028 let file = H5File::open(&path).unwrap();
8029 let ds = file.dataset("grid").unwrap();
8030 assert_eq!(ds.shape(), vec![4, 4]);
8031 let all = ds.read_raw::<i32>().unwrap();
8032 assert_eq!([all[0], all[1], all[4], all[5]], [0, 1, 2, 3]);
8034 assert_eq!([all[10], all[11], all[14], all[15]], [100, 101, 102, 103]);
8036 drop(file);
8037 std::fs::remove_file(&path).ok();
8038 }
8039
8040 #[cfg(feature = "deflate")]
8043 #[test]
8044 fn write_chunk_raw_at_round_trips_on_the_array_indexes() {
8045 for (label, max_shape) in [
8046 ("wcr_at_ea", Some(vec![None, Some(4usize)])),
8047 ("wcr_at_fa", None),
8048 ] {
8049 let path = temp_path(label);
8050 let raw: Vec<u8> = (0..8i32).flat_map(|v| v.to_le_bytes()).collect();
8051 {
8052 let file = H5File::create(&path).unwrap();
8053 let mut b = file
8054 .new_dataset::<i32>()
8055 .shape([4usize, 4])
8056 .chunk(&[2, 4])
8057 .deflate(6);
8058 if let Some(ref ms) = max_shape {
8059 b = b.max_shape(ms);
8060 }
8061 let ds = b.create("grid").unwrap();
8062 ds.write_chunk_raw_at(&[1, 0], &raw, 1).unwrap();
8064 file.close().unwrap();
8065 }
8066 let file = H5File::open(&path).unwrap();
8067 let ds = file.dataset("grid").unwrap();
8068 let all = ds.read_raw::<i32>().unwrap();
8069 assert_eq!(&all[8..16], &(0..8).collect::<Vec<i32>>()[..], "{label}");
8070 drop(file);
8071 std::fs::remove_file(&path).ok();
8072 }
8073 }
8074
8075 #[cfg(feature = "deflate")]
8079 #[test]
8080 fn write_chunk_raw_at_rejects_an_oversized_btree_v2_chunk() {
8081 let path = temp_path("wcr_at_oversized");
8082 let file = H5File::create(&path).unwrap();
8083 let ds = file
8084 .new_dataset::<i32>()
8085 .shape([0, 0])
8086 .chunk(&[1, 1])
8087 .max_shape(&[None, None])
8088 .deflate(4)
8089 .create("grid")
8090 .unwrap();
8091 let err = ds
8092 .write_chunk_raw_at(&[0, 0], &vec![0u8; 70000], 0)
8093 .unwrap_err();
8094 assert!(
8095 err.to_string().contains("does not fit"),
8096 "expected a chunk-size-field overflow error, got: {err}"
8097 );
8098 std::fs::remove_file(&path).ok();
8099 }
8100
8101 #[test]
8104 fn write_chunk_raw_at_rejects_an_unfiltered_btree_v2() {
8105 let path = temp_path("wcr_at_unfiltered");
8106 let file = H5File::create(&path).unwrap();
8107 let ds = file
8108 .new_dataset::<i32>()
8109 .shape([0, 0])
8110 .chunk(&[2, 2])
8111 .max_shape(&[None, None])
8112 .create("grid")
8113 .unwrap();
8114 let err = ds.write_chunk_raw_at(&[0, 0], &[0u8; 16], 0).unwrap_err();
8115 assert!(
8116 err.to_string().contains("filtered dataset"),
8117 "expected a filtered-dataset error, got: {err}"
8118 );
8119 std::fs::remove_file(&path).ok();
8120 }
8121
8122 #[cfg(feature = "deflate")]
8127 #[test]
8128 fn write_chunk_raw_rejects_oversized_chunk() {
8129 let path = temp_path("wcr_oversized");
8130 let file = H5File::create(&path).unwrap();
8131 let ds = file
8132 .new_dataset::<i32>()
8133 .shape([0])
8134 .chunk(&[1])
8135 .max_shape(&[None])
8136 .deflate(4)
8137 .create("v")
8138 .unwrap();
8139 let err = ds.write_chunk_raw(0, &vec![0u8; 70000], 0).unwrap_err();
8140 assert!(
8141 err.to_string().contains("does not fit"),
8142 "expected a chunk-size-field overflow error, got: {err}"
8143 );
8144 std::fs::remove_file(&path).ok();
8145 }
8146
8147 use crate::format::messages::datatype::{CompoundMember, DatatypeMessage};
8150
8151 fn write_fixed_string_dataset(
8154 path: &std::path::Path,
8155 dt: DatatypeMessage,
8156 width: usize,
8157 elems: &[&[u8]],
8158 compressed: bool,
8159 ) {
8160 let mut raw = Vec::with_capacity(elems.len() * width);
8161 for e in elems {
8162 assert!(e.len() <= width);
8163 raw.extend_from_slice(e);
8164 raw.resize(raw.len() + (width - e.len()), 0);
8165 }
8166 let file = H5File::create(path).unwrap();
8167 let mut b = file.new_dataset::<u8>().datatype(dt).shape([elems.len()]);
8168 if compressed {
8169 b = b.chunk(&[2]).deflate(6);
8170 }
8171 let ds = b.create("labels").unwrap();
8172 ds.write_raw_bytes(&raw).unwrap();
8173 file.close().unwrap();
8174 }
8175
8176 #[test]
8179 fn read_strings_handles_any_fixed_width() {
8180 for width in [4usize, 24, 100] {
8181 let path = temp_path(&format!("fixed_str_{width}"));
8182 write_fixed_string_dataset(
8183 &path,
8184 DatatypeMessage::fixed_string(width as u32),
8185 width,
8186 &[b"ab", b"cde", b""],
8187 false,
8188 );
8189 let file = H5File::open(&path).unwrap();
8190 let got = file.dataset("labels").unwrap().read_strings().unwrap();
8191 assert_eq!(got, vec!["ab", "cde", ""], "width {width}");
8192 std::fs::remove_file(&path).ok();
8193 }
8194 }
8195
8196 #[test]
8206 fn read_strings_honors_every_padding_rule() {
8207 let elem: &[u8] = b"ab\0X\0\0";
8209 for (padding, want) in [(0u8, "ab"), (1, "ab")] {
8210 let path = temp_path(&format!("fixed_pad_{padding}"));
8211 write_fixed_string_dataset(
8212 &path,
8213 DatatypeMessage::FixedString {
8214 size: 6,
8215 padding,
8216 charset: 0,
8217 },
8218 6,
8219 &[elem],
8220 false,
8221 );
8222 let file = H5File::open(&path).unwrap();
8223 let got = file.dataset("labels").unwrap().read_strings().unwrap();
8224 assert_eq!(got, vec![want.to_string()], "padding {padding}");
8225 std::fs::remove_file(&path).ok();
8226 }
8227 let path = temp_path("fixed_pad_2");
8229 write_fixed_string_dataset(
8230 &path,
8231 DatatypeMessage::FixedString {
8232 size: 8,
8233 padding: 2,
8234 charset: 0,
8235 },
8236 8,
8237 &[b"a b "],
8238 false,
8239 );
8240 let file = H5File::open(&path).unwrap();
8241 assert_eq!(
8242 file.dataset("labels").unwrap().read_strings().unwrap(),
8243 vec!["a b".to_string()]
8244 );
8245 std::fs::remove_file(&path).ok();
8246
8247 let path = temp_path("fixed_pad_2_nul");
8249 write_fixed_string_dataset(
8250 &path,
8251 DatatypeMessage::FixedString {
8252 size: 8,
8253 padding: 2,
8254 charset: 0,
8255 },
8256 8,
8257 &[b"a\0b "],
8258 false,
8259 );
8260 let file = H5File::open(&path).unwrap();
8261 assert_eq!(
8262 file.dataset("labels").unwrap().read_strings().unwrap(),
8263 vec!["a\0b".to_string()]
8264 );
8265 std::fs::remove_file(&path).ok();
8266 }
8267
8268 #[test]
8271 fn read_strings_rejects_reserved_datatype_codes() {
8272 for (padding, charset, want) in [(3u8, 0u8, "padding rule 3"), (0, 7, "character set 7")] {
8273 let path = temp_path(&format!("fixed_reserved_{padding}_{charset}"));
8274 write_fixed_string_dataset(
8275 &path,
8276 DatatypeMessage::FixedString {
8277 size: 4,
8278 padding,
8279 charset,
8280 },
8281 4,
8282 &[b"ab"],
8283 false,
8284 );
8285 let file = H5File::open(&path).unwrap();
8286 let err = file
8287 .dataset("labels")
8288 .unwrap()
8289 .read_strings()
8290 .unwrap_err()
8291 .to_string();
8292 assert!(err.contains(want), "got: {err}");
8293 std::fs::remove_file(&path).ok();
8294 }
8295 }
8296
8297 #[test]
8301 fn to_host_byte_order_converts_scalars_and_refuses_composites() {
8302 use crate::dataset::{to_host_byte_order, HOST_BYTE_ORDER};
8303 use crate::format::messages::datatype::ByteOrder;
8304
8305 let foreign = match HOST_BYTE_ORDER {
8306 ByteOrder::LittleEndian => ByteOrder::BigEndian,
8307 ByteOrder::BigEndian => ByteOrder::LittleEndian,
8308 };
8309 let int = |order, size| DatatypeMessage::FixedPoint {
8310 size,
8311 byte_order: order,
8312 signed: false,
8313 bit_offset: 0,
8314 bit_precision: (size * 8) as u16,
8315 };
8316
8317 let mut buf = [1u8, 2, 3, 4, 5, 6, 7, 8];
8319 to_host_byte_order(&mut buf, &int(foreign, 4), 4).unwrap();
8320 assert_eq!(buf, [4, 3, 2, 1, 8, 7, 6, 5]);
8321
8322 let mut buf = [1u8, 2, 3, 4];
8324 to_host_byte_order(&mut buf, &int(HOST_BYTE_ORDER, 4), 4).unwrap();
8325 assert_eq!(buf, [1, 2, 3, 4]);
8326
8327 let mut buf = [1u8, 2, 3, 4];
8329 to_host_byte_order(&mut buf, &int(foreign, 1), 1).unwrap();
8330 assert_eq!(buf, [1, 2, 3, 4]);
8331
8332 let mut buf = [1u8, 2];
8334 let enumeration = DatatypeMessage::Enum {
8335 base: Box::new(int(foreign, 2)),
8336 members: Vec::new(),
8337 };
8338 to_host_byte_order(&mut buf, &enumeration, 2).unwrap();
8339 assert_eq!(buf, [2, 1]);
8340
8341 let mut buf = *b"abcd";
8343 to_host_byte_order(&mut buf, &DatatypeMessage::fixed_string(4), 4).unwrap();
8344 assert_eq!(&buf, b"abcd");
8345
8346 let compound = |order| DatatypeMessage::Compound {
8348 size: 4,
8349 members: vec![CompoundMember {
8350 name: "x".into(),
8351 offset: 0,
8352 datatype: int(order, 4),
8353 }],
8354 };
8355 let mut buf = [1u8, 2, 3, 4];
8356 to_host_byte_order(&mut buf, &compound(HOST_BYTE_ORDER), 4).unwrap();
8357 assert_eq!(buf, [1, 2, 3, 4]);
8358
8359 let mut buf = [1u8, 2, 3, 4];
8361 let err = to_host_byte_order(&mut buf, &compound(foreign), 4)
8362 .expect_err("a foreign-order compound was reinterpreted")
8363 .to_string();
8364 assert!(err.contains("read_raw_bytes"), "got: {err}");
8365 assert_eq!(buf, [1, 2, 3, 4], "the refused image is left alone");
8366 }
8367
8368 #[test]
8372 fn to_stored_byte_order_converts_scalars_and_refuses_composites() {
8373 use crate::dataset::{to_stored_byte_order, FOREIGN_BYTE_ORDER, HOST_BYTE_ORDER};
8374 use std::borrow::Cow;
8375
8376 let int = |order, size| DatatypeMessage::FixedPoint {
8377 size,
8378 byte_order: order,
8379 signed: false,
8380 bit_offset: 0,
8381 bit_precision: (size * 8) as u16,
8382 };
8383
8384 let host = [1u8, 2, 3, 4, 5, 6, 7, 8];
8386 let stored = to_stored_byte_order(&host, &int(FOREIGN_BYTE_ORDER, 4), 4).unwrap();
8387 assert_eq!(&*stored, &[4, 3, 2, 1, 8, 7, 6, 5]);
8388 assert!(matches!(stored, Cow::Owned(_)), "a swap needs its own copy");
8389
8390 let stored = to_stored_byte_order(&host, &int(HOST_BYTE_ORDER, 4), 4).unwrap();
8392 assert!(matches!(stored, Cow::Borrowed(_)), "no copy without a swap");
8393 assert_eq!(&*stored, &host);
8394
8395 let stored = to_stored_byte_order(&host, &int(FOREIGN_BYTE_ORDER, 1), 1).unwrap();
8397 assert_eq!(&*stored, &host);
8398
8399 let enumeration = DatatypeMessage::Enum {
8401 base: Box::new(int(FOREIGN_BYTE_ORDER, 2)),
8402 members: Vec::new(),
8403 };
8404 let stored = to_stored_byte_order(&[1u8, 2], &enumeration, 2).unwrap();
8405 assert_eq!(&*stored, &[2, 1]);
8406
8407 let compound = |order| DatatypeMessage::Compound {
8410 size: 4,
8411 members: vec![CompoundMember {
8412 name: "x".into(),
8413 offset: 0,
8414 datatype: int(order, 4),
8415 }],
8416 };
8417 let stored = to_stored_byte_order(&[1u8, 2, 3, 4], &compound(HOST_BYTE_ORDER), 4).unwrap();
8418 assert_eq!(&*stored, &[1, 2, 3, 4]);
8419 let err = to_stored_byte_order(&[1u8, 2, 3, 4], &compound(FOREIGN_BYTE_ORDER), 4)
8420 .expect_err("a foreign-order compound was written from host bytes")
8421 .to_string();
8422 assert!(err.contains("write_raw_bytes"), "got: {err}");
8423 }
8424
8425 #[test]
8429 fn read_strings_enforces_the_character_set_and_lossy_does_not() {
8430 for (charset, bytes, want) in [
8433 (0u8, b"caf\xe9".as_slice(), "ASCII character set"),
8434 (1, b"a\xff".as_slice(), "not valid UTF-8"),
8435 ] {
8436 let path = temp_path(&format!("fixed_charset_{charset}"));
8437 write_fixed_string_dataset(
8438 &path,
8439 DatatypeMessage::FixedString {
8440 size: 6,
8441 padding: 1,
8442 charset,
8443 },
8444 6,
8445 &[b"ok", bytes],
8446 false,
8447 );
8448 let file = H5File::open(&path).unwrap();
8449 let ds = file.dataset("labels").unwrap();
8450 let err = ds.read_strings().unwrap_err().to_string();
8451 assert!(err.contains(want) && err.contains("string 1"), "got: {err}");
8452 let lossy = ds.read_strings_lossy().unwrap();
8453 assert_eq!(lossy[0], "ok");
8454 assert_eq!(
8455 lossy[1].chars().next().unwrap(),
8456 if charset == 0 { 'c' } else { 'a' }
8457 );
8458 std::fs::remove_file(&path).ok();
8459 }
8460 }
8461
8462 #[test]
8465 fn read_strings_reads_utf8_fixed_strings() {
8466 let path = temp_path("fixed_utf8");
8467 write_fixed_string_dataset(
8468 &path,
8469 DatatypeMessage::fixed_string_utf8(12),
8470 12,
8471 &["héllo".as_bytes(), "안녕".as_bytes()],
8472 false,
8473 );
8474 let file = H5File::open(&path).unwrap();
8475 assert_eq!(
8476 file.dataset("labels").unwrap().read_strings().unwrap(),
8477 vec!["héllo".to_string(), "안녕".to_string()]
8478 );
8479 std::fs::remove_file(&path).ok();
8480 }
8481
8482 #[cfg(feature = "deflate")]
8485 #[test]
8486 fn read_strings_reads_a_compressed_fixed_string_dataset() {
8487 let path = temp_path("fixed_str_deflate");
8488 write_fixed_string_dataset(
8489 &path,
8490 DatatypeMessage::fixed_string(16),
8491 16,
8492 &[b"alpha", b"beta", b"gamma", b"delta", b"epsilon"],
8493 true,
8494 );
8495 let file = H5File::open(&path).unwrap();
8496 assert_eq!(
8497 file.dataset("labels").unwrap().read_strings().unwrap(),
8498 vec!["alpha", "beta", "gamma", "delta", "epsilon"]
8499 );
8500 std::fs::remove_file(&path).ok();
8501 }
8502
8503 #[test]
8506 fn read_strings_also_reads_variable_length_strings() {
8507 let path = temp_path("read_strings_vlen");
8508 {
8509 let file = H5File::create(&path).unwrap();
8510 file.write_vlen_strings("names", &["alpha", "", "안녕"])
8511 .unwrap();
8512 file.close().unwrap();
8513 }
8514 let file = H5File::open(&path).unwrap();
8515 assert_eq!(
8516 file.dataset("names").unwrap().read_strings().unwrap(),
8517 vec!["alpha".to_string(), String::new(), "안녕".to_string()]
8518 );
8519 std::fs::remove_file(&path).ok();
8520 }
8521
8522 #[test]
8527 fn read_strings_rejects_a_zero_width_fixed_string_dataset() {
8528 use crate::format::checksum::checksum_metadata;
8529 use crate::format::object_header::OHDR_SIGNATURE;
8530
8531 let path = temp_path("fixed_str_zero_width");
8532 write_fixed_string_dataset(
8533 &path,
8534 DatatypeMessage::fixed_string(37),
8535 37,
8536 &[b"ab", b"cd"],
8537 false,
8538 );
8539
8540 let mut bytes = std::fs::read(&path).unwrap();
8544 let needle = [0x13u8, 0, 0, 0, 37, 0, 0, 0];
8545 let at = bytes
8546 .windows(needle.len())
8547 .position(|w| w == needle)
8548 .expect("encoded fixed-string datatype message");
8549 assert!(
8550 !bytes[at + 1..].windows(needle.len()).any(|w| w == needle),
8551 "the datatype message pattern is not unique in the file"
8552 );
8553
8554 let ohdr = bytes[..at]
8558 .windows(4)
8559 .rposition(|w| w == OHDR_SIGNATURE)
8560 .expect("enclosing object header");
8561 let cksum_at = (at + needle.len()..bytes.len() - 4)
8562 .find(|&e| {
8563 u32::from_le_bytes(bytes[e..e + 4].try_into().unwrap())
8564 == checksum_metadata(&bytes[ohdr..e])
8565 })
8566 .expect("object header checksum");
8567
8568 bytes[at + 4..at + 8].copy_from_slice(&0u32.to_le_bytes());
8569 let fixed = checksum_metadata(&bytes[ohdr..cksum_at]);
8570 bytes[cksum_at..cksum_at + 4].copy_from_slice(&fixed.to_le_bytes());
8571 std::fs::write(&path, &bytes).unwrap();
8572
8573 let file = H5File::open(&path).unwrap();
8574 let err = file
8575 .dataset("labels")
8576 .unwrap()
8577 .read_strings()
8578 .unwrap_err()
8579 .to_string();
8580 assert!(err.contains("zero width"), "got: {err}");
8581 std::fs::remove_file(&path).ok();
8582 }
8583
8584 #[test]
8586 fn read_strings_rejects_a_non_string_dataset() {
8587 let path = temp_path("read_strings_numeric");
8588 {
8589 let file = H5File::create(&path).unwrap();
8590 let ds = file.new_dataset::<i32>().shape([3]).create("nums").unwrap();
8591 ds.write_raw(&[1i32, 2, 3]).unwrap();
8592 file.close().unwrap();
8593 }
8594 let file = H5File::open(&path).unwrap();
8595 let err = file
8596 .dataset("nums")
8597 .unwrap()
8598 .read_strings()
8599 .unwrap_err()
8600 .to_string();
8601 assert!(err.contains("only for string datasets"), "got: {err}");
8602 std::fs::remove_file(&path).ok();
8603 }
8604
8605 #[test]
8610 fn write_vlen_strings_slice_replaces_one_element() {
8611 let path = temp_path("vlen_slice_contig");
8612 {
8613 let file = H5File::create(&path).unwrap();
8614 file.write_vlen_strings("notes", &["a", "b", "c", "d"])
8615 .unwrap();
8616 file.close().unwrap();
8617 }
8618 {
8619 let file = H5File::open_rw(&path).unwrap();
8620 file.dataset_writer("notes")
8621 .unwrap()
8622 .write_vlen_strings_slice(1, &["replacement"])
8623 .unwrap();
8624 file.close().unwrap();
8625 }
8626 let file = H5File::open(&path).unwrap();
8627 let ds = file.dataset("notes").unwrap();
8628 assert_eq!(ds.shape(), vec![4]);
8629 assert_eq!(
8630 ds.read_vlen_strings().unwrap(),
8631 vec!["a", "replacement", "c", "d"]
8632 );
8633 std::fs::remove_file(&path).ok();
8634 }
8635
8636 #[test]
8639 fn write_vlen_strings_slice_spans_chunks_after_reopen() {
8640 let path = temp_path("vlen_slice_chunked");
8641 {
8642 let file = H5File::create(&path).unwrap();
8643 file.create_appendable_vlen_dataset("notes", 2, None)
8644 .unwrap();
8645 let all: Vec<String> = (0..6).map(|i| format!("v{i}")).collect();
8646 let refs: Vec<&str> = all.iter().map(|s| s.as_str()).collect();
8647 file.append_vlen_strings("notes", &refs).unwrap();
8648 file.close().unwrap();
8649 }
8650 {
8651 let file = H5File::open_rw(&path).unwrap();
8653 file.dataset_writer("notes")
8654 .unwrap()
8655 .write_vlen_strings_slice(1, &["x", "y", "z"])
8656 .unwrap();
8657 file.close().unwrap();
8658 }
8659 let file = H5File::open(&path).unwrap();
8660 let ds = file.dataset("notes").unwrap();
8661 assert_eq!(ds.shape(), vec![6]);
8662 assert_eq!(
8663 ds.read_vlen_strings().unwrap(),
8664 vec!["v0", "x", "y", "z", "v4", "v5"]
8665 );
8666 std::fs::remove_file(&path).ok();
8667 }
8668
8669 #[test]
8673 fn write_vlen_strings_slice_updates_buffered_elements() {
8674 let path = temp_path("vlen_slice_buffered");
8675 {
8676 let file = H5File::create(&path).unwrap();
8677 file.create_appendable_vlen_dataset("notes", 4, None)
8678 .unwrap();
8679 file.append_vlen_strings("notes", &["a", "b", "c"]).unwrap();
8681 file.dataset_writer("notes")
8682 .unwrap()
8683 .write_vlen_strings_slice(1, &["patched"])
8684 .unwrap();
8685 file.close().unwrap();
8686 }
8687 let file = H5File::open(&path).unwrap();
8688 assert_eq!(
8689 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8690 vec!["a", "patched", "c"]
8691 );
8692 std::fs::remove_file(&path).ok();
8693 }
8694
8695 #[test]
8699 fn write_vlen_strings_slice_checks_its_range() {
8700 let build = |name: &str, empty_call: bool| {
8701 let path = temp_path(name);
8702 let file = H5File::create(&path).unwrap();
8703 file.write_vlen_strings("notes", &["a", "b"]).unwrap();
8704 let ds = file.dataset_writer("notes").unwrap();
8705 let err = ds
8706 .write_vlen_strings_slice(1, &["x", "y"])
8707 .unwrap_err()
8708 .to_string();
8709 assert!(
8710 err.contains("outside the dataset's 2 elements"),
8711 "got: {err}"
8712 );
8713 if empty_call {
8714 ds.write_vlen_strings_slice(0, &[]).unwrap();
8715 }
8716 file.close().unwrap();
8717 path
8718 };
8719
8720 let with_empty = build("vlen_slice_range", true);
8721 let control = build("vlen_slice_range_control", false);
8722 assert_eq!(
8723 std::fs::metadata(&with_empty).unwrap().len(),
8724 std::fs::metadata(&control).unwrap().len(),
8725 "the rejected and empty calls must leave the file untouched"
8726 );
8727
8728 let file = H5File::open(&with_empty).unwrap();
8729 assert_eq!(
8730 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8731 vec!["a", "b"]
8732 );
8733 std::fs::remove_file(&with_empty).ok();
8734 std::fs::remove_file(&control).ok();
8735 }
8736
8737 #[test]
8740 fn write_vlen_strings_slice_rejects_a_multidimensional_dataset() {
8741 let path = temp_path("vlen_slice_2d");
8742 let file = H5File::create(&path).unwrap();
8743 let ds = file
8744 .new_dataset::<u8>()
8745 .datatype(DatatypeMessage::vlen_string_utf8())
8746 .shape([2, 3])
8747 .create("grid")
8748 .unwrap();
8749 let err = ds
8750 .write_vlen_strings_slice(0, &["x"])
8751 .unwrap_err()
8752 .to_string();
8753 assert!(err.contains("1-dimension datasets"), "got: {err}");
8754 file.close().unwrap();
8755 std::fs::remove_file(&path).ok();
8756 }
8757
8758 #[test]
8761 fn write_vlen_strings_slice_enforces_the_ascii_character_set() {
8762 let path = temp_path("vlen_slice_ascii");
8763 let file = H5File::create(&path).unwrap();
8764 let ds = file
8765 .new_dataset::<u8>()
8766 .datatype(DatatypeMessage::vlen_string_ascii())
8767 .shape([3])
8768 .create("notes")
8769 .unwrap();
8770 let err = ds
8771 .write_vlen_strings_slice(0, &["ok", "안녕"])
8772 .unwrap_err()
8773 .to_string();
8774 assert!(
8775 err.contains("string 1") && err.contains("is not ASCII"),
8776 "got: {err}"
8777 );
8778 ds.write_vlen_strings_slice(0, &["ok", "fine"]).unwrap();
8779 file.close().unwrap();
8780 std::fs::remove_file(&path).ok();
8781 }
8782
8783 #[test]
8786 fn write_vlen_strings_slice_rejects_a_non_vlen_dataset() {
8787 let path = temp_path("vlen_slice_numeric");
8788 let file = H5File::create(&path).unwrap();
8789 let ds = file.new_dataset::<i32>().shape([3]).create("nums").unwrap();
8790 ds.write_raw(&[1i32, 2, 3]).unwrap();
8791 let err = ds
8792 .write_vlen_strings_slice(0, &["x"])
8793 .unwrap_err()
8794 .to_string();
8795 assert!(
8796 err.contains("only for variable-length string datasets"),
8797 "got: {err}"
8798 );
8799 file.close().unwrap();
8800 std::fs::remove_file(&path).ok();
8801 }
8802
8803 #[test]
8810 fn write_vlen_strings_slice_reuses_the_freed_heap_block() {
8811 let size_after = |updates: usize| {
8812 let path = temp_path(&format!("vlen_slice_heap_reuse_{updates}"));
8813 let file = H5File::create(&path).unwrap();
8814 file.write_vlen_strings("notes", &["a", "b"]).unwrap();
8815 let ds = file.dataset_writer("notes").unwrap();
8816 for i in 0..updates {
8817 ds.write_vlen_strings_slice(0, &[&format!("update {i}")])
8818 .unwrap();
8819 }
8820 file.close().unwrap();
8821 let n = std::fs::metadata(&path).unwrap().len();
8822 let read = H5File::open(&path).unwrap();
8823 assert_eq!(
8824 read.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8825 vec![format!("update {}", updates - 1), "b".to_string()]
8826 );
8827 drop(read);
8828 std::fs::remove_file(&path).ok();
8829 n
8830 };
8831
8832 let settled = size_after(3);
8835 assert_eq!(size_after(20), settled, "20 updates against 3");
8836 assert_eq!(size_after(50), settled, "50 updates against 3");
8837 }
8838
8839 #[test]
8843 fn write_vlen_strings_slice_frees_an_empty_strings_object() {
8844 let size_after = |updates: usize| {
8845 let path = temp_path(&format!("vlen_slice_empty_reuse_{updates}"));
8846 let file = H5File::create(&path).unwrap();
8847 file.write_vlen_strings("notes", &["", "b"]).unwrap();
8848 let ds = file.dataset_writer("notes").unwrap();
8849 for _ in 0..updates {
8850 ds.write_vlen_strings_slice(0, &[""]).unwrap();
8851 }
8852 file.close().unwrap();
8853 let n = std::fs::metadata(&path).unwrap().len();
8854 let read = H5File::open(&path).unwrap();
8855 assert_eq!(
8856 read.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8857 vec!["".to_string(), "b".to_string()]
8858 );
8859 drop(read);
8860 std::fs::remove_file(&path).ok();
8861 n
8862 };
8863
8864 let settled = size_after(3);
8865 assert_eq!(size_after(20), settled, "20 empty updates against 3");
8866 assert_eq!(size_after(50), settled, "50 empty updates against 3");
8867 }
8868
8869 #[test]
8872 fn write_vlen_strings_slice_keeps_the_untouched_strings_readable() {
8873 let path = temp_path("vlen_slice_heap_survivors");
8874 {
8875 let file = H5File::create(&path).unwrap();
8876 file.write_vlen_strings("notes", &["a", "b", "c", "d"])
8877 .unwrap();
8878 let ds = file.dataset_writer("notes").unwrap();
8879 ds.write_vlen_strings_slice(1, &["B"]).unwrap();
8882 ds.write_vlen_strings_slice(3, &["D"]).unwrap();
8883 file.close().unwrap();
8884 }
8885 let file = H5File::open(&path).unwrap();
8886 assert_eq!(
8887 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8888 vec!["a", "B", "c", "D"]
8889 );
8890 std::fs::remove_file(&path).ok();
8891 }
8892
8893 #[test]
8897 fn write_vlen_strings_slice_frees_an_emptied_collection() {
8898 let path = temp_path("vlen_slice_heap_emptied");
8899 {
8900 let file = H5File::create(&path).unwrap();
8901 file.create_appendable_vlen_dataset("notes", 2, None)
8902 .unwrap();
8903 file.append_vlen_strings("notes", &["p", "q", "r", "s"])
8904 .unwrap();
8905 file.close().unwrap();
8906 }
8907 {
8908 let file = H5File::open_rw(&path).unwrap();
8909 file.dataset_writer("notes")
8910 .unwrap()
8911 .write_vlen_strings_slice(0, &["w", "x", "y", "z"])
8912 .unwrap();
8913 file.close().unwrap();
8914 }
8915 let file = H5File::open(&path).unwrap();
8916 assert_eq!(
8917 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8918 vec!["w", "x", "y", "z"]
8919 );
8920 std::fs::remove_file(&path).ok();
8921 }
8922
8923 #[test]
8929 fn write_vlen_strings_slice_keeps_an_oversized_collections_block_whole() {
8930 let big = |tag: char| std::iter::repeat_n(tag, 2000).collect::<String>();
8931 let size_after = |cycles: usize| {
8932 let path = temp_path(&format!("vlen_slice_heap_big_{cycles}"));
8933 let file = H5File::create(&path).unwrap();
8934 let seed: Vec<String> = "abcd".chars().map(big).collect();
8935 let refs: Vec<&str> = seed.iter().map(|s| s.as_str()).collect();
8936 file.write_vlen_strings("notes", &refs).unwrap();
8939 let ds = file.dataset_writer("notes").unwrap();
8940 for _ in 0..cycles {
8941 let head = big('x');
8944 ds.write_vlen_strings_slice(0, &[&head]).unwrap();
8945 let tail: Vec<String> = "yzw".chars().map(big).collect();
8946 let tail_refs: Vec<&str> = tail.iter().map(|s| s.as_str()).collect();
8947 ds.write_vlen_strings_slice(1, &tail_refs).unwrap();
8948 }
8949 file.close().unwrap();
8950 let n = std::fs::metadata(&path).unwrap().len();
8951 let read = H5File::open(&path).unwrap();
8952 assert_eq!(
8953 read.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8954 vec![big('x'), big('y'), big('z'), big('w')]
8955 );
8956 drop(read);
8957 std::fs::remove_file(&path).ok();
8958 n
8959 };
8960
8961 let settled = size_after(4);
8962 assert_eq!(size_after(30), settled, "30 cycles against 4");
8963 }
8964
8965 #[test]
8968 fn write_vlen_strings_slice_releases_a_buffered_elements_object() {
8969 let path = temp_path("vlen_slice_heap_buffered");
8970 let file = H5File::create(&path).unwrap();
8971 file.create_appendable_vlen_dataset("notes", 4, None)
8972 .unwrap();
8973 file.append_vlen_strings("notes", &["a", "b", "c"]).unwrap();
8974 let ds = file.dataset_writer("notes").unwrap();
8975 for i in 0..20 {
8976 ds.write_vlen_strings_slice(1, &[&format!("patch {i}")])
8977 .unwrap();
8978 }
8979 file.close().unwrap();
8980
8981 let file = H5File::open(&path).unwrap();
8982 assert_eq!(
8983 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8984 vec!["a", "patch 19", "c"]
8985 );
8986 let size = std::fs::metadata(&path).unwrap().len();
8987 std::fs::remove_file(&path).ok();
8988 assert!(
8989 size < 20 * 4096,
8990 "20 buffered updates left {size} bytes, one collection per update"
8991 );
8992 }
8993
8994 #[test]
8999 fn write_slice_into_the_buffered_tail_survives_close() {
9000 let path = temp_path("slice_into_buffered_tail");
9001 {
9002 let file = H5File::create(&path).unwrap();
9003 let ds = file
9004 .new_dataset::<i32>()
9005 .shape([0])
9006 .chunk(&[4])
9007 .max_shape(&[None])
9008 .create("d")
9009 .unwrap();
9010 ds.append(&[10, 11, 12, 13, 50, 51]).unwrap();
9012 ds.write_slice(&[4], &[1], &[99]).unwrap();
9013 file.close().unwrap();
9014 }
9015 {
9016 let file = H5File::open(&path).unwrap();
9017 let ds = file.dataset("d").unwrap();
9018 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![10, 11, 12, 13, 99, 51]);
9019 }
9020 std::fs::remove_file(&path).ok();
9021 }
9022
9023 #[test]
9028 fn extend_does_not_move_buffered_appends() {
9029 let path = temp_path("extend_keeps_buffered_rows");
9030 {
9031 let file = H5File::create(&path).unwrap();
9032 let ds = file
9033 .new_dataset::<i32>()
9034 .shape([0])
9035 .chunk(&[4])
9036 .max_shape(&[None])
9037 .create("d")
9038 .unwrap();
9039 ds.append(&[10, 11, 12, 13, 50, 51]).unwrap(); ds.extend(&[10]).unwrap();
9041 file.close().unwrap();
9042 }
9043 {
9044 let file = H5File::open(&path).unwrap();
9045 let ds = file.dataset("d").unwrap();
9046 assert_eq!(
9047 ds.read_raw::<i32>().unwrap(),
9048 vec![10, 11, 12, 13, 50, 51, 0, 0, 0, 0]
9049 );
9050 }
9051 std::fs::remove_file(&path).ok();
9052 }
9053
9054 #[test]
9060 fn append_to_a_btree_v2_dataset_survives_close() {
9061 let path = temp_path("append_bt2_close");
9062 {
9063 let file = H5File::create(&path).unwrap();
9064 let ds = file
9065 .new_dataset::<i32>()
9066 .shape([0, 3])
9067 .chunk(&[4, 3])
9068 .max_shape(&[None, None])
9069 .create("d")
9070 .unwrap();
9071 ds.append(&[1, 2, 3]).unwrap();
9075 ds.append(&(4..=15).collect::<Vec<i32>>()).unwrap();
9076 file.close().unwrap();
9077 }
9078 {
9079 let file = H5File::open(&path).unwrap();
9080 let ds = file.dataset("d").unwrap();
9081 assert_eq!(ds.shape(), vec![5, 3]);
9082 assert_eq!(
9083 ds.read_raw::<i32>().unwrap(),
9084 (1..=15).collect::<Vec<i32>>()
9085 );
9086 }
9087 std::fs::remove_file(&path).ok();
9088 }
9089
9090 #[test]
9094 fn append_scatters_frames_across_narrow_chunk_tiles() {
9095 let path = temp_path("append_narrow_chunks");
9096 {
9097 let file = H5File::create(&path).unwrap();
9098 let ds = file
9099 .new_dataset::<i32>()
9100 .shape([0, 8])
9101 .chunk(&[2, 4])
9102 .max_shape(&[None, Some(8)])
9103 .create("d")
9104 .unwrap();
9105 ds.append(&(0..24).collect::<Vec<i32>>()).unwrap();
9108 file.close().unwrap();
9109 }
9110 {
9111 let file = H5File::open(&path).unwrap();
9112 let ds = file.dataset("d").unwrap();
9113 assert_eq!(ds.shape(), vec![3, 8]);
9114 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..24).collect::<Vec<i32>>());
9115 }
9116 std::fs::remove_file(&path).ok();
9117 }
9118
9119 #[test]
9123 fn append_to_a_full_fixed_array_dataset_errors() {
9124 let path = temp_path("append_fa_errors");
9125 let file = H5File::create(&path).unwrap();
9126 let ds = file
9127 .new_dataset::<i32>()
9128 .shape([4, 3])
9129 .chunk(&[2, 3])
9130 .create("d")
9131 .unwrap();
9132 let err = ds.append(&(0..6).collect::<Vec<i32>>()).unwrap_err();
9133 assert!(
9134 err.to_string().contains("chunk grid"),
9135 "unexpected error: {err}"
9136 );
9137 file.close().unwrap();
9138 std::fs::remove_file(&path).ok();
9139 }
9140
9141 #[test]
9147 fn fixed_array_with_a_larger_max_shape_grows_and_survives_close() {
9148 let path = temp_path("fa_growable_dim0");
9149 {
9150 let file = H5File::create(&path).unwrap();
9151 let ds = file
9152 .new_dataset::<i32>()
9153 .shape([4, 3])
9154 .chunk(&[2, 3])
9155 .max_shape(&[Some(10), Some(3)])
9156 .create("d")
9157 .unwrap();
9158 ds.write_raw(&(0..12).collect::<Vec<i32>>()).unwrap();
9159 ds.append(&(12..18).collect::<Vec<i32>>()).unwrap();
9160 file.close().unwrap();
9161 }
9162 {
9163 let file = H5File::open(&path).unwrap();
9164 let ds = file.dataset("d").unwrap();
9165 assert_eq!(ds.shape(), vec![6, 3]);
9166 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..18).collect::<Vec<i32>>());
9167 }
9168 std::fs::remove_file(&path).ok();
9169 }
9170
9171 #[test]
9177 fn fixed_array_growable_inner_dimension_keeps_chunk_slots() {
9178 let path = temp_path("fa_growable_dim1");
9179 {
9180 let file = H5File::create(&path).unwrap();
9181 let ds = file
9182 .new_dataset::<i32>()
9183 .shape([4, 3])
9184 .chunk(&[2, 3])
9185 .max_shape(&[Some(4), Some(9)])
9186 .create("d")
9187 .unwrap();
9188 ds.write_raw(&(0..12).collect::<Vec<i32>>()).unwrap();
9189 ds.extend(&[4, 6]).unwrap();
9190 ds.write_slice(&[0, 3], &[4, 3], &(12..24).collect::<Vec<i32>>())
9191 .unwrap();
9192 file.close().unwrap();
9193 }
9194 {
9195 let file = H5File::open(&path).unwrap();
9196 let ds = file.dataset("d").unwrap();
9197 assert_eq!(ds.shape(), vec![4, 6]);
9198 let mut expect = Vec::new();
9201 for r in 0i32..4 {
9202 expect.extend((r * 3)..(r * 3 + 3));
9203 expect.extend((12 + r * 3)..(12 + r * 3 + 3));
9204 }
9205 assert_eq!(ds.read_raw::<i32>().unwrap(), expect);
9206 }
9207 std::fs::remove_file(&path).ok();
9208 }
9209
9210 #[test]
9214 fn extend_beyond_the_maximum_is_rejected() {
9215 let path = temp_path("extend_beyond_max");
9216 let file = H5File::create(&path).unwrap();
9217 let ds = file
9218 .new_dataset::<i32>()
9219 .shape([4, 3])
9220 .chunk(&[2, 3])
9221 .max_shape(&[Some(6), Some(3)])
9222 .create("d")
9223 .unwrap();
9224 ds.extend(&[6, 3]).unwrap();
9225 let err = ds.extend(&[8, 3]).unwrap_err();
9226 assert!(
9227 err.to_string().contains("exceeds the maximum"),
9228 "unexpected error: {err}"
9229 );
9230 file.close().unwrap();
9231 std::fs::remove_file(&path).ok();
9232 }
9233
9234 #[test]
9240 fn builder_accepts_an_unlimited_inner_dimension() {
9241 let path = temp_path("unlimited_inner_dim");
9242 let file = H5File::create(&path).unwrap();
9243 let ds = file
9244 .new_dataset::<i32>()
9245 .shape([4, 0])
9246 .chunk(&[2, 2])
9247 .max_shape(&[Some(4), None])
9248 .create("d")
9249 .unwrap();
9250 assert_eq!(ds.shape(), vec![4, 0]);
9251
9252 ds.extend(&[4, 2]).unwrap();
9257 ds.write_slice(&[0, 0], &[4, 2], &[1, 2, 3, 4, 5, 6, 7, 8])
9258 .unwrap();
9259 ds.extend(&[4, 4]).unwrap();
9260 ds.write_slice(&[0, 2], &[4, 2], &[9, 10, 11, 12, 13, 14, 15, 16])
9261 .unwrap();
9262
9263 file.close().unwrap();
9264 let file = H5File::open(&path).unwrap();
9265 let ds = file.dataset("d").unwrap();
9266 assert_eq!(
9267 ds.read_slice::<i32>(&[0, 0], &[4, 4]).unwrap(),
9268 vec![1, 2, 9, 10, 3, 4, 11, 12, 5, 6, 13, 14, 7, 8, 15, 16]
9269 );
9270 std::fs::remove_file(&path).ok();
9271 }
9272
9273 #[test]
9278 fn builder_rejects_a_chunk_wider_than_a_fixed_max_dimension() {
9279 let path = temp_path("builder_chunk_wider_than_max");
9280 let file = H5File::create(&path).unwrap();
9281 let err = match file
9282 .new_dataset::<i32>()
9283 .shape([0, 2])
9284 .chunk(&[2, 4])
9285 .max_shape(&[None, Some(2)])
9286 .create("v5")
9287 {
9288 Ok(_) => panic!("create accepted a chunk wider than the fixed max dimension"),
9289 Err(e) => e,
9290 };
9291 assert!(
9292 err.to_string().contains("maximum dimension size"),
9293 "unexpected error: {err}"
9294 );
9295 file.close().unwrap();
9296 std::fs::remove_file(&path).ok();
9297 }
9298
9299 #[test]
9302 fn numeric_int_checked_conversion_boundaries() {
9303 let path = temp_path("numeric_int_bounds");
9304 {
9305 let file = H5File::create(&path).unwrap();
9306 let ds = file.new_dataset::<u64>().shape([2]).create("u").unwrap();
9307 ds.write_raw(&[1u64, u64::MAX]).unwrap();
9308 let ds = file.new_dataset::<i32>().shape([2]).create("i").unwrap();
9309 ds.write_raw(&[-1i32, 5]).unwrap();
9310 file.close().unwrap();
9311 }
9312 let file = H5File::open(&path).unwrap();
9313
9314 let u = file.dataset("u").unwrap();
9315 assert_eq!(u.read_numeric_as::<u64>().unwrap(), vec![1, u64::MAX]);
9316 assert_eq!(
9317 u.read_numeric_as::<i128>().unwrap(),
9318 vec![1, i128::from(u64::MAX)]
9319 );
9320 let err = u.read_numeric_as::<i64>().unwrap_err();
9321 assert!(
9322 err.to_string()
9323 .contains("value 18446744073709551615 at element 1 does not fit in i64"),
9324 "unexpected error: {err}"
9325 );
9326
9327 let i = file.dataset("i").unwrap();
9328 assert_eq!(i.read_numeric_as::<i64>().unwrap(), vec![-1, 5]);
9329 let err = i.read_numeric_as::<u32>().unwrap_err();
9330 assert!(
9331 err.to_string()
9332 .contains("value -1 at element 0 does not fit in u32"),
9333 "unexpected error: {err}"
9334 );
9335 std::fs::remove_file(&path).ok();
9336 }
9337
9338 #[test]
9340 fn numeric_float_widening_only() {
9341 let path = temp_path("numeric_float");
9342 {
9343 let file = H5File::create(&path).unwrap();
9344 let ds = file.new_dataset::<f32>().shape([2]).create("f4").unwrap();
9345 ds.write_raw(&[1.5f32, -2.25]).unwrap();
9346 let ds = file.new_dataset::<f64>().shape([1]).create("f8").unwrap();
9347 ds.write_raw(&[3.75f64]).unwrap();
9348 file.close().unwrap();
9349 }
9350 let file = H5File::open(&path).unwrap();
9351
9352 let f4 = file.dataset("f4").unwrap();
9353 assert_eq!(f4.read_numeric_as::<f32>().unwrap(), vec![1.5, -2.25]);
9354 assert_eq!(f4.read_numeric_as::<f64>().unwrap(), vec![1.5, -2.25]);
9355
9356 let f8 = file.dataset("f8").unwrap();
9357 assert_eq!(f8.read_numeric_as::<f64>().unwrap(), vec![3.75]);
9358 let err = f8.read_numeric_as::<f32>().unwrap_err();
9359 assert!(
9360 err.to_string().contains("narrowing"),
9361 "unexpected error: {err}"
9362 );
9363 std::fs::remove_file(&path).ok();
9364 }
9365
9366 #[test]
9369 fn numeric_cross_class_and_non_numeric_rejected() {
9370 let path = temp_path("numeric_cross_class");
9371 {
9372 let file = H5File::create(&path).unwrap();
9373 let ds = file.new_dataset::<f64>().shape([1]).create("f8").unwrap();
9374 ds.write_raw(&[1.0f64]).unwrap();
9375 let ds = file.new_dataset::<i32>().shape([1]).create("i4").unwrap();
9376 ds.write_raw(&[7i32]).unwrap();
9377 file.write_vlen_strings("s", &["a", "b"]).unwrap();
9378 file.close().unwrap();
9379 }
9380 let file = H5File::open(&path).unwrap();
9381
9382 let err = file
9383 .dataset("f8")
9384 .unwrap()
9385 .read_numeric_as::<i64>()
9386 .unwrap_err();
9387 assert!(
9388 err.to_string().contains("floating-point dataset as i64"),
9389 "unexpected error: {err}"
9390 );
9391 let err = file
9392 .dataset("i4")
9393 .unwrap()
9394 .read_numeric_as::<f64>()
9395 .unwrap_err();
9396 assert!(
9397 err.to_string().contains("integer dataset as f64"),
9398 "unexpected error: {err}"
9399 );
9400 let err = file
9401 .dataset("s")
9402 .unwrap()
9403 .read_numeric_as::<i64>()
9404 .unwrap_err();
9405 assert!(
9406 err.to_string().contains("is not numeric"),
9407 "unexpected error: {err}"
9408 );
9409 std::fs::remove_file(&path).ok();
9410 }
9411
9412 #[test]
9416 fn numeric_big_endian_decode() {
9417 use super::numeric;
9418 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
9419
9420 let dt = DatatypeMessage::FixedPoint {
9421 size: 4,
9422 byte_order: ByteOrder::BigEndian,
9423 signed: true,
9424 bit_offset: 0,
9425 bit_precision: 32,
9426 };
9427 let mut raw = Vec::new();
9428 raw.extend_from_slice(&(-2i32).to_be_bytes());
9429 raw.extend_from_slice(&(100_000i32).to_be_bytes());
9430 let kind = numeric::classify(&dt).unwrap();
9431 assert_eq!(
9432 numeric::convert::<i64>(kind, &raw).unwrap(),
9433 vec![-2, 100_000]
9434 );
9435
9436 let dt = DatatypeMessage::FloatingPoint {
9437 size: 8,
9438 byte_order: ByteOrder::BigEndian,
9439 sign_location: 63,
9440 bit_offset: 0,
9441 bit_precision: 64,
9442 exponent_location: 52,
9443 exponent_size: 11,
9444 mantissa_location: 0,
9445 mantissa_size: 52,
9446 exponent_bias: 1023,
9447 };
9448 let raw = (-2.25f64).to_be_bytes();
9449 let kind = numeric::classify(&dt).unwrap();
9450 assert_eq!(numeric::convert::<f64>(kind, &raw).unwrap(), vec![-2.25]);
9451 }
9452
9453 #[test]
9458 fn attr_read_numeric_validates_datatype() {
9459 use crate::types::{Complex64, HBool, VarLenUnicode};
9460 let path = temp_path("attr_read_numeric_validate");
9461 {
9462 let file = H5File::create(&path).unwrap();
9463 let ds = file.new_dataset::<f32>().shape([2]).create("d").unwrap();
9464 ds.write_raw(&[1.0f32; 2]).unwrap();
9465 let a = ds.new_attr::<f64>().shape(()).create("f8").unwrap();
9466 a.write_numeric(&1.5f64).unwrap();
9467 let a = ds.new_attr::<i32>().shape(()).create("i4").unwrap();
9468 a.write_numeric(&-7i32).unwrap();
9469 let a = ds.new_attr::<HBool>().shape(()).create("b").unwrap();
9470 a.write_numeric(&HBool::from(true)).unwrap();
9471 let a = ds.new_attr::<Complex64>().shape(()).create("z").unwrap();
9472 a.write_numeric(&Complex64 { re: 1.0, im: -2.0 }).unwrap();
9473 let a = ds
9474 .new_attr::<VarLenUnicode>()
9475 .shape(())
9476 .create("s")
9477 .unwrap();
9478 a.write_scalar(&VarLenUnicode("text".into())).unwrap();
9479 file.close().unwrap();
9480 }
9481 let file = H5File::open(&path).unwrap();
9482 let ds = file.dataset("d").unwrap();
9483
9484 let f8 = ds.attr("f8").unwrap();
9485 assert_eq!(f8.read_numeric::<f64>().unwrap(), 1.5);
9486 let err = f8.read_numeric::<f32>().unwrap_err();
9488 assert!(
9489 err.to_string().contains("read_numeric_as"),
9490 "unexpected error: {err}"
9491 );
9492 assert!(f8.read_numeric::<i64>().is_err());
9493
9494 let i4 = ds.attr("i4").unwrap();
9495 assert_eq!(i4.read_numeric::<i32>().unwrap(), -7);
9496 assert!(i4.read_numeric::<u32>().is_err());
9497
9498 assert!(bool::from(
9499 ds.attr("b").unwrap().read_numeric::<HBool>().unwrap()
9500 ));
9501 let z = ds.attr("z").unwrap().read_numeric::<Complex64>().unwrap();
9502 assert_eq!((z.re, z.im), (1.0, -2.0));
9503
9504 let s = ds.attr("s").unwrap();
9507 assert!(s.read_numeric::<f64>().is_err());
9508 assert!(s.read_numeric_as::<f64>().is_err());
9509 std::fs::remove_file(&path).ok();
9510 }
9511
9512 #[test]
9516 fn attr_read_numeric_as_converts() {
9517 let path = temp_path("attr_read_numeric_as");
9518 {
9519 let file = H5File::create(&path).unwrap();
9520 let ds = file.new_dataset::<f32>().shape([2]).create("d").unwrap();
9521 ds.write_raw(&[1.0f32; 2]).unwrap();
9522 let a = ds.new_attr::<i32>().shape(()).create("i4").unwrap();
9523 a.write_numeric(&-7i32).unwrap();
9524 let a = ds.new_attr::<u64>().shape(()).create("u8max").unwrap();
9525 a.write_numeric(&u64::MAX).unwrap();
9526 let a = ds.new_attr::<i16>().shape([3]).create("arr").unwrap();
9527 a.write_array(&[1i16, -2, 3]).unwrap();
9528 file.close().unwrap();
9529 }
9530 let file = H5File::open(&path).unwrap();
9531 let ds = file.dataset("d").unwrap();
9532 assert_eq!(
9533 ds.attr("i4").unwrap().read_numeric_as::<i64>().unwrap(),
9534 vec![-7]
9535 );
9536 let err = ds
9537 .attr("u8max")
9538 .unwrap()
9539 .read_numeric_as::<i64>()
9540 .unwrap_err();
9541 assert!(
9542 err.to_string().contains("does not fit in i64"),
9543 "unexpected error: {err}"
9544 );
9545 assert_eq!(
9546 ds.attr("arr").unwrap().read_numeric_as::<i32>().unwrap(),
9547 vec![1, -2, 3]
9548 );
9549 assert!(ds.attr("i4").unwrap().read_numeric_as::<f64>().is_err());
9550 std::fs::remove_file(&path).ok();
9551 }
9552
9553 #[test]
9555 fn numeric_slice_conversion() {
9556 let path = temp_path("numeric_slice");
9557 {
9558 let file = H5File::create(&path).unwrap();
9559 let ds = file.new_dataset::<i16>().shape([2, 3]).create("m").unwrap();
9560 ds.write_raw(&[1i16, 2, 3, 4, 5, 6]).unwrap();
9561 file.close().unwrap();
9562 }
9563 let file = H5File::open(&path).unwrap();
9564 let m = file.dataset("m").unwrap();
9565 assert_eq!(
9566 m.read_numeric_slice_as::<i32>(&[0, 1], &[2, 2]).unwrap(),
9567 vec![2, 3, 5, 6]
9568 );
9569 std::fs::remove_file(&path).ok();
9570 }
9571
9572 #[test]
9577 fn null_dataspace_distinct_from_scalar() {
9578 let path = temp_path("null_vs_scalar");
9579 {
9580 let file = H5File::create(&path).unwrap();
9581 file.new_dataset::<i32>().null().create("empty").unwrap();
9582 let scalar = file.new_dataset::<i32>().scalar().create("scalar").unwrap();
9583 scalar.write_raw(&[42i32]).unwrap();
9584 file.close().unwrap();
9585 }
9586 let file = H5File::open(&path).unwrap();
9587
9588 let empty = file.dataset("empty").unwrap();
9589 assert!(empty.is_null());
9590 assert_eq!(empty.shape(), Vec::<usize>::new());
9591 assert_eq!(empty.total_elements(), 0);
9592 assert_eq!(empty.read_raw_bytes().unwrap(), Vec::<u8>::new());
9593
9594 let scalar = file.dataset("scalar").unwrap();
9595 assert!(!scalar.is_null());
9596 assert_eq!(scalar.shape(), Vec::<usize>::new());
9597 assert_eq!(scalar.total_elements(), 1);
9598 assert_eq!(scalar.read_raw::<i32>().unwrap(), vec![42]);
9599
9600 std::fs::remove_file(&path).ok();
9601 }
9602
9603 #[test]
9607 fn null_dataspace_rejects_writes() {
9608 let path = temp_path("null_write_rejected");
9609 let file = H5File::create(&path).unwrap();
9610 let ds = file.new_dataset::<i32>().null().create("empty").unwrap();
9611 assert!(ds.write_raw(&[1i32]).is_err());
9612 assert!(ds.write_raw_bytes(&[0u8; 4]).is_err());
9613 file.close().unwrap();
9614 std::fs::remove_file(&path).ok();
9615 }
9616
9617 #[test]
9621 fn null_dataspace_rejects_chunking_and_fill_value() {
9622 let path = temp_path("null_chunk_rejected");
9623 let file = H5File::create(&path).unwrap();
9624 assert!(file
9625 .new_dataset::<i32>()
9626 .null()
9627 .chunk(&[4])
9628 .create("a")
9629 .is_err());
9630 assert!(file
9631 .new_dataset::<i32>()
9632 .null()
9633 .fill_value(7i32)
9634 .create("b")
9635 .is_err());
9636 file.close().unwrap();
9637 std::fs::remove_file(&path).ok();
9638 }
9639
9640 #[test]
9645 fn a_committed_type_that_cannot_be_shared_creates_no_dataset() {
9646 use crate::format::messages::datatype::DatatypeMessage;
9647
9648 let path = temp_path("committed_refused");
9649 let file = H5File::create(&path).unwrap();
9650 file.commit_datatype("t", DatatypeMessage::i32_type())
9651 .unwrap();
9652
9653 assert!(file
9654 .new_dataset::<i32>()
9655 .committed_type("absent")
9656 .shape([2usize])
9657 .create("a")
9658 .is_err());
9659 assert!(file
9660 .new_dataset::<u64>()
9661 .committed_type("t")
9662 .object_references()
9663 .shape([2usize])
9664 .create("b")
9665 .is_err());
9666 file.new_dataset::<i32>()
9669 .shape([2usize])
9670 .create("taken")
9671 .unwrap();
9672 assert!(file
9673 .commit_datatype("taken", DatatypeMessage::i32_type())
9674 .is_err());
9675 assert!(file
9676 .commit_datatype("t", DatatypeMessage::f64_type())
9677 .is_err());
9678
9679 assert_eq!(file.dataset_names(), vec!["taken".to_string()]);
9680 assert_eq!(file.named_datatype_names(), vec!["t".to_string()]);
9681 file.close().unwrap();
9682 std::fs::remove_file(&path).ok();
9683 }
9684
9685 #[test]
9689 fn deleting_a_group_takes_the_committed_datatypes_it_named() {
9690 use crate::format::messages::datatype::DatatypeMessage;
9691
9692 let path = temp_path("committed_group_deleted");
9693 {
9694 let file = H5File::create(&path).unwrap();
9695 let types = file.create_group("types").unwrap();
9696 types
9697 .commit_datatype("t", DatatypeMessage::i32_type())
9698 .unwrap();
9699 assert_eq!(file.named_datatype_names(), vec!["types/t".to_string()]);
9700
9701 file.delete_group("types").unwrap();
9702 assert!(file.named_datatype_names().is_empty());
9703 assert!(file
9704 .new_dataset::<i32>()
9705 .committed_type("types/t")
9706 .shape([2usize])
9707 .create("d")
9708 .is_err());
9709
9710 let types = file.create_group("types").unwrap();
9712 types
9713 .commit_datatype("t", DatatypeMessage::f64_type())
9714 .unwrap();
9715 file.close().unwrap();
9716 }
9717 let file = H5File::open(&path).unwrap();
9718 assert_eq!(file.named_datatype_names(), vec!["types/t".to_string()]);
9719 assert_eq!(
9720 file.named_datatype("types/t").unwrap().datatype().unwrap(),
9721 crate::format::messages::datatype::DatatypeMessage::f64_type()
9722 );
9723 drop(file);
9724 std::fs::remove_file(&path).ok();
9725 }
9726
9727 fn compact_image(path: &std::path::Path, name: &str) -> Option<Vec<u8>> {
9731 use crate::format::messages::data_layout::DataLayoutMessage;
9732 let mut reader = crate::io::reader::Hdf5Reader::open(path).unwrap();
9733 match &reader.dataset_info(name).unwrap().layout {
9734 DataLayoutMessage::Compact { data } => Some(data.clone()),
9735 other => panic!("{name}: expected a compact layout, got {other:?}"),
9736 }
9737 }
9738
9739 #[test]
9743 fn a_compact_dataset_stores_its_data_in_the_layout_message() {
9744 let path = temp_path("compact_roundtrip");
9745 let values: Vec<i32> = (0..16).collect();
9746 {
9747 let file = H5File::create(&path).unwrap();
9748 file.new_dataset::<i32>()
9749 .shape([16usize])
9750 .compact()
9751 .create("d")
9752 .unwrap()
9753 .write_raw(&values)
9754 .unwrap();
9755 file.close().unwrap();
9756 }
9757
9758 let image = compact_image(&path, "d").unwrap();
9759 assert_eq!(
9760 image,
9761 values
9762 .iter()
9763 .flat_map(|v| v.to_le_bytes())
9764 .collect::<Vec<_>>()
9765 );
9766
9767 let file = H5File::open(&path).unwrap();
9768 let ds = file.dataset("d").unwrap();
9769 assert_eq!(ds.shape(), vec![16]);
9770 assert_eq!(ds.read_raw::<i32>().unwrap(), values);
9771 std::fs::remove_file(&path).ok();
9772 }
9773
9774 #[test]
9778 fn a_compact_dataset_lands_in_its_group() {
9779 let path = temp_path("compact_in_group");
9780 {
9781 let file = H5File::create(&path).unwrap();
9782 let g = file.root_group().create_group("g").unwrap();
9783 g.new_dataset::<u8>()
9784 .shape([4usize])
9785 .compact()
9786 .create("d")
9787 .unwrap()
9788 .write_raw(&[1u8, 2, 3, 4])
9789 .unwrap();
9790 file.close().unwrap();
9791 }
9792 assert_eq!(compact_image(&path, "/g/d").unwrap(), vec![1u8, 2, 3, 4]);
9793
9794 let file = H5File::open(&path).unwrap();
9795 assert_eq!(
9796 file.dataset("/g/d").unwrap().read_raw::<u8>().unwrap(),
9797 vec![1u8, 2, 3, 4]
9798 );
9799 std::fs::remove_file(&path).ok();
9800 }
9801
9802 #[test]
9806 fn an_unwritten_compact_dataset_reads_back_as_its_fill_value() {
9807 let path = temp_path("compact_fill");
9808 {
9809 let file = H5File::create(&path).unwrap();
9810 file.new_dataset::<i32>()
9811 .shape([4usize])
9812 .compact()
9813 .fill_value(-7i32)
9814 .create("d")
9815 .unwrap();
9816 file.close().unwrap();
9817 }
9818 let file = H5File::open(&path).unwrap();
9819 assert_eq!(
9820 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
9821 vec![-7i32; 4]
9822 );
9823 std::fs::remove_file(&path).ok();
9824 }
9825
9826 #[test]
9831 fn a_reopened_compact_dataset_keeps_its_image() {
9832 let path = temp_path("compact_reopen");
9833 let values: Vec<i32> = (100..108).collect();
9834 {
9835 let file = H5File::create(&path).unwrap();
9836 file.new_dataset::<i32>()
9837 .shape([8usize])
9838 .compact()
9839 .create("d")
9840 .unwrap()
9841 .write_raw(&values)
9842 .unwrap();
9843 file.close().unwrap();
9844 }
9845 {
9846 let file = H5File::open_rw(&path).unwrap();
9847 file.dataset_writer("d")
9848 .unwrap()
9849 .new_attr::<i32>()
9850 .shape(())
9851 .create("note")
9852 .unwrap()
9853 .write_numeric(&1i32)
9854 .unwrap();
9855 file.close().unwrap();
9856 }
9857 assert_eq!(
9858 compact_image(&path, "d").unwrap(),
9859 values
9860 .iter()
9861 .flat_map(|v| v.to_le_bytes())
9862 .collect::<Vec<_>>()
9863 );
9864
9865 let file = H5File::open(&path).unwrap();
9866 assert_eq!(
9867 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
9868 values
9869 );
9870 std::fs::remove_file(&path).ok();
9871 }
9872
9873 #[test]
9877 fn the_compact_ceiling_is_checked_in_bytes() {
9878 let path = temp_path("compact_ceiling");
9879 let file = H5File::create(&path).unwrap();
9880
9881 let fits = crate::MAX_COMPACT_DATA / 4;
9882 file.new_dataset::<i32>()
9883 .shape([fits])
9884 .compact()
9885 .create("fits")
9886 .unwrap();
9887
9888 let over = fits + 1;
9889 let err = match file
9890 .new_dataset::<i32>()
9891 .shape([over])
9892 .compact()
9893 .create("over")
9894 {
9895 Err(e) => e.to_string(),
9896 Ok(_) => panic!("an image {} bytes wide must be refused", over * 4),
9897 };
9898 assert!(
9899 err.contains(&(over * 4).to_string())
9900 && err.contains(&crate::MAX_COMPACT_DATA.to_string()),
9901 "the error must name both sizes: {err}"
9902 );
9903
9904 file.close().unwrap();
9905 std::fs::remove_file(&path).ok();
9906 }
9907
9908 #[test]
9912 fn compact_rejects_chunking_filters_growth_and_a_null_dataspace() {
9913 let path = temp_path("compact_rejects");
9914 let file = H5File::create(&path).unwrap();
9915 assert!(file
9916 .new_dataset::<i32>()
9917 .shape([4usize])
9918 .compact()
9919 .chunk(&[4])
9920 .create("a")
9921 .is_err());
9922 assert!(file
9923 .new_dataset::<i32>()
9924 .shape([4usize])
9925 .compact()
9926 .deflate(4)
9927 .create("b")
9928 .is_err());
9929 assert!(file
9930 .new_dataset::<i32>()
9931 .shape([4usize])
9932 .compact()
9933 .max_shape(&[None])
9934 .create("c")
9935 .is_err());
9936 assert!(file
9937 .new_dataset::<i32>()
9938 .shape([4usize])
9939 .compact()
9940 .max_shape(&[Some(8)])
9941 .create("d")
9942 .is_err());
9943 assert!(file
9944 .new_dataset::<i32>()
9945 .null()
9946 .compact()
9947 .create("e")
9948 .is_err());
9949 file.close().unwrap();
9950 std::fs::remove_file(&path).ok();
9951 }
9952
9953 fn stored_pipeline(
9955 path: &std::path::Path,
9956 name: &str,
9957 ) -> crate::format::messages::filter::FilterPipeline {
9958 let mut reader = crate::io::reader::Hdf5Reader::open(path).unwrap();
9959 reader
9960 .dataset_info(name)
9961 .unwrap()
9962 .filter_pipeline
9963 .clone()
9964 .unwrap_or_else(|| panic!("{name}: no filter pipeline"))
9965 }
9966
9967 #[test]
9971 fn shuffle_alone_is_a_filter_pipeline() {
9972 use crate::format::messages::filter::{FilterPipeline, FILTER_SHUFFLE};
9973 let path = temp_path("shuffle_alone");
9974 let values: Vec<i32> = (0..64).collect();
9975 {
9976 let file = H5File::create(&path).unwrap();
9977 file.new_dataset::<i32>()
9978 .shape([64usize])
9979 .chunk(&[16])
9980 .shuffle()
9981 .create("d")
9982 .unwrap()
9983 .write_raw(&values)
9984 .unwrap();
9985 file.close().unwrap();
9986 }
9987 let pipeline = stored_pipeline(&path, "d");
9988 assert_eq!(pipeline, FilterPipeline::shuffle(4));
9989 assert_eq!(pipeline.filters[0].id, FILTER_SHUFFLE);
9990
9991 let file = H5File::open(&path).unwrap();
9992 assert_eq!(
9993 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
9994 values
9995 );
9996 std::fs::remove_file(&path).ok();
9997 }
9998
9999 #[cfg(feature = "deflate")]
10003 #[test]
10004 fn shuffle_composes_with_deflate() {
10005 let path = temp_path("shuffle_then_deflate");
10006 let values: Vec<i32> = (0..64).collect();
10007 {
10008 let file = H5File::create(&path).unwrap();
10009 for (name, ds) in [
10010 ("split", file.new_dataset::<i32>().shuffle().deflate(6)),
10011 ("combined", file.new_dataset::<i32>().shuffle_deflate(6)),
10012 ] {
10013 ds.shape([64usize])
10014 .chunk(&[16])
10015 .create(name)
10016 .unwrap()
10017 .write_raw(&values)
10018 .unwrap();
10019 }
10020 file.close().unwrap();
10021 }
10022 assert_eq!(
10023 stored_pipeline(&path, "split"),
10024 crate::format::messages::filter::FilterPipeline::shuffle_deflate(4, 6)
10025 );
10026 assert_eq!(
10027 stored_pipeline(&path, "split"),
10028 stored_pipeline(&path, "combined")
10029 );
10030
10031 let file = H5File::open(&path).unwrap();
10032 for name in ["split", "combined"] {
10033 assert_eq!(
10034 file.dataset(name).unwrap().read_raw::<i32>().unwrap(),
10035 values,
10036 "{name}"
10037 );
10038 }
10039 std::fs::remove_file(&path).ok();
10040 }
10041
10042 #[test]
10047 fn shuffle_records_the_stored_element_width() {
10048 use crate::format::messages::datatype::DatatypeMessage;
10049 use crate::format::messages::filter::FilterPipeline;
10050 let path = temp_path("shuffle_override_width");
10051 let bytes: Vec<u8> = (0..16u8).collect();
10052 {
10053 let file = H5File::create(&path).unwrap();
10054 file.new_dataset::<u8>()
10055 .datatype(DatatypeMessage::i32_type())
10056 .shape([4usize])
10057 .chunk(&[4])
10058 .shuffle()
10059 .create("d")
10060 .unwrap()
10061 .write_raw_bytes(&bytes)
10062 .unwrap();
10063 file.close().unwrap();
10064 }
10065 assert_eq!(stored_pipeline(&path, "d"), FilterPipeline::shuffle(4));
10066
10067 let file = H5File::open(&path).unwrap();
10068 assert_eq!(
10069 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
10070 bytes
10071 .chunks(4)
10072 .map(|c| i32::from_le_bytes(c.try_into().unwrap()))
10073 .collect::<Vec<_>>()
10074 );
10075 std::fs::remove_file(&path).ok();
10076 }
10077
10078 #[test]
10083 fn a_virtual_dataset_refuses_every_write() {
10084 use crate::Selection;
10085 let path = temp_path("vds_write_refused");
10086 let file = H5File::create(&path).unwrap();
10087 let ds = file
10088 .new_dataset::<i32>()
10089 .shape([16usize])
10090 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
10091 .create("vds")
10092 .unwrap();
10093 for err in [
10094 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap_err(),
10095 ds.write_slice(&[0], &[2], &[1i32, 2]).unwrap_err(),
10096 ] {
10097 let msg = err.to_string();
10098 assert!(msg.contains("virtual dataset"), "{msg}");
10099 }
10100 file.close().unwrap();
10101 std::fs::remove_file(&path).ok();
10102 }
10103
10104 #[test]
10111 fn a_printf_source_name_needs_the_mapping_shape_that_uses_it() {
10112 use crate::Selection;
10113 let path = temp_path("vds_printf");
10114 let file = H5File::create(&path).unwrap();
10115 for (f, d) in [("src_%b.h5", "src"), ("src.h5", "block_%b")] {
10116 let err = match file
10117 .new_dataset::<i32>()
10118 .shape([16usize])
10119 .virtual_mapping(Selection::All, f, d, Selection::All)
10120 .create("vds")
10121 {
10122 Ok(_) => panic!("a bounded mapping has one block, so %b names nothing"),
10123 Err(e) => e.to_string(),
10124 };
10125 assert!(err.contains("printf specifier"), "{err}");
10126 }
10127 let err = match file
10129 .new_dataset::<i32>()
10130 .shape([1usize, 2])
10131 .max_shape(&[None, Some(2)])
10132 .virtual_mapping(unlimited_rows(), "src_%z.h5", "src", Selection::All)
10133 .create("vds_bad")
10134 {
10135 Ok(_) => panic!("%z is not a legal conversion"),
10136 Err(e) => e.to_string(),
10137 };
10138 assert!(err.contains("invalid format specifier"), "{err}");
10139 file.close().unwrap();
10140 std::fs::remove_file(&path).ok();
10141 }
10142
10143 #[test]
10148 fn a_printf_mapping_stitches_one_source_per_block() {
10149 use crate::Selection;
10150 let path = temp_path("vds_printf_blocks");
10151 {
10152 let file = H5File::create(&path).unwrap();
10153 for (b, base) in [(0, 0i32), (1, 100), (3, 300)] {
10154 file.new_dataset::<i32>()
10155 .shape([2usize])
10156 .create(&format!("block{b}"))
10157 .unwrap()
10158 .write_raw(&[base, base + 1])
10159 .unwrap();
10160 }
10161 file.new_dataset::<i32>()
10162 .shape([1usize, 2])
10163 .max_shape(&[None, Some(2)])
10164 .virtual_mapping(unlimited_rows(), ".", "block%b", Selection::All)
10165 .create("vds")
10166 .unwrap();
10167 file.close().unwrap();
10168 }
10169 let file = H5File::open(&path).unwrap();
10170 let ds = file.dataset("vds").unwrap();
10171 assert_eq!(ds.shape(), vec![2, 2]);
10173 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10174 drop(file);
10175 std::fs::remove_file(&path).ok();
10176 }
10177
10178 #[test]
10184 fn a_source_that_cannot_be_opened_reads_as_the_fill_value() {
10185 use crate::{Hyperslab, HyperslabBlock, Selection};
10186 let block = |start: u64, end: u64| Selection::Hyperslab {
10187 rank: 1,
10188 form: Hyperslab::Blocks(vec![HyperslabBlock {
10189 start: vec![start],
10190 end: vec![end],
10191 }]),
10192 };
10193 let path = temp_path("vds_absent_source");
10194 {
10195 let file = H5File::create(&path).unwrap();
10196 file.new_dataset::<i32>()
10197 .shape([4usize])
10198 .create("here")
10199 .unwrap()
10200 .write_raw(&[1i32, 2, 3, 4])
10201 .unwrap();
10202 file.new_dataset::<i32>()
10203 .shape([12usize])
10204 .fill_value(-3i32)
10205 .virtual_mapping(block(0, 3), ".", "here", block(0, 3))
10206 .virtual_mapping(block(4, 7), ".", "absent", block(0, 3))
10208 .virtual_mapping(block(8, 11), "no_such_vds_source.h5", "src", block(0, 3))
10210 .create("vds")
10211 .unwrap();
10212 file.close().unwrap();
10213 }
10214 let file = H5File::open(&path).unwrap();
10215 let ds = file.dataset("vds").unwrap();
10216 assert_eq!(
10217 ds.read_raw::<i32>().unwrap(),
10218 vec![1, 2, 3, 4, -3, -3, -3, -3, -3, -3, -3, -3]
10219 );
10220 assert_eq!(
10223 ds.read_slice::<i32>(&[2], &[6]).unwrap(),
10224 vec![3, 4, -3, -3, -3, -3]
10225 );
10226 drop(file);
10227 std::fs::remove_file(&path).ok();
10228 }
10229
10230 #[test]
10234 fn an_escaped_percent_is_a_literal_in_a_source_name() {
10235 use crate::Selection;
10236 let path = temp_path("vds_escaped_pct");
10237 {
10238 let file = H5File::create(&path).unwrap();
10239 file.new_dataset::<i32>()
10240 .shape([4usize])
10241 .create("od%d")
10242 .unwrap()
10243 .write_raw(&[5i32, 6, 7, 8])
10244 .unwrap();
10245 file.new_dataset::<i32>()
10246 .shape([4usize])
10247 .virtual_mapping(Selection::All, ".", "od%%d", Selection::All)
10248 .create("vds")
10249 .unwrap();
10250 file.close().unwrap();
10251 }
10252 let file = H5File::open(&path).unwrap();
10253 let ds = file.dataset("vds").unwrap();
10254 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![5, 6, 7, 8]);
10255 assert_eq!(ds.virtual_mappings().unwrap()[0].source_dset_name, "od%%d");
10257 drop(file);
10258 std::fs::remove_file(&path).ok();
10259 }
10260
10261 #[test]
10267 fn an_unlimited_mapping_takes_its_extent_from_its_source() {
10268 let path = temp_path("vds_unlimited");
10269 {
10270 let file = H5File::create(&path).unwrap();
10271 file.new_dataset::<i32>()
10272 .shape([5usize, 2])
10273 .chunk(&[5, 2])
10274 .max_shape(&[None, Some(2)])
10275 .create("src")
10276 .unwrap()
10277 .write_raw(&(0..10i32).collect::<Vec<_>>())
10278 .unwrap();
10279 file.new_dataset::<i32>()
10280 .shape([1usize, 2])
10281 .max_shape(&[None, Some(2)])
10282 .virtual_mapping(unlimited_rows(), ".", "src", unlimited_rows())
10283 .create("vds")
10284 .unwrap();
10285 file.close().unwrap();
10286 }
10287 let file = H5File::open(&path).unwrap();
10288 let ds = file.dataset("vds").unwrap();
10289 assert_eq!(ds.shape(), vec![5, 2]);
10290 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..10).collect::<Vec<i32>>());
10291 drop(file);
10292 std::fs::remove_file(&path).ok();
10293 }
10294
10295 fn printf_gap_file(tag: &str) -> std::path::PathBuf {
10299 use crate::Selection;
10300 let path = temp_path(tag);
10301 let file = H5File::create(&path).unwrap();
10302 for (b, base) in [(0, 0i32), (1, 100), (3, 300)] {
10303 file.new_dataset::<i32>()
10304 .shape([2usize])
10305 .create(&format!("block{b}"))
10306 .unwrap()
10307 .write_raw(&[base, base + 1])
10308 .unwrap();
10309 }
10310 file.new_dataset::<i32>()
10311 .shape([1usize, 2])
10312 .max_shape(&[None, Some(2)])
10313 .fill_value(-7i32)
10314 .virtual_mapping(unlimited_rows(), ".", "block%b", Selection::All)
10315 .create("vds")
10316 .unwrap();
10317 file.close().unwrap();
10318 path
10319 }
10320
10321 #[test]
10327 fn a_printf_gap_looks_past_the_missing_block() {
10328 use crate::DatasetAccess;
10329 let path = printf_gap_file("vds_printf_gap");
10330 let file = H5File::open(&path).unwrap();
10331 for (gap, shape, data) in [
10332 (0u64, vec![2usize, 2], vec![0i32, 1, 100, 101]),
10333 (1, vec![4, 2], vec![0, 1, 100, 101, -7, -7, 300, 301]),
10334 (2, vec![4, 2], vec![0, 1, 100, 101, -7, -7, 300, 301]),
10335 ] {
10336 let ds = file
10337 .dataset_with("vds", DatasetAccess::new().virtual_printf_gap(gap))
10338 .unwrap();
10339 assert_eq!(ds.shape(), shape, "gap {gap}");
10340 assert_eq!(ds.read_raw::<i32>().unwrap(), data, "gap {gap}");
10341 }
10342 let ds = file.dataset("vds").unwrap();
10345 assert_eq!(ds.shape(), vec![2, 2]);
10346 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10347 drop(file);
10348 std::fs::remove_file(&path).ok();
10349 }
10350
10351 #[test]
10360 fn a_relative_source_resolves_next_to_the_virtual_dataset() {
10361 use crate::Selection;
10362 let dir = std::env::temp_dir().join(format!(
10363 "rust_hdf5_vds_beside_{}_{:?}",
10364 std::process::id(),
10365 std::thread::current().id()
10366 ));
10367 std::fs::create_dir_all(&dir).unwrap();
10368 {
10369 let file = H5File::create(dir.join("src.h5")).unwrap();
10370 file.new_dataset::<i32>()
10371 .shape([2usize, 4])
10372 .create("data")
10373 .unwrap()
10374 .write_raw(&(0..8i32).collect::<Vec<_>>())
10375 .unwrap();
10376 file.close().unwrap();
10377 }
10378 {
10379 let file = H5File::create(dir.join("v.h5")).unwrap();
10380 file.new_dataset::<i32>()
10381 .shape([2usize, 4])
10382 .fill_value(-9i32)
10383 .virtual_mapping(Selection::All, "src.h5", "data", Selection::All)
10386 .create("v")
10387 .unwrap();
10388 file.close().unwrap();
10389 }
10390 assert_ne!(std::env::current_dir().unwrap(), dir);
10393 let file = H5File::open(dir.join("v.h5")).unwrap();
10394 let ds = file.dataset("v").unwrap();
10395 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..8i32).collect::<Vec<_>>());
10396 drop(file);
10397 std::fs::remove_dir_all(&dir).ok();
10398 }
10399
10400 #[test]
10412 fn the_first_open_of_a_virtual_dataset_fixes_the_properties_for_later_opens() {
10413 use crate::DatasetAccess;
10414 let path = printf_gap_file("vds_printf_first_open");
10415 let file = H5File::open(&path).unwrap();
10416 let gap = |g: u64| DatasetAccess::new().virtual_printf_gap(g);
10417 {
10418 let a = file.dataset_with("vds", gap(0)).unwrap();
10419 let b = file.dataset_with("vds", gap(1)).unwrap();
10420 assert_eq!(a.shape(), vec![2, 2]);
10421 assert_eq!(b.shape(), vec![2, 2]);
10422 assert_eq!(b.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10423 }
10424 {
10425 let c = file.dataset_with("vds", gap(1)).unwrap();
10426 let d = file.dataset_with("vds", gap(0)).unwrap();
10427 assert_eq!(c.shape(), vec![4, 2]);
10428 assert_eq!(d.shape(), vec![4, 2]);
10429 assert_eq!(
10430 d.read_raw::<i32>().unwrap(),
10431 vec![0, 1, 100, 101, -7, -7, 300, 301]
10432 );
10433 }
10434 let e = file.dataset_with("vds", gap(0)).unwrap();
10435 assert_eq!(e.shape(), vec![2, 2]);
10436 assert_eq!(e.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10437 drop(e);
10438 drop(file);
10439 std::fs::remove_file(&path).ok();
10440 }
10441
10442 #[test]
10447 fn the_first_missing_view_ignores_the_printf_gap() {
10448 use crate::{DatasetAccess, VirtualView};
10449 let path = printf_gap_file("vds_printf_first_missing");
10450 let file = H5File::open(&path).unwrap();
10451 for gap in [0u64, 2] {
10452 let ds = file
10453 .dataset_with(
10454 "vds",
10455 DatasetAccess::new()
10456 .virtual_view(VirtualView::FirstMissing)
10457 .virtual_printf_gap(gap),
10458 )
10459 .unwrap();
10460 assert_eq!(ds.shape(), vec![2, 2], "gap {gap}");
10461 assert_eq!(
10462 ds.read_raw::<i32>().unwrap(),
10463 vec![0, 1, 100, 101],
10464 "gap {gap}"
10465 );
10466 }
10467 drop(file);
10468 std::fs::remove_file(&path).ok();
10469 }
10470
10471 #[test]
10479 fn the_view_decides_whether_a_trailing_gap_is_inside_the_extent() {
10480 use crate::format::selection::UNLIMITED;
10481 use crate::{DatasetAccess, Hyperslab, RegularHyperslab, Selection, VirtualView};
10482 let strided = || Selection::Hyperslab {
10483 rank: 2,
10484 form: Hyperslab::Regular(RegularHyperslab {
10485 start: vec![0, 0],
10486 stride: vec![3, 1],
10487 count: vec![UNLIMITED, 1],
10488 block: vec![2, 2],
10489 }),
10490 };
10491 let path = temp_path("vds_view_trail");
10492 {
10493 let file = H5File::create(&path).unwrap();
10494 file.new_dataset::<i32>()
10495 .shape([3usize, 2])
10496 .max_shape(&[None, Some(2)])
10497 .chunk(&[1, 2])
10498 .create("src")
10499 .unwrap()
10500 .write_raw(&(0..6i32).collect::<Vec<_>>())
10501 .unwrap();
10502 file.new_dataset::<i32>()
10503 .shape([1usize, 2])
10504 .max_shape(&[None, Some(2)])
10505 .fill_value(-9i32)
10506 .virtual_mapping(strided(), ".", "src", strided())
10507 .create("vds")
10508 .unwrap();
10509 file.close().unwrap();
10510 }
10511 let file = H5File::open(&path).unwrap();
10512 {
10513 let last = file.dataset("vds").unwrap();
10514 assert_eq!(last.shape(), vec![2, 2]);
10515 assert_eq!(last.read_raw::<i32>().unwrap(), vec![0, 1, 2, 3]);
10516 }
10517 let first = file
10519 .dataset_with(
10520 "vds",
10521 DatasetAccess::new().virtual_view(VirtualView::FirstMissing),
10522 )
10523 .unwrap();
10524 assert_eq!(first.shape(), vec![3, 2]);
10525 assert_eq!(first.read_raw::<i32>().unwrap(), vec![0, 1, 2, 3, -9, -9]);
10526 drop(file);
10527 std::fs::remove_file(&path).ok();
10528 }
10529
10530 #[test]
10535 fn the_access_property_list_reads_back_and_refuses_hsize_undef() {
10536 use crate::{DatasetAccess, VirtualView};
10537 let plist = DatasetAccess::new();
10538 assert_eq!(plist.view(), VirtualView::LastAvailable);
10539 assert_eq!(plist.printf_gap(), 0);
10540 let set = plist
10541 .virtual_view(VirtualView::FirstMissing)
10542 .virtual_printf_gap(4);
10543 assert_eq!(set.view(), VirtualView::FirstMissing);
10544 assert_eq!(set.printf_gap(), 4);
10547
10548 let path = printf_gap_file("vds_printf_gap_undef");
10549 let file = H5File::open(&path).unwrap();
10550 let err = match file.dataset_with("vds", DatasetAccess::new().virtual_printf_gap(u64::MAX))
10551 {
10552 Ok(_) => panic!("HSIZE_UNDEF is not a valid printf gap size"),
10553 Err(e) => e.to_string(),
10554 };
10555 assert!(err.contains("HSIZE_UNDEF"), "{err}");
10556 drop(file);
10557 std::fs::remove_file(&path).ok();
10558 }
10559
10560 fn unlimited_rows() -> crate::Selection {
10563 use crate::format::selection::UNLIMITED;
10564 use crate::{Hyperslab, RegularHyperslab, Selection};
10565 Selection::Hyperslab {
10566 rank: 2,
10567 form: Hyperslab::Regular(RegularHyperslab {
10568 start: vec![0, 0],
10569 stride: vec![1, 1],
10570 count: vec![UNLIMITED, 1],
10571 block: vec![1, 2],
10572 }),
10573 }
10574 }
10575
10576 #[test]
10581 fn an_unlimited_virtual_selection_over_a_limited_source_is_refused() {
10582 use crate::Selection;
10583 let path = temp_path("vds_unlim_limited_src");
10584 let file = H5File::create(&path).unwrap();
10585 let err = match file
10586 .new_dataset::<i32>()
10587 .shape([1usize, 2])
10588 .max_shape(&[None, Some(2)])
10589 .virtual_mapping(unlimited_rows(), "src.h5", "src", Selection::All)
10590 .create("vds")
10591 {
10592 Ok(_) => panic!("no printf substitution names the mapping's later blocks"),
10593 Err(e) => e.to_string(),
10594 };
10595 assert!(err.contains("printf"), "{err}");
10596 file.close().unwrap();
10597 std::fs::remove_file(&path).ok();
10598 }
10599
10600 #[test]
10603 fn a_virtual_dataset_cannot_also_be_chunked_or_external() {
10604 use crate::Selection;
10605 let path = temp_path("vds_exclusive");
10606 let file = H5File::create(&path).unwrap();
10607 let builder = || {
10608 file.new_dataset::<i32>().shape([16usize]).virtual_mapping(
10609 Selection::All,
10610 "src.h5",
10611 "src",
10612 Selection::All,
10613 )
10614 };
10615 for (which, res) in [
10616 ("chunked", builder().chunk(&[4]).create("a")),
10617 ("compact", builder().compact().create("b")),
10618 (
10619 "external",
10620 builder().external(&[("x.raw", 0, 64)]).create("c"),
10621 ),
10622 ("references", builder().object_references().create("d")),
10623 ] {
10624 match res {
10625 Ok(_) => panic!("a virtual dataset cannot also be {which}"),
10626 Err(e) => assert!(e.to_string().contains("virtual dataset"), "{which}: {e}"),
10627 }
10628 }
10629 file.close().unwrap();
10630 std::fs::remove_file(&path).ok();
10631 }
10632
10633 #[test]
10638 fn deleting_a_virtual_dataset_frees_its_mapping_list() {
10639 use crate::Selection;
10640 let path = temp_path("vds_delete");
10641 let baseline = {
10642 let file = H5File::create(&path).unwrap();
10643 file.new_dataset::<i32>()
10644 .shape([16usize])
10645 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
10646 .create("vds")
10647 .unwrap();
10648 file.delete_dataset("vds").unwrap();
10649 file.close().unwrap();
10650 std::fs::metadata(&path).unwrap().len()
10651 };
10652 std::fs::remove_file(&path).ok();
10653
10654 let file = H5File::create(&path).unwrap();
10658 for i in 0..10 {
10659 let name = format!("vds{i}");
10660 file.new_dataset::<i32>()
10661 .shape([16usize])
10662 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
10663 .create(&name)
10664 .unwrap();
10665 file.delete_dataset(&name).unwrap();
10666 }
10667 file.close().unwrap();
10668 assert_eq!(std::fs::metadata(&path).unwrap().len(), baseline);
10669 std::fs::remove_file(&path).ok();
10670 }
10671
10672 #[test]
10675 fn storage_layout_reports_each_class() {
10676 use crate::StorageLayout;
10677 let path = temp_path("storage_layout");
10678 {
10679 let file = H5File::create(&path).unwrap();
10680 file.new_dataset::<i32>()
10681 .shape([4usize])
10682 .create("contig")
10683 .unwrap();
10684 file.new_dataset::<i32>()
10685 .shape([4usize])
10686 .compact()
10687 .create("compact")
10688 .unwrap();
10689 file.new_dataset::<i32>()
10690 .shape([8usize])
10691 .chunk(&[4])
10692 .create("chunked")
10693 .unwrap();
10694 file.close().unwrap();
10695 }
10696 let file = H5File::open(&path).unwrap();
10697 assert_eq!(
10698 file.dataset("contig").unwrap().storage_layout().unwrap(),
10699 StorageLayout::Contiguous
10700 );
10701 assert_eq!(
10702 file.dataset("compact").unwrap().storage_layout().unwrap(),
10703 StorageLayout::Compact
10704 );
10705 assert_eq!(
10706 file.dataset("chunked").unwrap().storage_layout().unwrap(),
10707 StorageLayout::Chunked
10708 );
10709 }
10710
10711 #[test]
10713 fn storage_layout_errors_in_write_mode() {
10714 let path = temp_path("storage_layout_write_mode");
10715 let file = H5File::create(&path).unwrap();
10716 let ds = file
10717 .new_dataset::<i32>()
10718 .shape([4usize])
10719 .create("data")
10720 .unwrap();
10721 assert!(ds.storage_layout().is_err());
10722 file.close().unwrap();
10723 }
10724
10725 #[test]
10730 fn chunk_index_reports_the_stored_kind() {
10731 use crate::ChunkIndex;
10732 let path = temp_path("chunk_index");
10733 {
10734 let file = H5File::create(&path).unwrap();
10735 file.new_dataset::<i32>()
10736 .shape([4usize])
10737 .create("contig")
10738 .unwrap();
10739 file.new_dataset::<i32>()
10740 .shape([16usize])
10741 .chunk(&[4])
10742 .max_shape(&[None])
10743 .create("earray")
10744 .unwrap();
10745 file.set_libver_latest(false).unwrap();
10746 file.new_dataset::<i32>()
10747 .shape([8usize])
10748 .chunk(&[4])
10749 .max_shape(&[None])
10750 .create("btree1")
10751 .unwrap();
10752 file.close().unwrap();
10753 }
10754 let file = H5File::open(&path).unwrap();
10755 assert_eq!(file.dataset("contig").unwrap().chunk_index().unwrap(), None);
10756 assert_eq!(
10757 file.dataset("earray").unwrap().chunk_index().unwrap(),
10758 Some(ChunkIndex::ExtensibleArray)
10759 );
10760 assert_eq!(
10761 file.dataset("btree1").unwrap().chunk_index().unwrap(),
10762 Some(ChunkIndex::BtreeV1)
10763 );
10764 }
10765
10766 #[test]
10770 fn filters_reports_the_stored_pipeline() {
10771 use crate::format::messages::filter::{FILTER_DEFLATE, FILTER_SHUFFLE, FLAG_OPTIONAL};
10772 let path = temp_path("filters");
10773 {
10774 let file = H5File::create(&path).unwrap();
10775 file.new_dataset::<i32>()
10776 .shape([16usize])
10777 .create("unfiltered")
10778 .unwrap();
10779 file.new_dataset::<i32>()
10780 .shape([16usize])
10781 .chunk(&[4])
10782 .shuffle()
10783 .deflate(6)
10784 .create("filtered")
10785 .unwrap();
10786 file.close().unwrap();
10787 }
10788 let file = H5File::open(&path).unwrap();
10789 assert_eq!(
10790 file.dataset("unfiltered").unwrap().filters().unwrap(),
10791 Vec::new()
10792 );
10793 let filters = file.dataset("filtered").unwrap().filters().unwrap();
10794 assert_eq!(filters.len(), 2);
10795 assert_eq!(filters[0].id, FILTER_SHUFFLE);
10796 assert_eq!(filters[0].flags, FLAG_OPTIONAL);
10797 assert_eq!(filters[1].id, FILTER_DEFLATE);
10798 assert_eq!(filters[1].cd_values, vec![6]);
10799 }
10800
10801 #[test]
10807 fn fill_value_reports_the_stored_value() {
10808 use crate::FillValue;
10809 let path = temp_path("fill_value");
10810 {
10811 let file = H5File::create(&path).unwrap();
10812 file.new_dataset::<i32>()
10813 .shape([4usize])
10814 .create("unset")
10815 .unwrap();
10816 file.new_dataset::<i32>()
10817 .shape([4usize])
10818 .fill_value(-7i32)
10819 .create("set")
10820 .unwrap();
10821 file.close().unwrap();
10822 }
10823 let file = H5File::open(&path).unwrap();
10824 assert_eq!(
10825 file.dataset("unset").unwrap().fill_value().unwrap(),
10826 FillValue::Default
10827 );
10828 assert_eq!(
10829 file.dataset("set").unwrap().fill_value().unwrap(),
10830 FillValue::UserDefined((-7i32).to_le_bytes().to_vec())
10831 );
10832 }
10833
10834 #[test]
10839 fn the_unlimited_external_slot_keeps_its_three_rules() {
10840 use crate::format::messages::external_file_list::UNLIMITED;
10841 let path = temp_path("efl_unlim_rules");
10842 let file = H5File::create(&path).unwrap();
10843
10844 let err = match file
10847 .new_dataset::<i32>()
10848 .shape([8usize])
10849 .external(&[("a.raw", 0, UNLIMITED), ("b.raw", 0, 32)])
10850 .create("mid")
10851 {
10852 Ok(_) => panic!("an unlimited slot absorbs everything behind it"),
10853 Err(e) => e.to_string(),
10854 };
10855 assert!(err.contains("only be the last"), "{err}");
10856
10857 let err = match file
10859 .new_dataset::<i32>()
10860 .shape([8usize])
10861 .max_shape(&[None])
10862 .external(&[("a.raw", 0, 32)])
10863 .create("finite")
10864 {
10865 Ok(_) => panic!("no finite reservation covers an unlimited extent"),
10866 Err(e) => e.to_string(),
10867 };
10868 assert!(err.contains("unlimited dataspace"), "{err}");
10869
10870 let err = match file
10872 .new_dataset::<i32>()
10873 .shape([2usize, 4])
10874 .max_shape(&[Some(2), None])
10875 .external(&[("a.raw", 0, UNLIMITED)])
10876 .create("dim1")
10877 {
10878 Ok(_) => panic!("only the slowest-varying dimension may be extendible"),
10879 Err(e) => e.to_string(),
10880 };
10881 assert!(err.contains("only the first dimension"), "{err}");
10882
10883 file.new_dataset::<i32>()
10886 .shape([8usize])
10887 .max_shape(&[None])
10888 .external(&[("a.raw", 0, 16), ("b.raw", 0, UNLIMITED)])
10889 .create("ok")
10890 .unwrap()
10891 .write_raw(&(0..8i32).collect::<Vec<_>>())
10892 .unwrap();
10893 file.close().unwrap();
10894 std::fs::remove_file(&path).ok();
10895 for raw in ["a.raw", "b.raw"] {
10896 std::fs::remove_file(raw).ok();
10897 }
10898 }
10899
10900 #[test]
10905 fn an_unlimited_external_slot_reads_zero_past_the_end_of_its_file() {
10906 use crate::format::messages::external_file_list::UNLIMITED;
10907 let dir = std::env::temp_dir().join(format!("rh5_efl_short_{}", std::process::id()));
10908 std::fs::create_dir_all(&dir).unwrap();
10909 let path = dir.join("f.h5");
10910 let raw = dir.join("short.raw");
10911 std::fs::write(&raw, [0u8; 16]).unwrap();
10913 {
10914 let file = H5File::create(&path).unwrap();
10915 file.new_dataset::<i32>()
10916 .shape([8usize])
10917 .max_shape(&[None])
10918 .external(&[(raw.to_str().unwrap(), 0, UNLIMITED)])
10919 .create("data")
10920 .unwrap();
10921 file.close().unwrap();
10922 }
10923 let file = H5File::open(&path).unwrap();
10924 assert_eq!(
10925 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
10926 vec![0i32; 8]
10927 );
10928 drop(file);
10929 std::fs::remove_dir_all(&dir).ok();
10930 }
10931
10932 #[test]
10936 fn external_files_reports_the_stored_segments() {
10937 let path = temp_path("external_files");
10938 {
10939 let file = H5File::create(&path).unwrap();
10940 file.new_dataset::<i32>()
10941 .shape([4usize])
10942 .create("contig")
10943 .unwrap();
10944 file.new_dataset::<i32>()
10945 .shape([16usize])
10946 .external(&[("a.raw", 0, 32), ("b.raw", 8, 32)])
10947 .create("external")
10948 .unwrap();
10949 file.close().unwrap();
10950 }
10951 let file = H5File::open(&path).unwrap();
10952 assert_eq!(
10953 file.dataset("contig").unwrap().external_files().unwrap(),
10954 Vec::new()
10955 );
10956 let segments = file.dataset("external").unwrap().external_files().unwrap();
10957 assert_eq!(segments.len(), 2);
10958 assert_eq!(segments[0].name, "a.raw");
10959 assert_eq!(segments[0].offset, 0);
10960 assert_eq!(segments[0].size, 32);
10961 assert_eq!(segments[1].name, "b.raw");
10962 assert_eq!(segments[1].offset, 8);
10963 assert_eq!(segments[1].size, 32);
10964 }
10965
10966 #[test]
10971 fn max_shape_reports_unlimited_and_fixed_axes() {
10972 let path = temp_path("max_shape");
10973 {
10974 let file = H5File::create(&path).unwrap();
10975 file.new_dataset::<i32>()
10976 .shape([4usize, 8])
10977 .create("fixed")
10978 .unwrap();
10979 file.new_dataset::<i32>()
10980 .shape([4usize, 8])
10981 .chunk(&[2, 8])
10982 .max_shape(&[None, Some(8)])
10983 .create("unlimited")
10984 .unwrap();
10985 file.close().unwrap();
10986 }
10987 let file = H5File::open(&path).unwrap();
10988 assert_eq!(
10989 file.dataset("fixed").unwrap().max_shape().unwrap(),
10990 vec![Some(4), Some(8)]
10991 );
10992 assert_eq!(
10993 file.dataset("unlimited").unwrap().max_shape().unwrap(),
10994 vec![None, Some(8)]
10995 );
10996 }
10997
10998 #[test]
11002 fn virtual_mappings_reports_the_stored_mappings() {
11003 use crate::Selection;
11004 let path = temp_path("virtual_mappings");
11005 {
11006 let file = H5File::create(&path).unwrap();
11007 file.new_dataset::<i32>()
11008 .shape([4usize])
11009 .create("plain")
11010 .unwrap();
11011 file.new_dataset::<i32>()
11012 .shape([16usize])
11013 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
11014 .create("vds")
11015 .unwrap();
11016 file.close().unwrap();
11017 }
11018 let file = H5File::open(&path).unwrap();
11019 assert_eq!(
11020 file.dataset("plain").unwrap().virtual_mappings().unwrap(),
11021 Vec::new()
11022 );
11023 let mappings = file.dataset("vds").unwrap().virtual_mappings().unwrap();
11024 assert_eq!(mappings.len(), 1);
11025 assert_eq!(mappings[0].source_file_name, "src.h5");
11026 assert_eq!(mappings[0].source_dset_name, "src");
11027 assert_eq!(mappings[0].source_selection, Selection::All);
11028 assert_eq!(mappings[0].virtual_selection, Selection::All);
11029 }
11030}