1use std::borrow::Cow;
8
9use crate::attribute::AttrBuilder;
10use crate::error::{Hdf5Error, Result};
11use crate::file::{
12 borrow_inner, borrow_inner_mut, clone_inner, same_inner, H5FileInner, SharedInner,
13};
14use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
15use crate::format::messages::filter::Filter;
16use crate::format::messages::virtual_mapping::VirtualMapping;
17use crate::format::reference::{Reference, ReferenceTarget};
18use crate::format::selection::check_hyperslab;
19use crate::format::selection::Selection;
20use crate::format::storage_kind::AttributeStorage;
21use crate::io::file_handle::ReadDst;
22use crate::io::reader::{read_image_into_new, ExternalFileSegment};
23use crate::io::writer::ChunkIndexKind;
24use crate::types::H5Type;
25
26pub struct DatasetBuilder<T: H5Type> {
43 file_inner: SharedInner,
44 shape: Option<Vec<usize>>,
45 is_null: bool,
46 chunk_dims: Option<Vec<usize>>,
47 max_shape: Option<Vec<Option<usize>>>,
48 is_compact: bool,
49 early_allocation: bool,
50 deflate_level: Option<u32>,
51 shuffle: bool,
52 custom_pipeline: Option<crate::format::messages::filter::FilterPipeline>,
53 group_path: Option<String>,
54 fill_value: Option<Vec<u8>>,
55 fill_time: Option<FillTime>,
56 datatype_override: Option<crate::format::messages::datatype::DatatypeMessage>,
57 committed_type: Option<String>,
58 references: Option<ReferenceElement>,
59 external: Option<Vec<(String, u64, u64)>>,
60 efile_prefix: Option<String>,
61 virtual_mappings: Vec<VirtualMapping>,
62 _marker: std::marker::PhantomData<T>,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76enum ReferenceElement {
77 Object,
79 Region,
81 Revised,
86}
87
88impl ReferenceElement {
89 fn datatype(
91 self,
92 ctx: &crate::format::FormatContext,
93 ) -> crate::format::messages::datatype::DatatypeMessage {
94 use crate::format::messages::datatype::DatatypeMessage;
95 match self {
96 Self::Object => DatatypeMessage::object_reference(ctx),
97 Self::Region => DatatypeMessage::region_reference(ctx),
98 Self::Revised => DatatypeMessage::std_object_reference(ctx),
99 }
100 }
101}
102
103impl<T: H5Type> DatasetBuilder<T> {
104 pub(crate) fn new(file_inner: SharedInner) -> Self {
105 Self {
106 file_inner,
107 shape: None,
108 is_null: false,
109 chunk_dims: None,
110 max_shape: None,
111 is_compact: false,
112 early_allocation: false,
113 deflate_level: None,
114 shuffle: false,
115 custom_pipeline: None,
116 group_path: None,
117 fill_value: None,
118 fill_time: None,
119 datatype_override: None,
120 committed_type: None,
121 references: None,
122 external: None,
123 efile_prefix: None,
124 virtual_mappings: Vec::new(),
125 _marker: std::marker::PhantomData,
126 }
127 }
128
129 pub(crate) fn new_in_group(file_inner: SharedInner, group_path: String) -> Self {
130 Self {
131 file_inner,
132 shape: None,
133 is_null: false,
134 chunk_dims: None,
135 max_shape: None,
136 is_compact: false,
137 early_allocation: false,
138 deflate_level: None,
139 shuffle: false,
140 custom_pipeline: None,
141 group_path: Some(group_path),
142 fill_value: None,
143 fill_time: None,
144 datatype_override: None,
145 committed_type: None,
146 references: None,
147 external: None,
148 efile_prefix: None,
149 virtual_mappings: Vec::new(),
150 _marker: std::marker::PhantomData,
151 }
152 }
153
154 #[must_use]
160 pub fn shape<S: AsRef<[usize]>>(mut self, dims: S) -> Self {
161 self.shape = Some(dims.as_ref().to_vec());
162 self
163 }
164
165 #[must_use]
167 pub fn scalar(mut self) -> Self {
168 self.shape = Some(vec![]);
169 self
170 }
171
172 #[must_use]
180 pub fn null(mut self) -> Self {
181 self.is_null = true;
182 self
183 }
184
185 #[must_use]
191 pub fn chunk(mut self, chunk_dims: &[usize]) -> Self {
192 self.chunk_dims = Some(chunk_dims.to_vec());
193 self
194 }
195
196 #[must_use]
200 pub fn resizable(mut self) -> Self {
201 self.max_shape = Some(vec![None; self.shape.as_ref().map_or(0, |s| s.len())]);
202 self
203 }
204
205 #[must_use]
207 pub fn max_shape(mut self, max: &[Option<usize>]) -> Self {
208 self.max_shape = Some(max.to_vec());
209 self
210 }
211
212 #[must_use]
234 pub fn compact(mut self) -> Self {
235 self.is_compact = true;
236 self
237 }
238
239 #[must_use]
267 pub fn early_allocation(mut self) -> Self {
268 self.early_allocation = true;
269 self
270 }
271
272 #[must_use]
277 pub fn deflate(mut self, level: u32) -> Self {
278 self.deflate_level = Some(level);
279 self
280 }
281
282 #[must_use]
297 pub fn shuffle(mut self) -> Self {
298 self.shuffle = true;
299 self
300 }
301
302 #[must_use]
309 pub fn shuffle_deflate(mut self, level: u32) -> Self {
310 self.shuffle = true;
311 self.deflate_level = Some(level);
312 self
313 }
314
315 #[must_use]
319 pub fn zstd(mut self, level: u32) -> Self {
320 self.custom_pipeline = Some(crate::format::messages::filter::FilterPipeline::zstd(level));
321 self
322 }
323
324 #[must_use]
329 pub fn filter_pipeline(
330 mut self,
331 pipeline: crate::format::messages::filter::FilterPipeline,
332 ) -> Self {
333 self.custom_pipeline = Some(pipeline);
334 self
335 }
336
337 #[must_use]
350 pub fn datatype(mut self, dt: crate::format::messages::datatype::DatatypeMessage) -> Self {
351 self.datatype_override = Some(dt);
352 self
353 }
354
355 #[must_use]
380 pub fn committed_type(mut self, path: &str) -> Self {
381 self.committed_type = Some(path.to_string());
382 self
383 }
384
385 #[must_use]
406 pub fn object_references(mut self) -> Self {
407 self.references = Some(ReferenceElement::Object);
408 self
409 }
410
411 #[must_use]
432 pub fn std_object_references(mut self) -> Self {
433 self.references = Some(ReferenceElement::Revised);
434 self
435 }
436
437 #[must_use]
467 pub fn std_region_references(self) -> Self {
468 self.std_object_references()
469 }
470
471 #[must_use]
494 pub fn attribute_references(self) -> Self {
495 self.std_object_references()
496 }
497
498 #[must_use]
524 pub fn region_references(mut self) -> Self {
525 self.references = Some(ReferenceElement::Region);
526 self
527 }
528
529 #[must_use]
563 pub fn external(mut self, files: &[(&str, u64, u64)]) -> Self {
564 self.external = Some(
565 files
566 .iter()
567 .map(|&(name, offset, size)| (name.to_string(), offset, size))
568 .collect(),
569 );
570 self
571 }
572
573 #[must_use]
598 pub fn efile_prefix(mut self, prefix: impl Into<String>) -> Self {
599 self.efile_prefix = Some(prefix.into());
600 self
601 }
602
603 #[must_use]
649 pub fn virtual_mapping(
650 mut self,
651 virtual_selection: Selection,
652 source_file: &str,
653 source_dataset: &str,
654 source_selection: Selection,
655 ) -> Self {
656 self.virtual_mappings.push(VirtualMapping {
657 source_file_name: source_file.to_string(),
658 source_dset_name: source_dataset.to_string(),
659 source_selection,
660 virtual_selection,
661 });
662 self
663 }
664
665 #[must_use]
682 pub fn fill_value(mut self, value: T) -> Self {
683 let es = T::element_size();
684 let raw = unsafe { std::slice::from_raw_parts(&value as *const T as *const u8, es) };
688 self.fill_value = Some(raw.to_vec());
689 self
690 }
691
692 #[must_use]
701 pub fn fill_value_bytes(mut self, bytes: &[u8]) -> Self {
702 self.fill_value = Some(bytes.to_vec());
703 self
704 }
705
706 #[must_use]
728 pub fn fill_time(mut self, time: FillTime) -> Self {
729 self.fill_time = Some(time);
730 self
731 }
732
733 pub fn create(self, name: &str) -> Result<H5Dataset> {
738 let committed = self.resolve_committed_type()?;
744 let file_inner = clone_inner(&self.file_inner);
745 let ds = self.create_object(name, committed.as_ref().map(|(_, dt)| dt.clone()))?;
746 if let (Some((share, _)), DatasetInfo::Writer { index, .. }) = (committed, &ds.info) {
747 let inner = borrow_inner(&file_inner);
748 if let H5FileInner::Writer(writer) = &*inner {
749 writer.share_committed_type(*index, share);
750 }
751 }
752 Ok(ds)
753 }
754
755 fn resolve_committed_type(&self) -> Result<Option<(usize, DatatypeMessage)>> {
759 let Some(path) = self.committed_type.as_deref() else {
760 return Ok(None);
761 };
762 if self.references.is_some() {
763 return Err(Hdf5Error::InvalidState(
769 "a dataset cannot be built on a committed datatype and hold references".into(),
770 ));
771 }
772 let inner = borrow_inner(&self.file_inner);
773 match &*inner {
774 H5FileInner::Writer(writer) => Ok(Some(writer.committed_datatype_for_share(path)?)),
775 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
776 "cannot create a dataset in read mode".into(),
777 )),
778 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
779 }
780 }
781
782 fn create_object(self, name: &str, committed: Option<DatatypeMessage>) -> Result<H5Dataset> {
785 let full_name = if let Some(ref gp) = self.group_path {
787 if gp == "/" {
788 name.to_string()
789 } else {
790 let trimmed = gp.trim_start_matches('/');
791 format!("{}/{}", trimmed, name)
792 }
793 } else {
794 name.to_string()
795 };
796
797 let datatype = if let Some(kind) = self.references {
798 let inner = borrow_inner(&self.file_inner);
801 match &*inner {
802 H5FileInner::Writer(writer) => kind.datatype(writer.ctx()),
803 H5FileInner::Reader(_) => {
804 return Err(Hdf5Error::InvalidState(
805 "cannot create a dataset in read mode".into(),
806 ))
807 }
808 H5FileInner::Closed => {
809 return Err(Hdf5Error::InvalidState("file is closed".into()))
810 }
811 }
812 } else if let Some(dt) = committed {
813 dt
817 } else {
818 self.datatype_override.clone().unwrap_or_else(T::hdf5_type)
819 };
820 let element_size = datatype.element_size() as usize;
827 let fill_value = match self.fill_value.as_deref() {
833 Some(bytes) if bytes.len() != element_size => {
834 return Err(Hdf5Error::TypeMismatch(format!(
835 "fill value is {} bytes but datatype {datatype} stores {element_size}",
836 bytes.len()
837 )))
838 }
839 Some(bytes) => Some(to_stored_byte_order(bytes, &datatype, element_size)?.into_owned()),
840 None => None,
841 };
842
843 let wants_filter =
844 self.custom_pipeline.is_some() || self.shuffle || self.deflate_level.is_some();
845
846 if self.external.is_some() {
851 if self.chunk_dims.is_some() || wants_filter || self.is_compact || self.is_null {
852 return Err(Hdf5Error::InvalidState(
853 "a dataset whose raw data lives in external files is contiguous, so it \
854 cannot also be chunked, filtered, compact or NULL"
855 .into(),
856 ));
857 }
858 if self.references.is_some() {
859 return Err(Hdf5Error::InvalidState(
860 "object and region references are stamped into the dataset's own \
861 contiguous block, which a dataset stored in external files has none of"
862 .into(),
863 ));
864 }
865 }
866
867 if !self.virtual_mappings.is_empty() {
872 if self.chunk_dims.is_some()
873 || wants_filter
874 || self.is_compact
875 || self.is_null
876 || self.external.is_some()
877 {
878 return Err(Hdf5Error::InvalidState(
879 "a virtual dataset's elements live in the datasets its mappings name, \
880 so it cannot also be chunked, filtered, compact, NULL or stored in \
881 external files"
882 .into(),
883 ));
884 }
885 if self.references.is_some() {
886 return Err(Hdf5Error::InvalidState(
887 "object and region references are stamped into the dataset's own \
888 contiguous block, which a virtual dataset has none of"
889 .into(),
890 ));
891 }
892 }
893
894 if self.is_null {
895 if self.chunk_dims.is_some() || wants_filter || self.is_compact {
901 return Err(Hdf5Error::InvalidState(
902 "a NULL dataspace dataset cannot be chunked, filtered or compact".into(),
903 ));
904 }
905 if fill_value.is_some() {
906 return Err(Hdf5Error::InvalidState(
907 "a NULL dataspace dataset cannot have a fill value".into(),
908 ));
909 }
910 if self.fill_time.is_some() {
911 return Err(Hdf5Error::InvalidState(
912 "a NULL dataspace dataset cannot have a fill time".into(),
913 ));
914 }
915
916 let index = {
917 let inner = borrow_inner(&self.file_inner);
918 match &*inner {
919 H5FileInner::Writer(writer) => {
920 let idx = writer.create_null_dataset(&full_name, datatype)?;
921 if let Some(ref gp) = self.group_path {
922 if gp != "/" {
923 writer.assign_dataset_to_group(gp, idx)?;
924 }
925 }
926 idx
927 }
928 H5FileInner::Reader(_) => {
929 return Err(Hdf5Error::InvalidState(
930 "cannot create a dataset in read mode".into(),
931 ));
932 }
933 H5FileInner::Closed => {
934 return Err(Hdf5Error::InvalidState("file is closed".into()));
935 }
936 }
937 };
938
939 return Ok(H5Dataset {
940 file_inner: clone_inner(&self.file_inner),
941 info: DatasetInfo::Writer {
942 index,
943 shape: Vec::new(),
944 element_size,
945 chunk_index: None,
946 is_null: true,
947 },
948 _open: None,
949 });
950 }
951
952 let shape = self.shape.ok_or_else(|| {
953 Hdf5Error::InvalidState("shape must be set before calling create()".into())
954 })?;
955 let dims_u64: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
956
957 if self.is_compact {
958 if self.chunk_dims.is_some() || wants_filter {
963 return Err(Hdf5Error::InvalidState(
964 "a compact dataset stores its data in the object header, so it \
965 cannot be chunked or filtered"
966 .into(),
967 ));
968 }
969 if self
970 .max_shape
971 .as_ref()
972 .is_some_and(|max| max.iter().zip(&shape).any(|(m, &d)| *m != Some(d)))
973 {
974 return Err(Hdf5Error::InvalidState(
975 "a compact dataset cannot be extendible: its maximum shape must \
976 equal its shape"
977 .into(),
978 ));
979 }
980
981 let index = {
982 let inner = borrow_inner(&self.file_inner);
983 match &*inner {
984 H5FileInner::Writer(writer) => {
985 let idx = writer.create_compact_dataset(&full_name, datatype, &dims_u64)?;
986 if let Some(time) = self.fill_time {
989 writer.set_dataset_fill_time(idx, time.wire_byte())?;
990 }
991 if let Some(ref fv) = fill_value {
992 writer.set_dataset_fill_value(idx, fv.clone())?;
993 }
994 idx
995 }
996 H5FileInner::Reader(_) => {
997 return Err(Hdf5Error::InvalidState(
998 "cannot create a dataset in read mode".into(),
999 ));
1000 }
1001 H5FileInner::Closed => {
1002 return Err(Hdf5Error::InvalidState("file is closed".into()));
1003 }
1004 }
1005 };
1006
1007 return Ok(H5Dataset {
1008 file_inner: clone_inner(&self.file_inner),
1009 info: DatasetInfo::Writer {
1010 index,
1011 shape,
1012 element_size,
1013 chunk_index: None,
1014 is_null: false,
1015 },
1016 _open: None,
1017 });
1018 }
1019
1020 if !self.virtual_mappings.is_empty() {
1021 let index = {
1022 let inner = borrow_inner(&self.file_inner);
1023 match &*inner {
1024 H5FileInner::Writer(writer) => {
1025 let idx = writer.create_virtual_dataset(
1026 &full_name,
1027 datatype,
1028 &dims_u64,
1029 self.max_shape
1030 .as_ref()
1031 .map(|max| {
1032 max.iter()
1033 .map(|m| m.map_or(u64::MAX, |v| v as u64))
1034 .collect::<Vec<u64>>()
1035 })
1036 .as_deref(),
1037 &self.virtual_mappings,
1038 )?;
1039 if let Some(time) = self.fill_time {
1047 writer.set_dataset_fill_time(idx, time.wire_byte())?;
1048 }
1049 if let Some(ref fv) = fill_value {
1050 writer.set_dataset_fill_value(idx, fv.clone())?;
1051 }
1052 idx
1053 }
1054 H5FileInner::Reader(_) => {
1055 return Err(Hdf5Error::InvalidState(
1056 "cannot create a dataset in read mode".into(),
1057 ));
1058 }
1059 H5FileInner::Closed => {
1060 return Err(Hdf5Error::InvalidState("file is closed".into()));
1061 }
1062 }
1063 };
1064
1065 return Ok(H5Dataset {
1066 file_inner: clone_inner(&self.file_inner),
1067 info: DatasetInfo::Writer {
1068 index,
1069 shape,
1070 element_size,
1071 chunk_index: None,
1072 is_null: false,
1073 },
1074 _open: None,
1075 });
1076 }
1077
1078 let auto_chunk: Option<Vec<usize>> =
1085 if self.chunk_dims.is_none() && wants_filter && !shape.is_empty() {
1086 Some(shape.iter().map(|&d| d.max(1)).collect())
1087 } else {
1088 None
1089 };
1090
1091 if let Some(chunk_dims) = self.chunk_dims.as_ref().or(auto_chunk.as_ref()) {
1092 let chunk_u64: Vec<u64> = chunk_dims.iter().map(|&d| d as u64).collect();
1094 let max_u64: Vec<u64> = if let Some(ref max) = self.max_shape {
1095 max.iter()
1096 .map(|m| m.map_or(u64::MAX, |v| v as u64))
1097 .collect()
1098 } else {
1099 dims_u64.clone()
1101 };
1102
1103 let chunk_bytes = chunk_u64.iter().product::<u64>() * element_size as u64;
1113 let v110_indexing = match &*borrow_inner(&self.file_inner) {
1114 H5FileInner::Writer(writer) => writer.uses_v110_chunk_indexing(chunk_bytes),
1115 _ => true,
1118 };
1119
1120 let n_unlimited = max_u64.iter().filter(|&&m| m == u64::MAX).count();
1130 let one_chunk = chunk_u64 == dims_u64 && max_u64 == dims_u64;
1131 let kind = if !v110_indexing {
1132 ChunkIndexKind::BtreeV1
1133 } else if n_unlimited >= 2 {
1134 ChunkIndexKind::BtreeV2
1135 } else if n_unlimited == 1 {
1136 ChunkIndexKind::ExtensibleArray
1137 } else if one_chunk {
1138 ChunkIndexKind::SingleChunk
1139 } else if self.early_allocation && !wants_filter {
1140 ChunkIndexKind::Implicit
1141 } else {
1142 ChunkIndexKind::FixedArray
1143 };
1144
1145 let index = {
1146 let inner = borrow_inner(&self.file_inner);
1147 match &*inner {
1148 H5FileInner::Writer(writer) => {
1149 let explicit_pipeline = || {
1155 use crate::format::messages::filter::FilterPipeline;
1156 if let Some(p) = self.custom_pipeline.clone() {
1157 return p;
1158 }
1159 let es = element_size as u32;
1163 match (self.shuffle, self.deflate_level) {
1164 (true, Some(level)) => FilterPipeline::shuffle_deflate(es, level),
1165 (true, None) => FilterPipeline::shuffle(es),
1166 (false, level) => FilterPipeline::deflate(level.unwrap()),
1168 }
1169 };
1170 let idx = if kind == ChunkIndexKind::BtreeV1 {
1171 let pipeline = wants_filter.then(explicit_pipeline);
1176 writer.create_btree_v1_dataset(
1177 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64, pipeline,
1178 )?
1179 } else if kind == ChunkIndexKind::BtreeV2 {
1180 if wants_filter {
1185 writer.create_btree_v2_dataset_with_pipeline(
1186 &full_name,
1187 datatype,
1188 &dims_u64,
1189 &max_u64,
1190 &chunk_u64,
1191 explicit_pipeline(),
1192 )?
1193 } else {
1194 writer.create_btree_v2_dataset(
1195 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64,
1196 )?
1197 }
1198 } else if kind == ChunkIndexKind::Implicit {
1199 writer.create_implicit_dataset(
1205 &full_name, datatype, &dims_u64, &chunk_u64,
1206 )?
1207 } else if kind == ChunkIndexKind::SingleChunk {
1208 if wants_filter {
1217 writer.create_single_chunk_dataset_with_pipeline(
1218 &full_name,
1219 datatype,
1220 &dims_u64,
1221 &chunk_u64,
1222 explicit_pipeline(),
1223 )?
1224 } else {
1225 writer.create_single_chunk_dataset(
1226 &full_name,
1227 datatype,
1228 &dims_u64,
1229 &chunk_u64,
1230 self.early_allocation,
1231 )?
1232 }
1233 } else if kind == ChunkIndexKind::FixedArray {
1234 let pipeline = wants_filter.then(explicit_pipeline);
1242 writer.create_fixed_array_dataset_with_max(
1243 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64, pipeline,
1244 )?
1245 } else if wants_filter {
1246 writer.create_chunked_dataset_with_pipeline(
1250 &full_name,
1251 datatype,
1252 &dims_u64,
1253 &max_u64,
1254 &chunk_u64,
1255 explicit_pipeline(),
1256 )?
1257 } else {
1258 writer.create_chunked_dataset(
1259 &full_name, datatype, &dims_u64, &max_u64, &chunk_u64,
1260 )?
1261 };
1262 if let Some(time) = self.fill_time {
1265 writer.set_dataset_fill_time(idx, time.wire_byte())?;
1266 }
1267 if let Some(ref fv) = fill_value {
1268 writer.set_dataset_fill_value(idx, fv.clone())?;
1269 }
1270 idx
1271 }
1272 H5FileInner::Reader(_) => {
1273 return Err(Hdf5Error::InvalidState(
1274 "cannot create a dataset in read mode".into(),
1275 ));
1276 }
1277 H5FileInner::Closed => {
1278 return Err(Hdf5Error::InvalidState("file is closed".into()));
1279 }
1280 }
1281 };
1282
1283 Ok(H5Dataset {
1284 file_inner: clone_inner(&self.file_inner),
1285 info: DatasetInfo::Writer {
1286 index,
1287 shape,
1288 element_size,
1289 chunk_index: Some(kind),
1290 is_null: false,
1291 },
1292 _open: None,
1293 })
1294 } else {
1295 let efile_access = match self.efile_prefix.as_deref() {
1297 Some(p) => DatasetAccess::new().efile_prefix(p),
1298 None => DatasetAccess::new(),
1299 };
1300 let (index, open) = {
1301 let inner = borrow_inner(&self.file_inner);
1302 match &*inner {
1303 H5FileInner::Writer(writer) => {
1304 let idx = match self.external.as_deref() {
1305 Some(files) => {
1306 let slots: Vec<(&str, u64, u64)> = files
1307 .iter()
1308 .map(|(name, offset, size)| (name.as_str(), *offset, *size))
1309 .collect();
1310 writer.create_external_dataset(
1311 &full_name,
1312 datatype,
1313 &dims_u64,
1314 self.max_shape
1315 .as_ref()
1316 .map(|max| {
1317 max.iter()
1318 .map(|m| m.map_or(u64::MAX, |v| v as u64))
1319 .collect::<Vec<u64>>()
1320 })
1321 .as_deref(),
1322 &slots,
1323 )?
1324 }
1325 None => writer.create_dataset(&full_name, datatype, &dims_u64)?,
1326 };
1327 let open = writer.bind_efile_prefix(idx, &efile_access)?;
1333 if let Some(time) = self.fill_time {
1336 writer.set_dataset_fill_time(idx, time.wire_byte())?;
1337 }
1338 if let Some(ref fv) = fill_value {
1339 writer.set_dataset_fill_value(idx, fv.clone())?;
1340 }
1341 (idx, open)
1342 }
1343 H5FileInner::Reader(_) => {
1344 return Err(Hdf5Error::InvalidState(
1345 "cannot create a dataset in read mode".into(),
1346 ));
1347 }
1348 H5FileInner::Closed => {
1349 return Err(Hdf5Error::InvalidState("file is closed".into()));
1350 }
1351 }
1352 };
1353
1354 Ok(H5Dataset {
1355 file_inner: clone_inner(&self.file_inner),
1356 info: DatasetInfo::Writer {
1357 index,
1358 shape,
1359 element_size,
1360 chunk_index: None,
1361 is_null: false,
1362 },
1363 _open: open,
1364 })
1365 }
1366 }
1367}
1368
1369enum DatasetInfo {
1375 Writer {
1377 index: usize,
1379 shape: Vec<usize>,
1381 element_size: usize,
1383 chunk_index: Option<ChunkIndexKind>,
1390 is_null: bool,
1394 },
1395 Reader {
1397 name: String,
1399 shape: Vec<usize>,
1401 element_size: usize,
1403 },
1404}
1405
1406pub struct H5Dataset {
1416 file_inner: SharedInner,
1417 info: DatasetInfo,
1418 _open: Option<crate::io::reader::DatasetOpenToken>,
1428}
1429
1430impl Drop for H5Dataset {
1431 fn drop(&mut self) {
1443 let Some(open) = self._open.take() else {
1444 return;
1445 };
1446 drop(open);
1447 if matches!(self.info, DatasetInfo::Writer { .. }) {
1448 return;
1449 }
1450 let Some(mut inner) = crate::file::try_borrow_inner_mut(&self.file_inner) else {
1451 return;
1452 };
1453 if let crate::file::H5FileInner::Reader(reader) = &mut *inner {
1454 reader.release_closed_virtual_sources();
1455 }
1456 }
1457}
1458
1459#[derive(Clone, Copy)]
1464enum ChunkBytes<'a> {
1465 Unfiltered(&'a [u8]),
1468 Prefiltered { data: &'a [u8], filter_mask: u32 },
1471}
1472
1473pub(crate) const HOST_BYTE_ORDER: ByteOrder = if cfg!(target_endian = "big") {
1475 ByteOrder::BigEndian
1476} else {
1477 ByteOrder::LittleEndian
1478};
1479
1480pub(crate) const FOREIGN_BYTE_ORDER: ByteOrder = match HOST_BYTE_ORDER {
1482 ByteOrder::LittleEndian => ByteOrder::BigEndian,
1483 ByteOrder::BigEndian => ByteOrder::LittleEndian,
1484};
1485
1486#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1488enum ByteOrderAction {
1489 Keep,
1491 SwapElements,
1493 Refuse,
1495}
1496
1497fn byte_order_action(datatype: &DatatypeMessage, width: usize) -> ByteOrderAction {
1506 match datatype.scalar_byte_order() {
1507 Some(order) if order == FOREIGN_BYTE_ORDER && width > 1 => ByteOrderAction::SwapElements,
1508 Some(_) => ByteOrderAction::Keep,
1509 None if datatype.contains_byte_order(FOREIGN_BYTE_ORDER) => ByteOrderAction::Refuse,
1510 None => ByteOrderAction::Keep,
1511 }
1512}
1513
1514#[cfg(feature = "mmap")]
1525pub(crate) fn stored_image_mismatch(
1526 datatype: &DatatypeMessage,
1527 width: usize,
1528) -> Option<&'static str> {
1529 match byte_order_action(datatype, width) {
1530 ByteOrderAction::SwapElements => return Some("they are stored in the foreign byte order"),
1531 ByteOrderAction::Refuse => {
1532 return Some("it is a composite storing members in the foreign byte order")
1533 }
1534 ByteOrderAction::Keep => {}
1535 }
1536 if crate::format::nbit_scaleoffset::datatype_needs_bit_conversion(datatype) {
1537 return Some("the significant bits do not fill the stored element");
1538 }
1539 None
1540}
1541
1542pub(crate) fn to_host_byte_order(
1555 bytes: &mut [u8],
1556 datatype: &DatatypeMessage,
1557 width: usize,
1558) -> Result<()> {
1559 match byte_order_action(datatype, width) {
1560 ByteOrderAction::Keep => {}
1561 ByteOrderAction::SwapElements => {
1562 for elem in bytes.chunks_exact_mut(width) {
1563 elem.reverse();
1564 }
1565 }
1566 ByteOrderAction::Refuse => {
1567 return Err(Hdf5Error::TypeMismatch(format!(
1568 "dataset datatype {datatype} stores {FOREIGN_BYTE_ORDER:?} values, which a \
1569 typed read cannot reinterpret element by element; read_raw_bytes() returns \
1570 the image to decode member by member"
1571 )))
1572 }
1573 }
1574 Ok(())
1575}
1576
1577pub(crate) fn vlen_sequence_images<'a, T: H5Type>(
1586 items: &'a [&'a [T]],
1587) -> Result<Vec<std::borrow::Cow<'a, [u8]>>> {
1588 let base = T::hdf5_type();
1589 items
1590 .iter()
1591 .map(|item| {
1592 let host = unsafe {
1597 std::slice::from_raw_parts(item.as_ptr() as *const u8, std::mem::size_of_val(*item))
1598 };
1599 to_stored_byte_order(host, &base, T::element_size())
1600 })
1601 .collect()
1602}
1603
1604pub(crate) fn to_stored_byte_order<'a>(
1618 bytes: &'a [u8],
1619 datatype: &DatatypeMessage,
1620 width: usize,
1621) -> Result<std::borrow::Cow<'a, [u8]>> {
1622 match byte_order_action(datatype, width) {
1623 ByteOrderAction::Keep => Ok(std::borrow::Cow::Borrowed(bytes)),
1624 ByteOrderAction::SwapElements => {
1625 let mut owned = bytes.to_vec();
1626 for elem in owned.chunks_exact_mut(width) {
1627 elem.reverse();
1628 }
1629 Ok(std::borrow::Cow::Owned(owned))
1630 }
1631 ByteOrderAction::Refuse => Err(Hdf5Error::TypeMismatch(format!(
1632 "dataset datatype {datatype} stores {FOREIGN_BYTE_ORDER:?} values, which a typed \
1633 write cannot lay out element by element; write_raw_bytes() takes the image the \
1634 caller encodes member by member"
1635 ))),
1636 }
1637}
1638
1639fn trim_fixed_string(elem: &[u8], padding: u8, index: usize) -> Result<&[u8]> {
1646 crate::format::messages::datatype::fixed_string_content(elem, padding).ok_or_else(|| {
1647 Hdf5Error::InvalidState(format!(
1648 "string {index} uses padding rule {padding}, which the format reserves"
1649 ))
1650 })
1651}
1652
1653fn decode_string(bytes: &[u8], charset: u8, lossy: bool, index: usize) -> Result<String> {
1658 if lossy {
1659 return Ok(String::from_utf8_lossy(bytes).into_owned());
1660 }
1661 match charset {
1662 0 => match bytes.iter().position(|&b| b >= 0x80) {
1664 None => Ok(String::from_utf8_lossy(bytes).into_owned()),
1665 Some(at) => Err(Hdf5Error::InvalidState(format!(
1666 "string {index} declares the ASCII character set but byte {at} is {:#04x}",
1667 bytes[at]
1668 ))),
1669 },
1670 1 => String::from_utf8(bytes.to_vec()).map_err(|e| {
1671 Hdf5Error::InvalidState(format!(
1672 "string {index} declares UTF-8 but is not valid UTF-8: {e}"
1673 ))
1674 }),
1675 other => Err(Hdf5Error::InvalidState(format!(
1676 "string {index} uses character set {other}, which the format reserves"
1677 ))),
1678 }
1679}
1680
1681#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1688pub enum StorageLayout {
1689 Compact,
1691 Contiguous,
1696 Chunked,
1700 Virtual,
1703}
1704
1705#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1714pub enum ChunkIndex {
1715 BtreeV1,
1718 BtreeV2,
1720 SingleChunk,
1723 Implicit,
1726 FixedArray,
1728 ExtensibleArray,
1730}
1731
1732#[derive(Debug, Clone, PartialEq, Eq)]
1735pub enum FillValue {
1736 Default,
1739 Undefined,
1742 UserDefined(Vec<u8>),
1744}
1745
1746#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1755pub enum FillTime {
1756 Alloc,
1758 Never,
1760 IfSet,
1764}
1765
1766impl FillTime {
1767 fn wire_byte(self) -> u8 {
1770 match self {
1771 Self::Alloc => 0,
1772 Self::Never => 1,
1773 Self::IfSet => 2,
1774 }
1775 }
1776}
1777
1778#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1790pub enum AllocTime {
1791 Early,
1793 Late,
1795 Incr,
1798}
1799
1800#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1807pub enum VirtualView {
1808 #[default]
1812 LastAvailable,
1813 FirstMissing,
1822}
1823
1824#[derive(Debug, Clone, PartialEq, Eq, Default)]
1849pub struct DatasetAccess {
1850 view: VirtualView,
1851 printf_gap: u64,
1852 virtual_prefix: Option<String>,
1853 efile_prefix: Option<String>,
1854}
1855
1856impl DatasetAccess {
1857 pub fn new() -> Self {
1862 Self::default()
1863 }
1864
1865 pub fn virtual_view(mut self, view: VirtualView) -> Self {
1869 self.view = view;
1870 self
1871 }
1872
1873 pub fn virtual_printf_gap(mut self, gap: u64) -> Self {
1882 self.printf_gap = gap;
1883 self
1884 }
1885
1886 pub fn view(&self) -> VirtualView {
1888 self.view
1889 }
1890
1891 pub fn virtual_prefix(mut self, prefix: impl Into<String>) -> Self {
1910 self.virtual_prefix = Some(prefix.into());
1911 self
1912 }
1913
1914 pub fn efile_prefix(mut self, prefix: impl Into<String>) -> Self {
1944 self.efile_prefix = Some(prefix.into());
1945 self
1946 }
1947
1948 pub fn printf_gap(&self) -> u64 {
1952 self.printf_gap
1953 }
1954
1955 pub fn virtual_prefix_value(&self) -> Option<&str> {
1960 self.virtual_prefix.as_deref()
1961 }
1962
1963 pub fn efile_prefix_value(&self) -> Option<&str> {
1968 self.efile_prefix.as_deref()
1969 }
1970
1971 pub(crate) fn effective_printf_gap(&self) -> u64 {
1979 match self.view {
1980 VirtualView::LastAvailable => self.printf_gap,
1981 VirtualView::FirstMissing => 0,
1982 }
1983 }
1984
1985 pub(crate) fn validate(&self) -> Result<()> {
1988 if self.printf_gap == u64::MAX {
1989 return Err(Hdf5Error::InvalidState(
1990 "virtual_printf_gap(u64::MAX) is libhdf5's HSIZE_UNDEF, which \
1991 H5Pset_virtual_printf_gap refuses as \"not a valid printf gap size\""
1992 .into(),
1993 ));
1994 }
1995 Ok(())
1996 }
1997}
1998
1999fn element_count(counts: &[u64]) -> Result<usize> {
2002 counts
2003 .iter()
2004 .try_fold(1usize, |acc, &c| {
2005 usize::try_from(c).ok().and_then(|c| acc.checked_mul(c))
2006 })
2007 .ok_or_else(|| {
2008 Hdf5Error::InvalidState(format!("selection {counts:?} has more elements than usize"))
2009 })
2010}
2011
2012impl H5Dataset {
2013 pub(crate) fn new_reader(
2015 file_inner: SharedInner,
2016 name: String,
2017 shape: Vec<usize>,
2018 element_size: usize,
2019 open: Option<crate::io::reader::DatasetOpenToken>,
2020 ) -> Self {
2021 Self {
2022 file_inner,
2023 info: DatasetInfo::Reader {
2024 name,
2025 shape,
2026 element_size,
2027 },
2028 _open: open,
2029 }
2030 }
2031
2032 pub(crate) fn new_writer(
2043 file_inner: SharedInner,
2044 index: usize,
2045 parts: crate::io::writer::DatasetHandleParts,
2046 ) -> Self {
2047 Self {
2048 file_inner,
2049 info: DatasetInfo::Writer {
2050 index,
2051 shape: parts.shape,
2052 element_size: parts.element_size,
2053 chunk_index: parts.chunk_index,
2054 is_null: false,
2055 },
2056 _open: parts.open,
2057 }
2058 }
2059
2060 pub fn shape(&self) -> Vec<usize> {
2062 match &self.info {
2063 DatasetInfo::Writer { shape, .. } => shape.clone(),
2064 DatasetInfo::Reader { shape, .. } => shape.clone(),
2065 }
2066 }
2067
2068 pub fn ndims(&self) -> usize {
2070 match &self.info {
2071 DatasetInfo::Writer { shape, .. } => shape.len(),
2072 DatasetInfo::Reader { shape, .. } => shape.len(),
2073 }
2074 }
2075
2076 pub fn total_elements(&self) -> usize {
2082 if self.is_null() {
2083 return 0;
2084 }
2085 match &self.info {
2086 DatasetInfo::Writer { shape, .. } => shape.iter().product(),
2087 DatasetInfo::Reader { shape, .. } => shape.iter().product(),
2088 }
2089 }
2090
2091 pub fn element_size(&self) -> usize {
2093 match &self.info {
2094 DatasetInfo::Writer { element_size, .. } => *element_size,
2095 DatasetInfo::Reader { element_size, .. } => *element_size,
2096 }
2097 }
2098
2099 pub fn is_null(&self) -> bool {
2104 match &self.info {
2105 DatasetInfo::Writer { is_null, .. } => *is_null,
2106 DatasetInfo::Reader { name, .. } => {
2107 let mut inner = borrow_inner_mut(&self.file_inner);
2108 match &mut *inner {
2109 H5FileInner::Reader(reader) => reader
2110 .dataset_info(name)
2111 .map(|info| info.dataspace.is_null())
2112 .unwrap_or(false),
2113 _ => false,
2114 }
2115 }
2116 }
2117 }
2118
2119 pub fn datatype(&self) -> Result<DatatypeMessage> {
2150 match &self.info {
2151 DatasetInfo::Reader { name, .. } => {
2152 let mut inner = borrow_inner_mut(&self.file_inner);
2153 match &mut *inner {
2154 H5FileInner::Reader(reader) => reader
2155 .dataset_info(name)
2156 .map(|info| info.datatype.clone())
2157 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2158 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2159 }
2160 }
2161 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2162 "datatype() is only available in read mode".into(),
2163 )),
2164 }
2165 }
2166
2167 pub fn chunk_dims(&self) -> Option<Vec<usize>> {
2169 match &self.info {
2170 DatasetInfo::Reader { name, .. } => {
2171 let mut inner = borrow_inner_mut(&self.file_inner);
2172 if let H5FileInner::Reader(reader) = &mut *inner {
2173 if let Some(info) = reader.dataset_info(name) {
2174 use crate::format::messages::data_layout::DataLayoutMessage;
2175 let chunk_dims = match &info.layout {
2176 DataLayoutMessage::ChunkedV4 { chunk_dims, .. }
2177 | DataLayoutMessage::ChunkedV3 { chunk_dims, .. } => Some(chunk_dims),
2178 _ => None,
2179 };
2180 if let Some(chunk_dims) = chunk_dims {
2181 return Some(
2183 chunk_dims[..chunk_dims.len() - 1]
2184 .iter()
2185 .map(|&d| d as usize)
2186 .collect(),
2187 );
2188 }
2189 }
2190 }
2191 None
2192 }
2193 DatasetInfo::Writer { .. } => None,
2194 }
2195 }
2196
2197 pub fn is_chunked(&self) -> bool {
2199 match &self.info {
2200 DatasetInfo::Writer { chunk_index, .. } => chunk_index.is_some(),
2201 DatasetInfo::Reader { name, .. } => {
2202 let mut inner = borrow_inner_mut(&self.file_inner);
2203 match &mut *inner {
2204 H5FileInner::Reader(reader) => {
2205 if let Some(info) = reader.dataset_info(name) {
2206 use crate::format::messages::data_layout::DataLayoutMessage;
2207 matches!(
2208 info.layout,
2209 DataLayoutMessage::ChunkedV4 { .. }
2210 | DataLayoutMessage::ChunkedV3 { .. }
2211 )
2212 } else {
2213 false
2214 }
2215 }
2216 _ => false,
2217 }
2218 }
2219 }
2220 }
2221
2222 pub fn storage_layout(&self) -> Result<StorageLayout> {
2229 match &self.info {
2230 DatasetInfo::Reader { name, .. } => {
2231 let mut inner = borrow_inner_mut(&self.file_inner);
2232 match &mut *inner {
2233 H5FileInner::Reader(reader) => {
2234 use crate::format::messages::data_layout::DataLayoutMessage;
2235 reader
2236 .dataset_info(name)
2237 .map(|info| match &info.layout {
2238 DataLayoutMessage::Compact { .. } => StorageLayout::Compact,
2239 DataLayoutMessage::Contiguous { .. } => StorageLayout::Contiguous,
2240 DataLayoutMessage::ChunkedV3 { .. }
2241 | DataLayoutMessage::ChunkedV4 { .. } => StorageLayout::Chunked,
2242 DataLayoutMessage::Virtual { .. } => StorageLayout::Virtual,
2243 })
2244 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))
2245 }
2246 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2247 }
2248 }
2249 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2250 "storage_layout() is only available in read mode".into(),
2251 )),
2252 }
2253 }
2254
2255 pub fn chunk_index(&self) -> Result<Option<ChunkIndex>> {
2263 match &self.info {
2264 DatasetInfo::Reader { name, .. } => {
2265 let mut inner = borrow_inner_mut(&self.file_inner);
2266 match &mut *inner {
2267 H5FileInner::Reader(reader) => {
2268 use crate::format::messages::data_layout::{
2269 ChunkIndexType, DataLayoutMessage,
2270 };
2271 reader
2272 .dataset_info(name)
2273 .map(|info| match &info.layout {
2274 DataLayoutMessage::ChunkedV3 { .. } => Some(ChunkIndex::BtreeV1),
2275 DataLayoutMessage::ChunkedV4 { index_type, .. } => {
2276 Some(match index_type {
2277 ChunkIndexType::SingleChunk => ChunkIndex::SingleChunk,
2278 ChunkIndexType::Implicit => ChunkIndex::Implicit,
2279 ChunkIndexType::FixedArray => ChunkIndex::FixedArray,
2280 ChunkIndexType::ExtensibleArray => {
2281 ChunkIndex::ExtensibleArray
2282 }
2283 ChunkIndexType::BTreeV2 => ChunkIndex::BtreeV2,
2284 })
2285 }
2286 _ => None,
2287 })
2288 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))
2289 }
2290 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2291 }
2292 }
2293 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2294 "chunk_index() is only available in read mode".into(),
2295 )),
2296 }
2297 }
2298
2299 pub fn filters(&self) -> Result<Vec<Filter>> {
2308 match &self.info {
2309 DatasetInfo::Reader { name, .. } => {
2310 let mut inner = borrow_inner_mut(&self.file_inner);
2311 match &mut *inner {
2312 H5FileInner::Reader(reader) => reader
2313 .dataset_info(name)
2314 .map(|info| {
2315 info.filter_pipeline
2316 .as_ref()
2317 .map(|fp| fp.filters.clone())
2318 .unwrap_or_default()
2319 })
2320 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2321 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2322 }
2323 }
2324 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2325 "filters() is only available in read mode".into(),
2326 )),
2327 }
2328 }
2329
2330 pub fn fill_value(&self) -> Result<FillValue> {
2337 match &self.info {
2338 DatasetInfo::Reader { name, .. } => {
2339 let mut inner = borrow_inner_mut(&self.file_inner);
2340 match &mut *inner {
2341 H5FileInner::Reader(reader) => reader
2342 .dataset_info(name)
2343 .map(|info| match info.fill_defined {
2344 0 => FillValue::Undefined,
2345 2 => {
2346 FillValue::UserDefined(info.fill_value.clone().unwrap_or_default())
2347 }
2348 _ => FillValue::Default,
2349 })
2350 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2351 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2352 }
2353 }
2354 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2355 "fill_value() is only available in read mode".into(),
2356 )),
2357 }
2358 }
2359
2360 pub fn fill_time(&self) -> Result<FillTime> {
2368 match &self.info {
2369 DatasetInfo::Reader { name, .. } => {
2370 let mut inner = borrow_inner_mut(&self.file_inner);
2371 match &mut *inner {
2372 H5FileInner::Reader(reader) => reader
2373 .dataset_info(name)
2374 .map(|info| match info.fill_write_time {
2375 0 => FillTime::Alloc,
2376 1 => FillTime::Never,
2377 _ => FillTime::IfSet,
2378 })
2379 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2380 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2381 }
2382 }
2383 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2384 "fill_time() is only available in read mode".into(),
2385 )),
2386 }
2387 }
2388
2389 pub fn alloc_time(&self) -> Result<AllocTime> {
2397 match &self.info {
2398 DatasetInfo::Reader { name, .. } => {
2399 let mut inner = borrow_inner_mut(&self.file_inner);
2400 match &mut *inner {
2401 H5FileInner::Reader(reader) => reader
2402 .dataset_info(name)
2403 .map(|info| match info.alloc_time {
2404 1 => AllocTime::Early,
2405 3 => AllocTime::Incr,
2406 _ => AllocTime::Late,
2407 })
2408 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2409 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2410 }
2411 }
2412 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2413 "alloc_time() is only available in read mode".into(),
2414 )),
2415 }
2416 }
2417
2418 pub fn external_files(&self) -> Result<Vec<ExternalFileSegment>> {
2427 match &self.info {
2428 DatasetInfo::Reader { name, .. } => {
2429 let mut inner = borrow_inner_mut(&self.file_inner);
2430 match &mut *inner {
2431 H5FileInner::Reader(reader) => reader
2432 .dataset_info(name)
2433 .map(|info| info.external_files.clone())
2434 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2435 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2436 }
2437 }
2438 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2439 "external_files() is only available in read mode".into(),
2440 )),
2441 }
2442 }
2443
2444 pub fn max_shape(&self) -> Result<Vec<Option<usize>>> {
2454 match &self.info {
2455 DatasetInfo::Reader { name, .. } => {
2456 let mut inner = borrow_inner_mut(&self.file_inner);
2457 match &mut *inner {
2458 H5FileInner::Reader(reader) => reader
2459 .dataset_info(name)
2460 .map(|info| match &info.dataspace.max_dims {
2461 Some(max_dims) => max_dims
2462 .iter()
2463 .map(|&d| (d != u64::MAX).then_some(d as usize))
2464 .collect(),
2465 None => info
2466 .dataspace
2467 .dims
2468 .iter()
2469 .map(|&d| Some(d as usize))
2470 .collect(),
2471 })
2472 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2473 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2474 }
2475 }
2476 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2477 "max_shape() is only available in read mode".into(),
2478 )),
2479 }
2480 }
2481
2482 pub fn virtual_mappings(&self) -> Result<Vec<VirtualMapping>> {
2491 match &self.info {
2492 DatasetInfo::Reader { name, .. } => {
2493 let mut inner = borrow_inner_mut(&self.file_inner);
2494 match &mut *inner {
2495 H5FileInner::Reader(reader) => reader
2496 .dataset_info(name)
2497 .map(|info| {
2498 info.virtual_mappings
2499 .as_ref()
2500 .map(|vml| vml.mappings.clone())
2501 .unwrap_or_default()
2502 })
2503 .ok_or_else(|| Hdf5Error::NotFound(name.clone())),
2504 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2505 }
2506 }
2507 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2508 "virtual_mappings() is only available in read mode".into(),
2509 )),
2510 }
2511 }
2512
2513 pub fn attr_names(&self) -> Result<Vec<String>> {
2515 match &self.info {
2516 DatasetInfo::Reader { name, .. } => {
2517 let mut inner = borrow_inner_mut(&self.file_inner);
2518 match &mut *inner {
2519 H5FileInner::Reader(reader) => Ok(reader.dataset_attr_names(name)?),
2520 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2521 }
2522 }
2523 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2524 "attr_names not available in write mode".into(),
2525 )),
2526 }
2527 }
2528
2529 pub fn attr_unreadable_reason(&self, attr_name: &str) -> Result<Option<String>> {
2537 match &self.info {
2538 DatasetInfo::Reader { name, .. } => {
2539 let mut inner = borrow_inner_mut(&self.file_inner);
2540 match &mut *inner {
2541 H5FileInner::Reader(reader) => Ok(reader
2542 .dataset_attr_unreadable_reason(name, attr_name)
2543 .map(str::to_string)),
2544 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2545 }
2546 }
2547 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2548 "attr_unreadable_reason not available in write mode".into(),
2549 )),
2550 }
2551 }
2552
2553 pub fn attrs_unreadable_reason(&self) -> Result<Option<String>> {
2563 match &self.info {
2564 DatasetInfo::Reader { name, .. } => {
2565 let mut inner = borrow_inner_mut(&self.file_inner);
2566 match &mut *inner {
2567 H5FileInner::Reader(reader) => Ok(reader
2568 .dataset_attrs_unreadable_reason(name)
2569 .map(str::to_string)),
2570 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2571 }
2572 }
2573 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2574 "attrs_unreadable_reason not available in write mode".into(),
2575 )),
2576 }
2577 }
2578
2579 pub fn attr_storage(&self) -> Result<AttributeStorage> {
2583 match &self.info {
2584 DatasetInfo::Reader { name, .. } => {
2585 let mut inner = borrow_inner_mut(&self.file_inner);
2586 match &mut *inner {
2587 H5FileInner::Reader(reader) => Ok(reader.dataset_attr_storage(name)?),
2588 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2589 }
2590 }
2591 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2592 "attr_storage not available in write mode".into(),
2593 )),
2594 }
2595 }
2596
2597 pub fn header_attr_count(&self) -> Result<u64> {
2600 match &self.info {
2601 DatasetInfo::Reader { name, .. } => {
2602 let mut inner = borrow_inner_mut(&self.file_inner);
2603 match &mut *inner {
2604 H5FileInner::Reader(reader) => Ok(reader.dataset_header_attr_count(name)?),
2605 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2606 }
2607 }
2608 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2609 "header_attr_count not available in write mode".into(),
2610 )),
2611 }
2612 }
2613
2614 pub fn attr(&self, attr_name: &str) -> Result<crate::attribute::H5Attribute> {
2616 match &self.info {
2617 DatasetInfo::Reader { name, .. } => {
2618 let mut inner = borrow_inner_mut(&self.file_inner);
2619 match &mut *inner {
2620 H5FileInner::Reader(reader) => {
2621 let attr_msg = reader.dataset_attr(name, attr_name)?.clone();
2622 Ok(crate::attribute::H5Attribute::new_reader(
2623 clone_inner(&self.file_inner),
2624 attr_msg,
2625 ))
2626 }
2627 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
2628 }
2629 }
2630 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
2631 "attr() not available in write mode".into(),
2632 )),
2633 }
2634 }
2635
2636 pub fn new_attr<T: 'static>(&self) -> AttrBuilder<'_, T> {
2652 let ds_index = match &self.info {
2653 DatasetInfo::Writer { index, .. } => *index,
2654 DatasetInfo::Reader { .. } => {
2655 usize::MAX
2658 }
2659 };
2660 AttrBuilder::new(&self.file_inner, ds_index)
2661 }
2662
2663 pub fn set_attr_typed(
2669 &self,
2670 name: &str,
2671 datatype: DatatypeMessage,
2672 value: Vec<u8>,
2673 ) -> Result<()> {
2674 let attr = crate::attribute::typed_scalar_attr(name, datatype, value)?;
2675 let DatasetInfo::Writer { index, .. } = &self.info else {
2676 return Err(Hdf5Error::InvalidState(
2677 "cannot write attributes in read mode".into(),
2678 ));
2679 };
2680 match &*borrow_inner(&self.file_inner) {
2681 H5FileInner::Writer(writer) => {
2682 writer.add_dataset_attribute(*index, attr)?;
2683 Ok(())
2684 }
2685 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
2686 "cannot write attributes in read mode".into(),
2687 )),
2688 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
2689 }
2690 }
2691
2692 pub fn write_raw<T: H5Type>(&self, data: &[T]) -> Result<()> {
2707 match &self.info {
2708 DatasetInfo::Writer {
2709 index,
2710 shape,
2711 element_size,
2712 chunk_index,
2713 is_null,
2714 } => {
2715 if *is_null {
2716 return Err(Hdf5Error::InvalidState(
2717 "cannot write to a NULL dataspace dataset".into(),
2718 ));
2719 }
2720 let total_elements: usize = shape.iter().product();
2721 if data.len() != total_elements {
2722 return Err(Hdf5Error::InvalidState(format!(
2723 "data length {} does not match dataset size {}",
2724 data.len(),
2725 total_elements,
2726 )));
2727 }
2728
2729 if T::element_size() != *element_size {
2731 return Err(Hdf5Error::TypeMismatch(format!(
2732 "write type has element size {} but dataset expects {}",
2733 T::element_size(),
2734 element_size,
2735 )));
2736 }
2737
2738 let byte_len = data.len() * T::element_size();
2742 let host =
2743 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) };
2744 let datatype = {
2745 let inner = borrow_inner(&self.file_inner);
2746 match &*inner {
2747 H5FileInner::Writer(writer) => writer.dataset_datatype(*index),
2748 _ => {
2749 return Err(Hdf5Error::InvalidState(
2750 "file is no longer in write mode".into(),
2751 ))
2752 }
2753 }
2754 };
2755 let stored = to_stored_byte_order(host, &datatype, T::element_size())?;
2756
2757 if let Some(kind) = *chunk_index {
2758 return self.write_full_image_chunked(*index, kind, &stored, *element_size);
2762 }
2763
2764 let inner = borrow_inner(&self.file_inner);
2765 match &*inner {
2766 H5FileInner::Writer(writer) => {
2767 writer.write_dataset_raw(*index, &stored)?;
2768 Ok(())
2769 }
2770 _ => Err(Hdf5Error::InvalidState(
2771 "file is no longer in write mode".into(),
2772 )),
2773 }
2774 }
2775 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
2776 "cannot write to a dataset opened in read mode".into(),
2777 )),
2778 }
2779 }
2780
2781 pub fn write_raw_bytes(&self, bytes: &[u8]) -> Result<()> {
2819 match &self.info {
2820 DatasetInfo::Writer {
2821 index,
2822 shape,
2823 element_size,
2824 chunk_index,
2825 is_null,
2826 } => {
2827 if *is_null {
2828 return Err(Hdf5Error::InvalidState(
2829 "cannot write to a NULL dataspace dataset".into(),
2830 ));
2831 }
2832 let expected: usize = shape.iter().product::<usize>() * *element_size;
2833 if bytes.len() != expected {
2834 return Err(Hdf5Error::InvalidState(format!(
2835 "raw byte length {} does not match dataset size {} \
2836 (product(shape) * element_size {})",
2837 bytes.len(),
2838 expected,
2839 element_size,
2840 )));
2841 }
2842 if let Some(kind) = *chunk_index {
2843 return self.write_full_image_chunked(*index, kind, bytes, *element_size);
2846 }
2847 let inner = borrow_inner(&self.file_inner);
2848 match &*inner {
2849 H5FileInner::Writer(writer) => {
2850 writer.write_dataset_raw(*index, bytes)?;
2851 Ok(())
2852 }
2853 _ => Err(Hdf5Error::InvalidState(
2854 "file is no longer in write mode".into(),
2855 )),
2856 }
2857 }
2858 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
2859 "cannot write to a dataset opened in read mode".into(),
2860 )),
2861 }
2862 }
2863
2864 fn write_full_image_chunked(
2874 &self,
2875 index: usize,
2876 kind: ChunkIndexKind,
2877 bytes: &[u8],
2878 element_size: usize,
2879 ) -> Result<()> {
2880 let inner = borrow_inner(&self.file_inner);
2881 let writer = match &*inner {
2882 H5FileInner::Writer(w) => w,
2883 _ => {
2884 return Err(Hdf5Error::InvalidState(
2885 "file is no longer in write mode".into(),
2886 ))
2887 }
2888 };
2889 let cell = writer.ds(index);
2893 let _op = cell.op.lock();
2894 writer.flush_append_buffer(index)?;
2897 let chunk_dims = writer
2898 .dataset_chunk_dims(index)
2899 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
2900 .to_vec();
2901 let dims = writer.dataset_dims(index).to_vec();
2902 let rank = dims.len();
2903
2904 let mut grid = vec![0u64; rank];
2906 for d in 0..rank {
2907 grid[d] = if chunk_dims[d] > 0 {
2908 dims[d].div_ceil(chunk_dims[d])
2909 } else {
2910 0
2911 };
2912 }
2913 let total_chunks: u64 = grid.iter().product();
2914
2915 let coords_of = |linear: u64| -> Vec<u64> {
2921 let mut rem = linear;
2922 let mut coords = vec![0u64; rank];
2923 for d in (0..rank).rev() {
2924 coords[d] = rem % grid[d];
2925 rem /= grid[d];
2926 }
2927 coords
2928 };
2929
2930 let batched = writer.dataset_is_filtered(index)
2936 && matches!(
2937 kind,
2938 ChunkIndexKind::ExtensibleArray | ChunkIndexKind::FixedArray
2939 );
2940 if !batched {
2941 let mut staging = Vec::new();
2947 for linear in 0..total_chunks {
2948 let coords = coords_of(linear);
2949 let chunk =
2950 match Self::contiguous_chunk_span(&dims, &chunk_dims, &coords, element_size) {
2951 Some(span) => &bytes[span],
2952 None => {
2953 Self::gather_chunk_into(
2954 &mut staging,
2955 bytes,
2956 &dims,
2957 &chunk_dims,
2958 &coords,
2959 element_size,
2960 );
2961 &staging[..]
2962 }
2963 };
2964 writer.write_chunk_at_coords(index, &coords, chunk)?;
2965 }
2966 } else {
2967 const BATCH_WINDOW: u64 = 256;
2978 let mut start = 0u64;
2979 while start < total_chunks {
2980 let end = (start + BATCH_WINDOW).min(total_chunks);
2981 let items: Vec<(Vec<u64>, Cow<'_, [u8]>)> = (start..end)
2982 .map(|counter| {
2983 let coords = coords_of(counter);
2984 let data = match Self::contiguous_chunk_span(
2985 &dims,
2986 &chunk_dims,
2987 &coords,
2988 element_size,
2989 ) {
2990 Some(span) => Cow::Borrowed(&bytes[span]),
2991 None => {
2992 let mut buf = Vec::new();
2993 Self::gather_chunk_into(
2994 &mut buf,
2995 bytes,
2996 &dims,
2997 &chunk_dims,
2998 &coords,
2999 element_size,
3000 );
3001 Cow::Owned(buf)
3002 }
3003 };
3004 (coords, data)
3005 })
3006 .collect();
3007 if kind == ChunkIndexKind::FixedArray {
3008 let pairs: Vec<(&[u64], &[u8])> = items
3009 .iter()
3010 .map(|(c, d)| (c.as_slice(), d.as_ref()))
3011 .collect();
3012 writer.write_chunks_fixed_array_batch_inner(index, &pairs)?;
3013 } else {
3014 let mut pairs: Vec<(u64, &[u8])> = Vec::with_capacity(items.len());
3015 for (c, d) in &items {
3016 pairs.push((writer.chunk_slot(index, c)?, d.as_ref()));
3017 }
3018 writer.write_chunks_batch_inner(index, &pairs)?;
3019 }
3020 start = end;
3021 }
3022 }
3023 Ok(())
3024 }
3025
3026 fn contiguous_chunk_span(
3038 dims: &[u64],
3039 chunk_dims: &[u64],
3040 coords: &[u64],
3041 element_size: usize,
3042 ) -> Option<std::ops::Range<usize>> {
3043 let rank = dims.len();
3044 if rank == 0 || chunk_dims[1..] != dims[1..] {
3045 return None;
3046 }
3047 if (coords[0] + 1) * chunk_dims[0] > dims[0] {
3048 return None;
3049 }
3050 let plane: u64 = dims[1..].iter().product::<u64>() * element_size as u64;
3051 let start = usize::try_from(coords[0] * chunk_dims[0] * plane).ok()?;
3052 let len = usize::try_from(chunk_dims[0] * plane).ok()?;
3053 Some(start..start.checked_add(len)?)
3054 }
3055
3056 fn gather_chunk_into(
3064 out: &mut Vec<u8>,
3065 source: &[u8],
3066 dims: &[u64],
3067 chunk_dims: &[u64],
3068 coords: &[u64],
3069 element_size: usize,
3070 ) {
3071 let rank = dims.len();
3072 let chunk_elems: u64 = chunk_dims.iter().product();
3073 let chunk_bytes = chunk_elems as usize * element_size;
3074 if rank == 0 {
3075 out.clear();
3077 out.resize(chunk_bytes, 0);
3078 if source.len() >= element_size {
3079 out[..element_size].copy_from_slice(&source[..element_size]);
3080 }
3081 return;
3082 }
3083
3084 let mut extent = vec![0u64; rank];
3087 for d in 0..rank {
3088 let start = coords[d] * chunk_dims[d];
3089 let end = ((coords[d] + 1) * chunk_dims[d]).min(dims[d]);
3090 extent[d] = end.saturating_sub(start);
3091 }
3092 if out.len() != chunk_bytes {
3097 out.clear();
3098 out.resize(chunk_bytes, 0);
3099 } else if extent != chunk_dims {
3100 out.fill(0);
3101 }
3102 if extent.contains(&0) {
3103 return; }
3105
3106 let mut src_stride = vec![1u64; rank];
3109 let mut dst_stride = vec![1u64; rank];
3110 for d in (0..rank - 1).rev() {
3111 src_stride[d] = src_stride[d + 1] * dims[d + 1];
3112 dst_stride[d] = dst_stride[d + 1] * chunk_dims[d + 1];
3113 }
3114
3115 let last = rank - 1;
3117 let run = extent[last] as usize * element_size;
3118 let outer: u64 = extent[..last].iter().product::<u64>().max(1);
3119 let mut idx = vec![0u64; rank]; for _ in 0..outer {
3121 let mut src_off = 0u64;
3122 let mut dst_off = 0u64;
3123 for d in 0..rank {
3124 let global = coords[d] * chunk_dims[d] + idx[d];
3125 src_off += global * src_stride[d];
3126 dst_off += idx[d] * dst_stride[d];
3127 }
3128 let s = src_off as usize * element_size;
3129 let dpos = dst_off as usize * element_size;
3130 out[dpos..dpos + run].copy_from_slice(&source[s..s + run]);
3131
3132 let mut d = last;
3135 while d > 0 {
3136 d -= 1;
3137 idx[d] += 1;
3138 if idx[d] < extent[d] {
3139 break;
3140 }
3141 idx[d] = 0;
3142 }
3143 }
3144 }
3145
3146 pub fn write_chunk(&self, chunk_idx: usize, data: &[u8]) -> Result<()> {
3154 match &self.info {
3155 DatasetInfo::Writer {
3156 index, chunk_index, ..
3157 } => {
3158 let Some(kind) = *chunk_index else {
3159 return Err(Hdf5Error::InvalidState(
3160 "write_chunk is only for chunked datasets".into(),
3161 ));
3162 };
3163 if kind == ChunkIndexKind::BtreeV2 {
3164 return Err(Hdf5Error::InvalidState(
3165 "this dataset uses a v2 B-tree chunk index; use write_chunk_at \
3166 with the chunk's grid coordinates"
3167 .into(),
3168 ));
3169 }
3170
3171 let inner = borrow_inner(&self.file_inner);
3172 match &*inner {
3173 H5FileInner::Writer(writer) => {
3174 let cell = writer.ds(*index);
3177 let _op = cell.op.lock();
3178 match kind {
3179 ChunkIndexKind::FixedArray
3183 | ChunkIndexKind::Implicit
3184 | ChunkIndexKind::SingleChunk
3185 | ChunkIndexKind::BtreeV1 => {
3186 let coords =
3187 writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3188 writer.write_chunk_at_coords(*index, &coords, data)?;
3189 }
3190 _ => writer.write_chunk_inner(*index, chunk_idx as u64, data)?,
3191 }
3192 Ok(())
3193 }
3194 _ => Err(Hdf5Error::InvalidState(
3195 "file is no longer in write mode".into(),
3196 )),
3197 }
3198 }
3199 DatasetInfo::Reader { .. } => {
3200 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3201 }
3202 }
3203 }
3204
3205 pub fn write_chunk_raw(&self, chunk_idx: usize, data: &[u8], filter_mask: u32) -> Result<()> {
3232 match &self.info {
3233 DatasetInfo::Writer {
3234 index, chunk_index, ..
3235 } => {
3236 let Some(kind) = *chunk_index else {
3237 return Err(Hdf5Error::InvalidState(
3238 "write_chunk_raw is only for chunked datasets".into(),
3239 ));
3240 };
3241 if kind == ChunkIndexKind::BtreeV2 {
3242 return Err(Hdf5Error::InvalidState(
3243 "this dataset uses a v2 B-tree chunk index; use \
3244 write_chunk_raw_at with the chunk's grid coordinates"
3245 .into(),
3246 ));
3247 }
3248 if kind == ChunkIndexKind::Implicit {
3249 return Err(Hdf5Error::InvalidState(
3250 "this dataset uses the implicit chunk index, which stores \
3251 every chunk at its full unfiltered size and has nowhere to \
3252 record a stored size or a filter mask"
3253 .into(),
3254 ));
3255 }
3256
3257 let inner = borrow_inner(&self.file_inner);
3258 match &*inner {
3259 H5FileInner::Writer(writer) => {
3260 let cell = writer.ds(*index);
3263 let _op = cell.op.lock();
3264 if kind == ChunkIndexKind::FixedArray {
3265 let coords = writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3268 writer.write_compressed_chunk_fixed_array_inner(
3269 *index,
3270 &coords,
3271 data,
3272 filter_mask,
3273 )?;
3274 } else if kind == ChunkIndexKind::BtreeV1 {
3275 let coords = writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3278 writer.write_compressed_chunk_btree_v1_inner(
3279 *index,
3280 &coords,
3281 data,
3282 filter_mask,
3283 )?;
3284 } else if kind == ChunkIndexKind::SingleChunk {
3285 let coords = writer.chunk_coords_from_slot(*index, chunk_idx as u64)?;
3289 writer.write_compressed_chunk_single_chunk_inner(
3290 *index,
3291 &coords,
3292 data,
3293 filter_mask,
3294 )?;
3295 } else {
3296 writer.write_compressed_chunk_inner(
3297 *index,
3298 chunk_idx as u64,
3299 data,
3300 filter_mask,
3301 )?;
3302 }
3303 Ok(())
3304 }
3305 _ => Err(Hdf5Error::InvalidState(
3306 "file is no longer in write mode".into(),
3307 )),
3308 }
3309 }
3310 DatasetInfo::Reader { .. } => {
3311 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3312 }
3313 }
3314 }
3315
3316 pub fn write_chunk_at(&self, chunk_coords: &[usize], data: &[u8]) -> Result<()> {
3337 self.write_chunk_at_inner(chunk_coords, ChunkBytes::Unfiltered(data), "write_chunk_at")
3338 }
3339
3340 pub fn write_chunk_raw_at(
3358 &self,
3359 chunk_coords: &[usize],
3360 data: &[u8],
3361 filter_mask: u32,
3362 ) -> Result<()> {
3363 self.write_chunk_at_inner(
3364 chunk_coords,
3365 ChunkBytes::Prefiltered { data, filter_mask },
3366 "write_chunk_raw_at",
3367 )
3368 }
3369
3370 fn write_chunk_at_inner(
3376 &self,
3377 chunk_coords: &[usize],
3378 bytes: ChunkBytes<'_>,
3379 what: &str,
3380 ) -> Result<()> {
3381 match &self.info {
3382 DatasetInfo::Writer {
3383 index, chunk_index, ..
3384 } => {
3385 let Some(kind) = *chunk_index else {
3386 return Err(Hdf5Error::InvalidState(format!(
3387 "{what} is only for chunked datasets"
3388 )));
3389 };
3390 let coords: Vec<u64> = chunk_coords.iter().map(|&c| c as u64).collect();
3391 let inner = borrow_inner(&self.file_inner);
3392 let writer = match &*inner {
3393 H5FileInner::Writer(w) => w,
3394 _ => {
3395 return Err(Hdf5Error::InvalidState(
3396 "file is no longer in write mode".into(),
3397 ))
3398 }
3399 };
3400 let cell = writer.ds(*index);
3404 let _op = cell.op.lock();
3405 let chunk_dims = writer
3406 .dataset_chunk_dims(*index)
3407 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
3408 .to_vec();
3409 let dims = writer.dataset_dims(*index).to_vec();
3410 if coords.len() != dims.len() {
3411 return Err(Hdf5Error::InvalidState(format!(
3412 "chunk_coords has {} entries but the dataset has {} dimensions",
3413 coords.len(),
3414 dims.len()
3415 )));
3416 }
3417 if chunk_dims.len() != dims.len() {
3418 return Err(Hdf5Error::InvalidState(format!(
3419 "dataset chunk shape has {} dimensions but the dataspace has {}",
3420 chunk_dims.len(),
3421 dims.len()
3422 )));
3423 }
3424
3425 if kind == ChunkIndexKind::FixedArray {
3426 match bytes {
3428 ChunkBytes::Unfiltered(data) => {
3429 writer.write_chunk_fixed_array_inner(*index, &coords, data)?
3430 }
3431 ChunkBytes::Prefiltered { data, filter_mask } => writer
3432 .write_compressed_chunk_fixed_array_inner(
3433 *index,
3434 &coords,
3435 data,
3436 filter_mask,
3437 )?,
3438 }
3439 return Ok(());
3440 }
3441
3442 if kind == ChunkIndexKind::Implicit {
3443 match bytes {
3446 ChunkBytes::Unfiltered(data) => {
3447 writer.write_chunk_implicit_inner(*index, &coords, data)?
3448 }
3449 ChunkBytes::Prefiltered { .. } => {
3450 return Err(Hdf5Error::InvalidState(
3451 "this dataset uses the implicit chunk index, which stores \
3452 every chunk at its full unfiltered size and has nowhere \
3453 to record a stored size or a filter mask"
3454 .into(),
3455 ))
3456 }
3457 }
3458 return Ok(());
3459 }
3460
3461 if kind == ChunkIndexKind::SingleChunk {
3462 match bytes {
3465 ChunkBytes::Unfiltered(data) => {
3466 writer.write_chunk_single_chunk_inner(*index, &coords, data)?
3467 }
3468 ChunkBytes::Prefiltered { data, filter_mask } => writer
3469 .write_compressed_chunk_single_chunk_inner(
3470 *index,
3471 &coords,
3472 data,
3473 filter_mask,
3474 )?,
3475 }
3476 return Ok(());
3477 }
3478
3479 let max_dims = writer.dataset_max_dims(*index);
3491 let mut new_dims = dims.clone();
3492 for d in 0..dims.len() {
3493 let needed = coords[d]
3494 .checked_add(1)
3495 .and_then(|c| c.checked_mul(chunk_dims[d]))
3496 .ok_or_else(|| {
3497 Hdf5Error::InvalidState(format!(
3498 "chunk coordinate {} in dimension {} is too large",
3499 coords[d], d
3500 ))
3501 })?;
3502 let needed = needed.min(max_dims[d]);
3503 if needed > new_dims[d] {
3504 new_dims[d] = needed;
3505 }
3506 }
3507
3508 if kind == ChunkIndexKind::BtreeV2 {
3509 match bytes {
3510 ChunkBytes::Unfiltered(data) => {
3511 writer.write_chunk_btree_v2_inner(*index, &coords, data)?
3512 }
3513 ChunkBytes::Prefiltered { data, filter_mask } => writer
3514 .write_compressed_chunk_btree_v2_inner(
3515 *index,
3516 &coords,
3517 data,
3518 filter_mask,
3519 )?,
3520 }
3521 } else if kind == ChunkIndexKind::BtreeV1 {
3522 match bytes {
3526 ChunkBytes::Unfiltered(data) => {
3527 writer.write_chunk_btree_v1_inner(*index, &coords, data)?
3528 }
3529 ChunkBytes::Prefiltered { data, filter_mask } => writer
3530 .write_compressed_chunk_btree_v1_inner(
3531 *index,
3532 &coords,
3533 data,
3534 filter_mask,
3535 )?,
3536 }
3537 } else {
3538 let linear = writer.chunk_slot(*index, &coords)?;
3541 match bytes {
3542 ChunkBytes::Unfiltered(data) => {
3543 writer.write_chunk_inner(*index, linear, data)?
3544 }
3545 ChunkBytes::Prefiltered { data, filter_mask } => writer
3546 .write_compressed_chunk_inner(*index, linear, data, filter_mask)?,
3547 }
3548 }
3549
3550 if new_dims != dims {
3551 writer.extend_dataset_inner(*index, &new_dims)?;
3552 }
3553 Ok(())
3554 }
3555 DatasetInfo::Reader { .. } => {
3556 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3557 }
3558 }
3559 }
3560
3561 pub fn write_chunks_batch(&self, chunks: &[(usize, &[u8])]) -> Result<()> {
3567 match &self.info {
3568 DatasetInfo::Writer {
3569 index, chunk_index, ..
3570 } => {
3571 if chunk_index.is_none() {
3572 return Err(Hdf5Error::InvalidState(
3573 "write_chunks_batch is only for chunked datasets".into(),
3574 ));
3575 }
3576 let pairs: Vec<(u64, &[u8])> = chunks
3577 .iter()
3578 .map(|(idx, data)| (*idx as u64, *data))
3579 .collect();
3580 let inner = borrow_inner(&self.file_inner);
3581 match &*inner {
3582 H5FileInner::Writer(writer) => {
3583 writer.write_chunks_batch(*index, &pairs)?;
3584 Ok(())
3585 }
3586 _ => Err(Hdf5Error::InvalidState(
3587 "file is no longer in write mode".into(),
3588 )),
3589 }
3590 }
3591 DatasetInfo::Reader { .. } => {
3592 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
3593 }
3594 }
3595 }
3596
3597 pub fn append<T: H5Type>(&self, data: &[T]) -> Result<()> {
3620 match &self.info {
3621 DatasetInfo::Writer {
3622 index,
3623 element_size,
3624 chunk_index,
3625 ..
3626 } => {
3627 if chunk_index.is_none() {
3628 return Err(Hdf5Error::InvalidState(
3629 "append is only for chunked datasets".into(),
3630 ));
3631 }
3632 if T::element_size() != *element_size {
3633 return Err(Hdf5Error::TypeMismatch(format!(
3634 "append type has element size {} but dataset expects {}",
3635 T::element_size(),
3636 element_size,
3637 )));
3638 }
3639
3640 let ds_index = *index;
3641 let es = *element_size;
3642
3643 let inner = borrow_inner(&self.file_inner);
3644 let writer = match &*inner {
3645 H5FileInner::Writer(w) => w,
3646 _ => {
3647 return Err(Hdf5Error::InvalidState(
3648 "file is no longer in write mode".into(),
3649 ))
3650 }
3651 };
3652
3653 let cell = writer.ds(ds_index);
3658 let _op = cell.op.lock();
3659
3660 let chunk_dims = writer
3661 .dataset_chunk_dims(ds_index)
3662 .ok_or_else(|| Hdf5Error::InvalidState("dataset has no chunk info".into()))?
3663 .to_vec();
3664 let dims = writer.dataset_dims(ds_index).to_vec();
3665
3666 let frame_elems: usize = if dims.len() > 1 {
3668 dims[1..].iter().map(|&d| d as usize).product()
3669 } else {
3670 1
3671 };
3672
3673 if frame_elems == 0 {
3674 return Err(Hdf5Error::InvalidState(
3675 "cannot append to dataset with zero-size trailing dimensions".into(),
3676 ));
3677 }
3678
3679 if !data.len().is_multiple_of(frame_elems) {
3680 return Err(Hdf5Error::InvalidState(format!(
3681 "data length {} is not a multiple of frame size {}",
3682 data.len(),
3683 frame_elems,
3684 )));
3685 }
3686
3687 let n_new_frames = data.len() / frame_elems;
3688 let current_dim0 = dims[0] as usize;
3689
3690 let chunk_dim0 = chunk_dims[0] as usize;
3692 let frame_bytes = frame_elems * es;
3693
3694 let host = unsafe {
3695 std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * es)
3696 };
3697 let datatype = writer.dataset_datatype(ds_index);
3698 let raw = to_stored_byte_order(host, &datatype, es)?;
3699
3700 let taken = { writer.ds(ds_index).lock().append.take() };
3705 let (base_dim0, buffered_frames, mut combined) = match taken {
3706 Some(b) if b.base + b.frames == current_dim0 as u64 => {
3707 (b.base as usize, b.frames as usize, b.bytes)
3708 }
3709 Some(b) => {
3710 writer.write_append_frames(ds_index, b.base, b.frames, &b.bytes)?;
3711 (current_dim0, 0, Vec::new())
3712 }
3713 None => (current_dim0, 0, Vec::new()),
3714 };
3715 combined.extend_from_slice(&raw);
3716
3717 let total_frames = buffered_frames + n_new_frames;
3718
3719 let last_boundary = ((base_dim0 + total_frames) / chunk_dim0) * chunk_dim0;
3726 let write_frames = last_boundary.saturating_sub(base_dim0);
3727 let tail_frames = total_frames - write_frames;
3728 if write_frames > 0 {
3729 writer.write_append_frames(
3730 ds_index,
3731 base_dim0 as u64,
3732 write_frames as u64,
3733 &combined[..write_frames * frame_bytes],
3734 )?;
3735 }
3736 if tail_frames > 0 {
3737 let ds = writer.ds(ds_index);
3738 let mut m = ds.lock();
3739 m.append = Some(crate::io::writer::AppendBuffer {
3740 base: (base_dim0 + write_frames) as u64,
3741 frames: tail_frames as u64,
3742 bytes: combined[write_frames * frame_bytes..].to_vec(),
3743 });
3744 }
3745
3746 let logical_dim0 = base_dim0 + total_frames;
3748 let mut new_dims: Vec<u64> = dims;
3749 new_dims[0] = logical_dim0 as u64;
3750 writer.extend_dataset_inner(ds_index, &new_dims)?;
3751
3752 Ok(())
3753 }
3754 DatasetInfo::Reader { .. } => {
3755 Err(Hdf5Error::InvalidState("cannot append in read mode".into()))
3756 }
3757 }
3758 }
3759
3760 pub fn extend(&self, new_dims: &[usize]) -> Result<()> {
3762 match &self.info {
3763 DatasetInfo::Writer {
3764 index, chunk_index, ..
3765 } => {
3766 if chunk_index.is_none() {
3767 return Err(Hdf5Error::InvalidState(
3768 "extend is only for chunked datasets".into(),
3769 ));
3770 }
3771
3772 let dims_u64: Vec<u64> = new_dims.iter().map(|&d| d as u64).collect();
3773 let inner = borrow_inner(&self.file_inner);
3774 match &*inner {
3775 H5FileInner::Writer(writer) => {
3776 writer.extend_dataset(*index, &dims_u64)?;
3777 Ok(())
3778 }
3779 _ => Err(Hdf5Error::InvalidState(
3780 "file is no longer in write mode".into(),
3781 )),
3782 }
3783 }
3784 DatasetInfo::Reader { .. } => {
3785 Err(Hdf5Error::InvalidState("cannot extend in read mode".into()))
3786 }
3787 }
3788 }
3789
3790 pub fn set_extent(&self, new_dims: &[usize]) -> Result<()> {
3804 match &self.info {
3805 DatasetInfo::Writer { index, .. } => {
3806 let dims_u64: Vec<u64> = new_dims.iter().map(|&d| d as u64).collect();
3807 let inner = borrow_inner(&self.file_inner);
3808 match &*inner {
3809 H5FileInner::Writer(writer) => {
3810 writer.set_dataset_extent(*index, &dims_u64)?;
3811 Ok(())
3812 }
3813 _ => Err(Hdf5Error::InvalidState(
3814 "file is no longer in write mode".into(),
3815 )),
3816 }
3817 }
3818 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(
3819 "cannot set extent in read mode".into(),
3820 )),
3821 }
3822 }
3823
3824 pub fn flush(&self) -> Result<()> {
3826 match &self.info {
3827 DatasetInfo::Writer { index, .. } => {
3828 let inner = borrow_inner(&self.file_inner);
3829 match &*inner {
3830 H5FileInner::Writer(writer) => {
3831 writer.flush_dataset(*index)?;
3832 Ok(())
3833 }
3834 _ => Ok(()),
3835 }
3836 }
3837 DatasetInfo::Reader { .. } => Ok(()),
3838 }
3839 }
3840
3841 pub fn read_slice<T: H5Type>(&self, starts: &[usize], counts: &[usize]) -> Result<Vec<T>> {
3846 match &self.info {
3847 DatasetInfo::Reader {
3848 name,
3849 shape,
3850 element_size,
3851 } => {
3852 if T::element_size() != *element_size {
3853 return Err(Hdf5Error::TypeMismatch(format!(
3854 "read type has element size {} but dataset has element size {}",
3855 T::element_size(),
3856 element_size,
3857 )));
3858 }
3859 let datatype = self.datatype()?;
3860 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
3861 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
3862
3863 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
3867 check_hyperslab(&dims, &starts_u64, &counts_u64)?;
3868 let count = element_count(&counts_u64)?;
3869 let mut inner = borrow_inner_mut(&self.file_inner);
3870 let H5FileInner::Reader(reader) = &mut *inner else {
3871 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
3872 };
3873 read_image_into_new(count, |image| {
3877 reader.read_slice_into_dst(
3878 name,
3879 &starts_u64,
3880 &counts_u64,
3881 image,
3882 ReadDst::Fresh,
3883 )?;
3884 to_host_byte_order(image, &datatype, T::element_size())
3885 })
3886 }
3887 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
3888 "cannot read_slice from a dataset in write mode".into(),
3889 )),
3890 }
3891 }
3892
3893 pub fn read_hyperslab<T: H5Type>(
3912 &self,
3913 start: &[usize],
3914 stride: &[usize],
3915 count: &[usize],
3916 block: &[usize],
3917 ) -> Result<Vec<T>> {
3918 match &self.info {
3919 DatasetInfo::Reader {
3920 name, element_size, ..
3921 } => {
3922 if T::element_size() != *element_size {
3923 return Err(Hdf5Error::TypeMismatch(format!(
3924 "read type has element size {} but dataset has element size {}",
3925 T::element_size(),
3926 element_size,
3927 )));
3928 }
3929 let datatype = self.datatype()?;
3930 let start_u64: Vec<u64> = start.iter().map(|&s| s as u64).collect();
3931 let stride_u64: Vec<u64> = stride.iter().map(|&s| s as u64).collect();
3932 let count_u64: Vec<u64> = count.iter().map(|&c| c as u64).collect();
3933 let block_u64: Vec<u64> = block.iter().map(|&b| b as u64).collect();
3934
3935 let selected: Vec<u64> = count_u64
3936 .iter()
3937 .zip(&block_u64)
3938 .map(|(&c, &b)| c.saturating_mul(b))
3939 .collect();
3940 let n = element_count(&selected)?;
3941 let mut inner = borrow_inner_mut(&self.file_inner);
3942 let H5FileInner::Reader(reader) = &mut *inner else {
3943 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
3944 };
3945 read_image_into_new(n, |image| {
3946 reader.read_hyperslab_into(
3947 name,
3948 &start_u64,
3949 &stride_u64,
3950 &count_u64,
3951 &block_u64,
3952 image,
3953 )?;
3954 to_host_byte_order(image, &datatype, T::element_size())
3955 })
3956 }
3957 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
3958 "cannot read_hyperslab from a dataset in write mode".into(),
3959 )),
3960 }
3961 }
3962
3963 pub fn read_points<T: H5Type>(&self, points: &[Vec<usize>]) -> Result<Vec<T>> {
3978 match &self.info {
3979 DatasetInfo::Reader {
3980 name, element_size, ..
3981 } => {
3982 if T::element_size() != *element_size {
3983 return Err(Hdf5Error::TypeMismatch(format!(
3984 "read type has element size {} but dataset has element size {}",
3985 T::element_size(),
3986 element_size,
3987 )));
3988 }
3989 let datatype = self.datatype()?;
3990 let points_u64: Vec<Vec<u64>> = points
3991 .iter()
3992 .map(|p| p.iter().map(|&c| c as u64).collect())
3993 .collect();
3994
3995 let mut inner = borrow_inner_mut(&self.file_inner);
3996 let H5FileInner::Reader(reader) = &mut *inner else {
3997 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
3998 };
3999 read_image_into_new(points_u64.len(), |image| {
4000 reader.read_points_into(name, &points_u64, image, ReadDst::Fresh)?;
4001 to_host_byte_order(image, &datatype, T::element_size())
4002 })
4003 }
4004 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4005 "cannot read_points from a dataset in write mode".into(),
4006 )),
4007 }
4008 }
4009
4010 pub fn read_chunk_raw_at(&self, chunk_coords: &[usize]) -> Result<(Vec<u8>, u32)> {
4032 match &self.info {
4033 DatasetInfo::Reader { name, .. } => {
4034 let coords_u64: Vec<u64> = chunk_coords.iter().map(|&c| c as u64).collect();
4035 let mut inner = borrow_inner_mut(&self.file_inner);
4036 match &mut *inner {
4037 H5FileInner::Reader(reader) => Ok(reader.read_chunk_raw_at(name, &coords_u64)?),
4038 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4039 }
4040 }
4041 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4042 "cannot read_chunk_raw_at from a dataset in write mode".into(),
4043 )),
4044 }
4045 }
4046
4047 pub fn write_slice<T: H5Type>(
4060 &self,
4061 starts: &[usize],
4062 counts: &[usize],
4063 data: &[T],
4064 ) -> Result<()> {
4065 match &self.info {
4066 DatasetInfo::Writer {
4067 index,
4068 element_size,
4069 ..
4070 } => {
4071 if T::element_size() != *element_size {
4072 return Err(Hdf5Error::TypeMismatch(format!(
4073 "write type has element size {} but dataset expects {}",
4074 T::element_size(),
4075 element_size,
4076 )));
4077 }
4078
4079 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
4080 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
4081
4082 let inner = borrow_inner(&self.file_inner);
4083 let H5FileInner::Writer(writer) = &*inner else {
4084 return Err(Hdf5Error::InvalidState(
4085 "file is no longer in write mode".into(),
4086 ));
4087 };
4088
4089 check_hyperslab(&writer.dataset_dims(*index), &starts_u64, &counts_u64)?;
4094 let expected = element_count(&counts_u64)?;
4095 if data.len() != expected {
4096 return Err(Hdf5Error::InvalidState(format!(
4097 "data length {} does not match slice size {}",
4098 data.len(),
4099 expected,
4100 )));
4101 }
4102
4103 let byte_len = data.len() * T::element_size();
4104 let host =
4105 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) };
4106
4107 let datatype = writer.dataset_datatype(*index);
4108 let stored = to_stored_byte_order(host, &datatype, T::element_size())?;
4109 writer.write_slice(*index, &starts_u64, &counts_u64, &stored)?;
4110 Ok(())
4111 }
4112 DatasetInfo::Reader { .. } => {
4113 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4114 }
4115 }
4116 }
4117
4118 pub fn write_vlen_strings_slice(&self, start: usize, strings: &[&str]) -> Result<()> {
4142 match &self.info {
4143 DatasetInfo::Writer { index, .. } => {
4144 let inner = borrow_inner(&self.file_inner);
4145 match &*inner {
4146 H5FileInner::Writer(writer) => {
4147 writer.write_vlen_strings_slice(*index, start as u64, strings)?;
4148 Ok(())
4149 }
4150 _ => Err(Hdf5Error::InvalidState(
4151 "file is no longer in write mode".into(),
4152 )),
4153 }
4154 }
4155 DatasetInfo::Reader { .. } => {
4156 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4157 }
4158 }
4159 }
4160
4161 pub fn read_vlen_strings(&self) -> Result<Vec<String>> {
4166 match &self.info {
4167 DatasetInfo::Reader { name, .. } => {
4168 let mut inner = borrow_inner_mut(&self.file_inner);
4169 match &mut *inner {
4170 H5FileInner::Reader(reader) => Ok(reader.read_vlen_strings(name)?),
4171 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4172 }
4173 }
4174 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4175 "cannot read vlen strings from a dataset in write mode".into(),
4176 )),
4177 }
4178 }
4179
4180 pub fn read_vlen_bytes(&self) -> Result<Vec<Vec<u8>>> {
4187 match &self.info {
4188 DatasetInfo::Reader { name, .. } => {
4189 let mut inner = borrow_inner_mut(&self.file_inner);
4190 match &mut *inner {
4191 H5FileInner::Reader(reader) => Ok(reader.read_vlen_bytes(name)?),
4192 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4193 }
4194 }
4195 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4196 "cannot read vlen bytes from a dataset in write mode".into(),
4197 )),
4198 }
4199 }
4200
4201 pub fn write_object_references(&self, paths: &[&str]) -> Result<()> {
4211 match &self.info {
4212 DatasetInfo::Writer { index, .. } => {
4213 let inner = borrow_inner(&self.file_inner);
4214 match &*inner {
4215 H5FileInner::Writer(writer) => {
4216 writer.write_object_references(*index, 0, paths)?;
4217 Ok(())
4218 }
4219 _ => Err(Hdf5Error::InvalidState(
4220 "file is no longer in write mode".into(),
4221 )),
4222 }
4223 }
4224 DatasetInfo::Reader { .. } => {
4225 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4226 }
4227 }
4228 }
4229
4230 pub fn set_scale(&self, name: Option<&str>) -> Result<()> {
4246 let index = self.writer_index("set_scale")?;
4247 let inner = borrow_inner(&self.file_inner);
4248 match &*inner {
4249 H5FileInner::Writer(writer) => Ok(writer.set_dimension_scale(index, name)?),
4250 _ => Err(Hdf5Error::InvalidState(
4251 "file is no longer in write mode".into(),
4252 )),
4253 }
4254 }
4255
4256 pub fn attach_scale(&self, axis: usize, scale: &H5Dataset) -> Result<()> {
4276 let did = self.writer_index("attach_scale")?;
4277 let dsid = scale.writer_index("attach_scale")?;
4278 if !same_inner(&self.file_inner, &scale.file_inner) {
4279 return Err(Hdf5Error::InvalidState(
4280 "the dimension scale belongs to another file".into(),
4281 ));
4282 }
4283 let inner = borrow_inner(&self.file_inner);
4284 match &*inner {
4285 H5FileInner::Writer(writer) => Ok(writer.attach_dimension_scale(did, dsid, axis)?),
4286 _ => Err(Hdf5Error::InvalidState(
4287 "file is no longer in write mode".into(),
4288 )),
4289 }
4290 }
4291
4292 fn writer_index(&self, what: &str) -> Result<usize> {
4295 match &self.info {
4296 DatasetInfo::Writer { index, .. } => Ok(*index),
4297 DatasetInfo::Reader { .. } => Err(Hdf5Error::InvalidState(format!(
4298 "{what} is only available in write mode"
4299 ))),
4300 }
4301 }
4302
4303 pub fn write_region_references(&self, targets: &[(&str, Selection)]) -> Result<()> {
4331 match &self.info {
4332 DatasetInfo::Writer { index, .. } => {
4333 let inner = borrow_inner(&self.file_inner);
4334 match &*inner {
4335 H5FileInner::Writer(writer) => {
4336 writer.write_region_references(*index, 0, targets)?;
4337 Ok(())
4338 }
4339 _ => Err(Hdf5Error::InvalidState(
4340 "file is no longer in write mode".into(),
4341 )),
4342 }
4343 }
4344 DatasetInfo::Reader { .. } => {
4345 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4346 }
4347 }
4348 }
4349
4350 pub fn write_std_region_references(&self, targets: &[(&str, Selection)]) -> Result<()> {
4364 let targets: Vec<(&str, ReferenceTarget)> = targets
4365 .iter()
4366 .map(|(path, selection)| (*path, ReferenceTarget::Region(selection.clone())))
4367 .collect();
4368 self.write_revised_references(&targets)
4369 }
4370
4371 pub fn write_attribute_references(&self, targets: &[(&str, &str)]) -> Result<()> {
4382 let targets: Vec<(&str, ReferenceTarget)> = targets
4383 .iter()
4384 .map(|(path, name)| (*path, ReferenceTarget::Attribute((*name).to_string())))
4385 .collect();
4386 self.write_revised_references(&targets)
4387 }
4388
4389 fn write_revised_references(&self, targets: &[(&str, ReferenceTarget)]) -> Result<()> {
4392 match &self.info {
4393 DatasetInfo::Writer { index, .. } => {
4394 let inner = borrow_inner(&self.file_inner);
4395 match &*inner {
4396 H5FileInner::Writer(writer) => {
4397 writer.write_revised_references(*index, 0, targets)?;
4398 Ok(())
4399 }
4400 _ => Err(Hdf5Error::InvalidState(
4401 "file is no longer in write mode".into(),
4402 )),
4403 }
4404 }
4405 DatasetInfo::Reader { .. } => {
4406 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
4407 }
4408 }
4409 }
4410
4411 pub fn read_references(&self) -> Result<Vec<Reference>> {
4438 match &self.info {
4439 DatasetInfo::Reader { name, .. } => {
4440 let mut inner = borrow_inner_mut(&self.file_inner);
4441 match &mut *inner {
4442 H5FileInner::Reader(reader) => Ok(reader.read_references(name)?),
4443 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4444 }
4445 }
4446 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4447 "cannot read references from a dataset in write mode".into(),
4448 )),
4449 }
4450 }
4451
4452 pub fn read_strings(&self) -> Result<Vec<String>> {
4471 self.read_strings_inner(false)
4472 }
4473
4474 pub fn read_strings_lossy(&self) -> Result<Vec<String>> {
4480 self.read_strings_inner(true)
4481 }
4482
4483 fn read_strings_inner(&self, lossy: bool) -> Result<Vec<String>> {
4487 if matches!(self.info, DatasetInfo::Writer { .. }) {
4488 return Err(Hdf5Error::InvalidState(
4489 "cannot read strings from a dataset in write mode".into(),
4490 ));
4491 }
4492 match self.datatype()? {
4493 DatatypeMessage::VarLenString { charset, .. } => self
4494 .read_vlen_bytes()?
4495 .iter()
4496 .enumerate()
4497 .map(|(i, bytes)| decode_string(bytes, charset, lossy, i))
4498 .collect(),
4499 DatatypeMessage::FixedString {
4500 size,
4501 padding,
4502 charset,
4503 } => {
4504 let width = size as usize;
4505 if width == 0 {
4506 return Err(Hdf5Error::InvalidState(
4508 "fixed-string datatype has zero width".into(),
4509 ));
4510 }
4511 let raw = self.read_raw_bytes()?;
4514 raw.chunks_exact(width)
4515 .enumerate()
4516 .map(|(i, elem)| {
4517 decode_string(trim_fixed_string(elem, padding, i)?, charset, lossy, i)
4518 })
4519 .collect()
4520 }
4521 other => Err(Hdf5Error::InvalidState(format!(
4522 "read_strings is only for string datasets, this one is {other:?}"
4523 ))),
4524 }
4525 }
4526
4527 pub fn read_raw<T: H5Type>(&self) -> Result<Vec<T>> {
4539 match &self.info {
4540 DatasetInfo::Reader {
4541 name, element_size, ..
4542 } => {
4543 if T::element_size() != *element_size {
4544 return Err(Hdf5Error::TypeMismatch(format!(
4545 "read type has element size {} but dataset has element size {}",
4546 T::element_size(),
4547 element_size,
4548 )));
4549 }
4550
4551 let datatype = self.datatype()?;
4552 let mut inner = borrow_inner_mut(&self.file_inner);
4553 let H5FileInner::Reader(reader) = &mut *inner else {
4554 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
4555 };
4556 let total = reader.dataset_raw_size(name)? as usize;
4557 if !total.is_multiple_of(T::element_size()) {
4558 return Err(Hdf5Error::TypeMismatch(format!(
4559 "raw data size {total} is not a multiple of element size {}",
4560 T::element_size(),
4561 )));
4562 }
4563
4564 read_image_into_new(total / T::element_size(), |image| {
4568 reader.read_dataset_raw_into_dst(name, image, ReadDst::Fresh)?;
4569 to_host_byte_order(image, &datatype, T::element_size())
4570 })
4571 }
4572 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4573 "cannot read from a dataset in write mode".into(),
4574 )),
4575 }
4576 }
4577
4578 #[cfg(feature = "mmap")]
4641 pub fn read_mapped<T: H5Type>(&self) -> Result<crate::mapped::MappedView<T>> {
4642 self.mapped_view(crate::mapped::ViewRange::Whole)
4643 }
4644
4645 #[cfg(feature = "mmap")]
4662 pub fn read_mapped_slice<T: H5Type>(
4663 &self,
4664 starts: &[usize],
4665 counts: &[usize],
4666 ) -> Result<crate::mapped::MappedView<T>> {
4667 self.mapped_view(crate::mapped::ViewRange::Slab { starts, counts })
4668 }
4669
4670 #[cfg(feature = "mmap")]
4675 fn mapped_view<T: H5Type>(
4676 &self,
4677 range: crate::mapped::ViewRange<'_>,
4678 ) -> Result<crate::mapped::MappedView<T>> {
4679 let DatasetInfo::Reader { name, .. } = &self.info else {
4680 return Err(Hdf5Error::InvalidState(
4681 "cannot read from a dataset in write mode".into(),
4682 ));
4683 };
4684 let mut inner = borrow_inner_mut(&self.file_inner);
4685 let H5FileInner::Reader(reader) = &mut *inner else {
4686 return Err(Hdf5Error::InvalidState("file is not in read mode".into()));
4687 };
4688 let src = reader.dataset_view_source(name)?;
4689 crate::mapped::view::<T>(&src, range).map_err(Hdf5Error::NotViewable)
4690 }
4691
4692 pub fn read_raw_bytes(&self) -> Result<Vec<u8>> {
4699 match &self.info {
4700 DatasetInfo::Reader { name, .. } => {
4701 let mut inner = borrow_inner_mut(&self.file_inner);
4702 match &mut *inner {
4703 H5FileInner::Reader(reader) => Ok(reader.read_dataset_raw(name)?),
4704 _ => Err(Hdf5Error::InvalidState("file is not in read mode".into())),
4705 }
4706 }
4707 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4708 "cannot read from a dataset in write mode".into(),
4709 )),
4710 }
4711 }
4712
4713 pub fn read_numeric_as<T: ReadNumeric>(&self) -> Result<Vec<T>> {
4737 match &self.info {
4738 DatasetInfo::Reader { name, .. } => {
4739 let (kind, raw) = {
4740 let mut inner = borrow_inner_mut(&self.file_inner);
4741 match &mut *inner {
4742 H5FileInner::Reader(reader) => {
4743 let info = reader
4744 .dataset_info(name)
4745 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))?;
4746 let kind = numeric::classify(&info.datatype)?;
4747 (kind, reader.read_dataset_raw(name)?)
4748 }
4749 _ => {
4750 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4751 }
4752 }
4753 };
4754 numeric::convert(kind, &raw)
4755 }
4756 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4757 "cannot read from a dataset in write mode".into(),
4758 )),
4759 }
4760 }
4761
4762 pub fn read_numeric_slice_as<T: ReadNumeric>(
4769 &self,
4770 starts: &[usize],
4771 counts: &[usize],
4772 ) -> Result<Vec<T>> {
4773 match &self.info {
4774 DatasetInfo::Reader { name, .. } => {
4775 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
4776 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
4777 let (kind, raw) = {
4778 let mut inner = borrow_inner_mut(&self.file_inner);
4779 match &mut *inner {
4780 H5FileInner::Reader(reader) => {
4781 let info = reader
4782 .dataset_info(name)
4783 .ok_or_else(|| Hdf5Error::NotFound(name.clone()))?;
4784 let kind = numeric::classify(&info.datatype)?;
4785 (kind, reader.read_slice(name, &starts_u64, &counts_u64)?)
4786 }
4787 _ => {
4788 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4789 }
4790 }
4791 };
4792 numeric::convert(kind, &raw)
4793 }
4794 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4795 "cannot read_slice from a dataset in write mode".into(),
4796 )),
4797 }
4798 }
4799
4800 pub fn read_raw_into<T: H5Type>(&self, out: &mut [T]) -> Result<()> {
4822 match &self.info {
4823 DatasetInfo::Reader {
4824 name, element_size, ..
4825 } => {
4826 if T::element_size() != *element_size {
4827 return Err(Hdf5Error::TypeMismatch(format!(
4828 "read type has element size {} but dataset has element size {}",
4829 T::element_size(),
4830 element_size,
4831 )));
4832 }
4833 let datatype = self.datatype()?;
4834 let byte_len = out.len() * T::element_size();
4840 let bytes = unsafe {
4841 std::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, byte_len)
4842 };
4843 {
4844 let mut inner = borrow_inner_mut(&self.file_inner);
4845 match &mut *inner {
4846 H5FileInner::Reader(reader) => reader.read_dataset_raw_into(name, bytes)?,
4847 _ => {
4848 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4849 }
4850 }
4851 }
4852 to_host_byte_order(bytes, &datatype, T::element_size())
4853 }
4854 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4855 "cannot read from a dataset in write mode".into(),
4856 )),
4857 }
4858 }
4859
4860 pub fn read_slice_into<T: H5Type>(
4879 &self,
4880 out: &mut [T],
4881 starts: &[usize],
4882 counts: &[usize],
4883 ) -> Result<()> {
4884 match &self.info {
4885 DatasetInfo::Reader {
4886 name, element_size, ..
4887 } => {
4888 if T::element_size() != *element_size {
4889 return Err(Hdf5Error::TypeMismatch(format!(
4890 "read type has element size {} but dataset has element size {}",
4891 T::element_size(),
4892 element_size,
4893 )));
4894 }
4895 let datatype = self.datatype()?;
4896 let starts_u64: Vec<u64> = starts.iter().map(|&s| s as u64).collect();
4897 let counts_u64: Vec<u64> = counts.iter().map(|&c| c as u64).collect();
4898 let byte_len = out.len() * T::element_size();
4901 let bytes = unsafe {
4902 std::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, byte_len)
4903 };
4904 {
4905 let mut inner = borrow_inner_mut(&self.file_inner);
4906 match &mut *inner {
4907 H5FileInner::Reader(reader) => {
4908 reader.read_slice_into(name, &starts_u64, &counts_u64, bytes)?
4909 }
4910 _ => {
4911 return Err(Hdf5Error::InvalidState("file is not in read mode".into()))
4912 }
4913 }
4914 }
4915 to_host_byte_order(bytes, &datatype, T::element_size())
4916 }
4917 DatasetInfo::Writer { .. } => Err(Hdf5Error::InvalidState(
4918 "cannot read from a dataset in write mode".into(),
4919 )),
4920 }
4921 }
4922}
4923
4924pub trait ReadNumeric: numeric::Sealed {}
4935impl<T: numeric::Sealed> ReadNumeric for T {}
4936
4937pub(crate) mod numeric {
4938 use crate::error::{Hdf5Error, Result};
4942 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage, IeeeFormat};
4943
4944 #[derive(Clone, Copy)]
4946 pub enum SourceKind {
4947 Int {
4948 size: usize,
4949 signed: bool,
4950 byte_order: ByteOrder,
4951 },
4952 F16(ByteOrder),
4953 F32(ByteOrder),
4954 F64(ByteOrder),
4955 }
4956
4957 impl SourceKind {
4958 fn element_size(self) -> usize {
4959 match self {
4960 SourceKind::Int { size, .. } => size,
4961 SourceKind::F16(_) => 2,
4962 SourceKind::F32(_) => 4,
4963 SourceKind::F64(_) => 8,
4964 }
4965 }
4966 }
4967
4968 fn f16_bits_to_f32(bits: u16) -> f32 {
4973 let sign = u32::from(bits >> 15);
4974 let exponent = u32::from((bits >> 10) & 0x1f);
4975 let mantissa = u32::from(bits & 0x03ff);
4976 if exponent == 0 {
4977 let magnitude = mantissa as f32 * (1.0 / 16_777_216.0);
4981 return if sign == 1 { -magnitude } else { magnitude };
4982 }
4983 let out = if exponent == 0x1f {
4984 (sign << 31) | 0x7f80_0000 | (mantissa << 13)
4987 } else {
4988 (sign << 31) | ((exponent + 112) << 23) | (mantissa << 13)
4991 };
4992 f32::from_bits(out)
4993 }
4994
4995 pub fn classify(dt: &DatatypeMessage) -> Result<SourceKind> {
5001 match *dt {
5002 DatatypeMessage::FixedPoint {
5003 size,
5004 byte_order,
5005 signed,
5006 bit_offset,
5007 bit_precision,
5008 } => {
5009 if !matches!(size, 1 | 2 | 4 | 8)
5010 || bit_offset != 0
5011 || u32::from(bit_precision) != size * 8
5012 {
5013 return Err(Hdf5Error::TypeMismatch(format!(
5014 "fixed-point datatype (size {size}, bit offset {bit_offset}, \
5015 precision {bit_precision}) is not a standard-width integer",
5016 )));
5017 }
5018 Ok(SourceKind::Int {
5019 size: size as usize,
5020 signed,
5021 byte_order,
5022 })
5023 }
5024 DatatypeMessage::BitField {
5025 size,
5026 byte_order,
5027 bit_offset,
5028 bit_precision,
5029 } => {
5030 if !matches!(size, 1 | 2 | 4 | 8)
5034 || bit_offset != 0
5035 || u32::from(bit_precision) != size * 8
5036 {
5037 return Err(Hdf5Error::TypeMismatch(format!(
5038 "bit-field datatype (size {size}, bit offset {bit_offset}, \
5039 precision {bit_precision}) is not a whole-width bit field",
5040 )));
5041 }
5042 Ok(SourceKind::Int {
5043 size: size as usize,
5044 signed: false,
5045 byte_order,
5046 })
5047 }
5048 DatatypeMessage::FloatingPoint {
5049 size,
5050 byte_order,
5051 exponent_size,
5052 mantissa_size,
5053 ..
5054 } => match dt.ieee_format() {
5055 Some(IeeeFormat::Binary16) => Ok(SourceKind::F16(byte_order)),
5056 Some(IeeeFormat::Binary32) => Ok(SourceKind::F32(byte_order)),
5057 Some(IeeeFormat::Binary64) => Ok(SourceKind::F64(byte_order)),
5058 None => Err(Hdf5Error::TypeMismatch(format!(
5059 "floating-point datatype (size {size}, exponent {exponent_size} bits, \
5060 mantissa {mantissa_size} bits) is not an IEEE 754 interchange format",
5061 ))),
5062 },
5063 ref other => Err(Hdf5Error::TypeMismatch(format!(
5064 "dataset datatype '{other}' is not numeric",
5065 ))),
5066 }
5067 }
5068
5069 pub fn convert<T: Sealed>(kind: SourceKind, raw: &[u8]) -> Result<Vec<T>> {
5077 let size = kind.element_size();
5078 if !raw.len().is_multiple_of(size) {
5079 return Err(Hdf5Error::TypeMismatch(format!(
5080 "raw data size {} is not a multiple of element size {size}",
5081 raw.len(),
5082 )));
5083 }
5084 let mut out = vec![T::default(); raw.len() / size];
5085 match kind {
5086 SourceKind::Int {
5087 size,
5088 signed,
5089 byte_order,
5090 } => {
5091 let le = byte_order == ByteOrder::LittleEndian;
5092 match (size, signed) {
5093 (1, true) => ints::<1, i64, T>(raw, le, &mut out),
5094 (1, false) => ints::<1, u64, T>(raw, le, &mut out),
5095 (2, true) => ints::<2, i64, T>(raw, le, &mut out),
5096 (2, false) => ints::<2, u64, T>(raw, le, &mut out),
5097 (4, true) => ints::<4, i64, T>(raw, le, &mut out),
5098 (4, false) => ints::<4, u64, T>(raw, le, &mut out),
5099 (8, true) => ints::<8, i64, T>(raw, le, &mut out),
5100 (8, false) => ints::<8, u64, T>(raw, le, &mut out),
5101 _ => unreachable!("classify admits 1-, 2-, 4- and 8-byte integers only"),
5102 }
5103 }
5104 SourceKind::F16(order) => floats::<2, T>(raw, order, &mut out, |b| {
5105 T::from_f32(f16_bits_to_f32(u16::from_le_bytes(b)))
5106 }),
5107 SourceKind::F32(order) => {
5108 floats::<4, T>(raw, order, &mut out, |b| T::from_f32(f32::from_le_bytes(b)))
5109 }
5110 SourceKind::F64(order) => {
5111 floats::<8, T>(raw, order, &mut out, |b| T::from_f64(f64::from_le_bytes(b)))
5112 }
5113 }?;
5114 Ok(out)
5115 }
5116
5117 fn ints<const N: usize, L: IntLane, T: Sealed>(
5119 raw: &[u8],
5120 le: bool,
5121 out: &mut [T],
5122 ) -> Result<()> {
5123 let (chunks, _) = raw.as_chunks::<N>();
5124 for (index, (o, &chunk)) in out.iter_mut().zip(chunks).enumerate() {
5125 let mut bytes = chunk;
5126 if !le {
5127 bytes.reverse();
5128 }
5129 *o = L::load(bytes).into_target(index)?;
5130 }
5131 Ok(())
5132 }
5133
5134 fn floats<const N: usize, T: Sealed>(
5137 raw: &[u8],
5138 order: ByteOrder,
5139 out: &mut [T],
5140 decode: impl Fn([u8; N]) -> Result<T>,
5141 ) -> Result<()> {
5142 let (chunks, _) = raw.as_chunks::<N>();
5143 for (o, &chunk) in out.iter_mut().zip(chunks) {
5144 let mut bytes = chunk;
5145 if order == ByteOrder::BigEndian {
5146 bytes.reverse();
5147 }
5148 *o = decode(bytes)?;
5149 }
5150 Ok(())
5151 }
5152
5153 pub trait IntLane: Copy {
5157 fn load<const N: usize>(bytes: [u8; N]) -> Self;
5159 fn into_target<T: Sealed>(self, index: usize) -> Result<T>;
5160 }
5161
5162 impl IntLane for u64 {
5163 fn load<const N: usize>(bytes: [u8; N]) -> Self {
5164 let mut padded = [0u8; 8];
5165 padded[..N].copy_from_slice(&bytes);
5166 u64::from_le_bytes(padded)
5167 }
5168 fn into_target<T: Sealed>(self, index: usize) -> Result<T> {
5169 T::from_u64(self, index)
5170 }
5171 }
5172
5173 impl IntLane for i64 {
5174 fn load<const N: usize>(bytes: [u8; N]) -> Self {
5175 let shift = 64 - 8 * N as u32;
5177 ((u64::load(bytes) << shift) as i64) >> shift
5178 }
5179 fn into_target<T: Sealed>(self, index: usize) -> Result<T> {
5180 T::from_i64(self, index)
5181 }
5182 }
5183
5184 pub trait Sealed: Copy + Default {
5188 fn from_i64(v: i64, index: usize) -> Result<Self>;
5189 fn from_u64(v: u64, index: usize) -> Result<Self>;
5190 fn from_f32(v: f32) -> Result<Self>;
5191 fn from_f64(v: f64) -> Result<Self>;
5192 }
5193
5194 macro_rules! int_targets {
5195 ($($t:ty),* $(,)?) => {$(
5196 impl Sealed for $t {
5197 fn from_i64(v: i64, index: usize) -> Result<Self> {
5198 <$t>::try_from(v).map_err(|_| int_overflow::<$t>(v, index))
5199 }
5200 fn from_u64(v: u64, index: usize) -> Result<Self> {
5201 <$t>::try_from(v).map_err(|_| int_overflow::<$t>(v, index))
5202 }
5203 fn from_f32(_: f32) -> Result<Self> {
5204 Err(float_as_int::<$t>())
5205 }
5206 fn from_f64(_: f64) -> Result<Self> {
5207 Err(float_as_int::<$t>())
5208 }
5209 }
5210 )*};
5211 }
5212 int_targets!(i8, i16, i32, i64, u8, u16, u32, u64);
5213
5214 impl Sealed for i128 {
5217 fn from_i64(v: i64, _index: usize) -> Result<Self> {
5218 Ok(Self::from(v))
5219 }
5220 fn from_u64(v: u64, _index: usize) -> Result<Self> {
5221 Ok(Self::from(v))
5222 }
5223 fn from_f32(_: f32) -> Result<Self> {
5224 Err(float_as_int::<i128>())
5225 }
5226 fn from_f64(_: f64) -> Result<Self> {
5227 Err(float_as_int::<i128>())
5228 }
5229 }
5230
5231 impl Sealed for u128 {
5232 fn from_i64(v: i64, index: usize) -> Result<Self> {
5233 Self::try_from(v).map_err(|_| int_overflow::<u128>(v, index))
5234 }
5235 fn from_u64(v: u64, _index: usize) -> Result<Self> {
5236 Ok(Self::from(v))
5237 }
5238 fn from_f32(_: f32) -> Result<Self> {
5239 Err(float_as_int::<u128>())
5240 }
5241 fn from_f64(_: f64) -> Result<Self> {
5242 Err(float_as_int::<u128>())
5243 }
5244 }
5245
5246 fn int_overflow<T>(v: impl std::fmt::Display, index: usize) -> Hdf5Error {
5247 Hdf5Error::TypeMismatch(format!(
5248 "value {v} at element {index} does not fit in {}",
5249 std::any::type_name::<T>()
5250 ))
5251 }
5252
5253 fn float_as_int<T>() -> Hdf5Error {
5254 Hdf5Error::TypeMismatch(format!(
5255 "cannot read a floating-point dataset as {}; read as f64 and convert explicitly",
5256 std::any::type_name::<T>()
5257 ))
5258 }
5259
5260 impl Sealed for f32 {
5261 fn from_i64(_: i64, _index: usize) -> Result<Self> {
5262 Err(int_as_f32())
5263 }
5264 fn from_u64(_: u64, _index: usize) -> Result<Self> {
5265 Err(int_as_f32())
5266 }
5267 fn from_f32(v: f32) -> Result<Self> {
5268 Ok(v)
5269 }
5270 fn from_f64(_: f64) -> Result<Self> {
5271 Err(Hdf5Error::TypeMismatch(
5272 "narrowing an f64 dataset to f32 loses precision; read as f64".into(),
5273 ))
5274 }
5275 }
5276
5277 fn int_as_f32() -> Hdf5Error {
5278 Hdf5Error::TypeMismatch(
5279 "cannot read an integer dataset as f32; read as an integer type and convert \
5280 explicitly"
5281 .into(),
5282 )
5283 }
5284
5285 impl Sealed for f64 {
5286 fn from_i64(_: i64, _index: usize) -> Result<Self> {
5287 Err(int_as_f64())
5288 }
5289 fn from_u64(_: u64, _index: usize) -> Result<Self> {
5290 Err(int_as_f64())
5291 }
5292 fn from_f32(v: f32) -> Result<Self> {
5294 Ok(f64::from(v))
5295 }
5296 fn from_f64(v: f64) -> Result<Self> {
5297 Ok(v)
5298 }
5299 }
5300
5301 fn int_as_f64() -> Hdf5Error {
5302 Hdf5Error::TypeMismatch(
5303 "cannot read an integer dataset as f64; integers above 2^53 lose precision — \
5304 read as an integer type and convert explicitly"
5305 .into(),
5306 )
5307 }
5308
5309 #[cfg(test)]
5310 mod tests {
5311 use super::*;
5312
5313 #[test]
5317 fn f16_widening_is_exact() {
5318 let cases: [(u16, f32); 10] = [
5319 (0x0000, 0.0),
5320 (0x3c00, 1.0),
5321 (0xc000, -2.0),
5322 (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),
5328 (0xfc00, f32::NEG_INFINITY),
5329 ];
5330 for (bits, expected) in cases {
5331 let got = f16_bits_to_f32(bits);
5332 assert_eq!(got, expected, "0x{bits:04x} widened to {got}");
5333 }
5334
5335 let neg_zero = f16_bits_to_f32(0x8000);
5336 assert_eq!(neg_zero, 0.0);
5337 assert!(neg_zero.is_sign_negative(), "-0.0 lost its sign");
5338
5339 let nan = f16_bits_to_f32(0x7e01);
5340 assert!(nan.is_nan());
5341 assert_eq!(nan.to_bits(), 0x7fc0_2000);
5343 }
5344
5345 #[test]
5346 fn f16_source_converts_and_honors_byte_order() {
5347 let kind = classify(&DatatypeMessage::f16_type()).unwrap();
5348 let raw = [0x00, 0x3c, 0x00, 0xc0, 0x55, 0x35, 0xff, 0x7b];
5350 assert_eq!(
5351 convert::<f32>(kind, &raw).unwrap(),
5352 vec![1.0, -2.0, 0.333_251_95, 65504.0]
5353 );
5354 assert_eq!(
5355 convert::<f64>(kind, &raw).unwrap(),
5356 vec![1.0, -2.0, 0.333_251_953_125, 65504.0]
5357 );
5358
5359 let DatatypeMessage::FloatingPoint { .. } = DatatypeMessage::f16_type() else {
5360 unreachable!()
5361 };
5362 let mut be = DatatypeMessage::f16_type();
5363 if let DatatypeMessage::FloatingPoint { byte_order, .. } = &mut be {
5364 *byte_order = ByteOrder::BigEndian;
5365 }
5366 let be_kind = classify(&be).unwrap();
5367 assert_eq!(convert::<f32>(be_kind, &[0x3c, 0x00]).unwrap(), vec![1.0]);
5368 }
5369
5370 #[test]
5373 fn non_ieee_float_is_refused() {
5374 let mut odd = DatatypeMessage::f32_type();
5375 if let DatatypeMessage::FloatingPoint { exponent_bias, .. } = &mut odd {
5376 *exponent_bias = 63;
5377 }
5378 assert!(odd.ieee_format().is_none());
5379 let err = classify(&odd).err().expect("non-IEEE float was accepted");
5380 assert!(
5381 err.to_string().contains("IEEE 754 interchange format"),
5382 "unexpected error: {err}"
5383 );
5384 }
5385 }
5386}
5387
5388#[cfg(test)]
5389mod tests {
5390 use crate::H5File;
5391 use std::path::PathBuf;
5392
5393 fn temp_path(name: &str) -> PathBuf {
5394 use std::sync::atomic::{AtomicU64, Ordering};
5398 static COUNTER: AtomicU64 = AtomicU64::new(0);
5399 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
5400 std::env::temp_dir().join(format!(
5401 "hdf5_dataset_test_{}_{}_{}.h5",
5402 name,
5403 std::process::id(),
5404 n
5405 ))
5406 }
5407
5408 #[test]
5409 fn runtime_compound_via_datatype_override_and_raw_bytes() {
5410 use crate::format::messages::datatype::DatatypeMessage;
5411 use crate::types::{CompoundType, H5Type};
5412
5413 let path = temp_path("compound_raw");
5414 let ct = CompoundType {
5418 members: vec![
5419 ("id".to_string(), i32::hdf5_type(), 0),
5420 ("val".to_string(), f64::hdf5_type(), 4),
5421 ],
5422 total_size: 12,
5423 };
5424 let recs: [(i32, f64); 3] = [(1, 2.5), (2, 3.5), (3, -4.0)];
5425 let mut bytes = Vec::new();
5426 for (id, val) in recs {
5427 bytes.extend_from_slice(&id.to_le_bytes());
5428 bytes.extend_from_slice(&val.to_le_bytes());
5429 }
5430
5431 {
5432 let file = H5File::create(&path).unwrap();
5433 let ds = file
5434 .new_dataset::<u8>()
5435 .datatype(ct.to_datatype())
5436 .shape([recs.len()])
5437 .create("records")
5438 .unwrap();
5439 ds.write_raw_bytes(&bytes).unwrap();
5440 file.close().unwrap();
5441 }
5442 {
5443 let file = H5File::open(&path).unwrap();
5444 let ds = file.dataset("records").unwrap();
5445 match ds.datatype().unwrap() {
5448 DatatypeMessage::Compound { size, members } => {
5449 assert_eq!(size, 12);
5450 assert_eq!(members.len(), 2);
5451 assert_eq!(members[0].name, "id");
5452 assert_eq!(members[0].offset, 0);
5453 assert_eq!(members[1].name, "val");
5454 assert_eq!(members[1].offset, 4);
5455 }
5456 other => panic!("expected compound datatype, got {other:?}"),
5457 }
5458 assert_eq!(ds.read_raw_bytes().unwrap(), bytes);
5459 }
5460 std::fs::remove_file(&path).ok();
5461 }
5462
5463 #[test]
5464 fn builder_requires_shape() {
5465 let path = temp_path("no_shape");
5466 let file = H5File::create(&path).unwrap();
5467 let result = file.new_dataset::<u8>().create("data");
5468 assert!(result.is_err());
5469 std::fs::remove_file(&path).ok();
5470 }
5471
5472 #[test]
5478 fn a_partial_edge_chunk_does_not_grow_past_the_declared_maximum() {
5479 let path = temp_path("edge_chunk_extent");
5480 let file = H5File::create(&path).unwrap();
5483 let ds = file
5484 .new_dataset::<i32>()
5485 .shape([10usize, 4])
5486 .max_shape(&[Some(10), None])
5487 .chunk(&[4, 4])
5488 .create("grid")
5489 .unwrap();
5490 let chunk: Vec<u8> = (0i32..16).flat_map(|v| v.to_le_bytes()).collect();
5491 ds.write_chunk_at(&[2, 0], &chunk).unwrap();
5493 assert_eq!(ds.shape(), vec![10, 4]);
5494 file.close().unwrap();
5495
5496 let file = H5File::open(&path).unwrap();
5497 let back = file.dataset("grid").unwrap().read_raw::<i32>().unwrap();
5498 assert_eq!(back.len(), 40);
5499 assert_eq!(&back[32..40], &[0, 1, 2, 3, 4, 5, 6, 7]);
5500 drop(file);
5501 std::fs::remove_file(&path).ok();
5502 }
5503
5504 #[test]
5513 fn the_edge_write_cap_holds_for_the_v2_btree_index() {
5514 let path = temp_path("edge_chunk_bt2");
5515 let file = H5File::create(&path).unwrap();
5516 let ds = file
5517 .new_dataset::<i32>()
5518 .shape([10usize, 4, 4])
5519 .max_shape(&[Some(10), None, None])
5520 .chunk(&[4, 4, 4])
5521 .create("cube")
5522 .unwrap();
5523 let chunk: Vec<u8> = (0i32..64).flat_map(|v| v.to_le_bytes()).collect();
5524 ds.write_chunk_at(&[2, 0, 0], &chunk).unwrap();
5526 assert_eq!(ds.shape(), vec![10, 4, 4]);
5527 file.close().unwrap();
5528
5529 let file = H5File::open(&path).unwrap();
5530 let back = file.dataset("cube").unwrap().read_raw::<i32>().unwrap();
5531 assert_eq!(back.len(), 160);
5532 assert_eq!(&back[128..160], &(0i32..32).collect::<Vec<_>>()[..]);
5534 drop(file);
5535 std::fs::remove_file(&path).ok();
5536 }
5537
5538 #[test]
5546 fn an_external_or_virtual_dataset_never_reaches_the_edge_write_cap() {
5547 use crate::Selection;
5548 let dir = std::env::temp_dir().join(format!(
5549 "rust_hdf5_edge_cap_{}_{}",
5550 std::process::id(),
5551 temp_path("x").file_name().unwrap().to_string_lossy()
5552 ));
5553 std::fs::create_dir_all(&dir).unwrap();
5554 let path = dir.join("edge_cap.h5");
5555 let file = H5File::create(&path).unwrap();
5556 let payload = dir.join("payload.raw");
5557
5558 for (which, res) in [
5560 (
5561 "external",
5562 file.new_dataset::<i32>()
5563 .shape([10usize])
5564 .external(&[(payload.to_str().unwrap(), 0, 40)])
5565 .chunk(&[4])
5566 .create("a"),
5567 ),
5568 (
5569 "virtual",
5570 file.new_dataset::<i32>()
5571 .shape([10usize])
5572 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
5573 .chunk(&[4])
5574 .create("b"),
5575 ),
5576 ] {
5577 match res {
5578 Ok(_) => panic!("a {which} dataset cannot also be chunked"),
5579 Err(e) => assert!(e.to_string().contains("chunked"), "{which}: {e}"),
5580 }
5581 }
5582
5583 let ext = file
5586 .new_dataset::<i32>()
5587 .shape([10usize])
5588 .external(&[(payload.to_str().unwrap(), 0, 40)])
5589 .create("outside")
5590 .unwrap();
5591 let vds = file
5592 .new_dataset::<i32>()
5593 .shape([10usize])
5594 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
5595 .create("elsewhere")
5596 .unwrap();
5597 let chunk: Vec<u8> = (0i32..4).flat_map(|v| v.to_le_bytes()).collect();
5598 for (which, ds) in [("external", &ext), ("virtual", &vds)] {
5599 let err = ds.write_chunk_at(&[2], &chunk).unwrap_err().to_string();
5600 assert!(err.contains("only for chunked datasets"), "{which}: {err}");
5601 let err = ds
5602 .write_chunk_raw_at(&[2], &chunk, 0)
5603 .unwrap_err()
5604 .to_string();
5605 assert!(err.contains("only for chunked datasets"), "{which}: {err}");
5606 assert_eq!(ds.shape(), vec![10], "{which}");
5607 }
5608 file.close().unwrap();
5609 std::fs::remove_dir_all(&dir).ok();
5610 }
5611
5612 #[test]
5613 fn write_raw_size_mismatch() {
5614 let path = temp_path("size_mismatch");
5615 let file = H5File::create(&path).unwrap();
5616 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
5617 let result = ds.write_raw(&[1u8, 2, 3]);
5619 assert!(result.is_err());
5620 std::fs::remove_file(&path).ok();
5621 }
5622
5623 #[cfg(feature = "deflate")]
5627 #[test]
5628 fn filter_without_chunk_autochunks_and_roundtrips() {
5629 let path = temp_path("autochunk_filter");
5630 let data: Vec<i32> = (0..8).collect();
5631 {
5632 let file = H5File::create(&path).unwrap();
5633 let ds = file
5634 .new_dataset::<i32>()
5635 .deflate(6)
5636 .shape([8])
5637 .create("seq")
5638 .unwrap();
5639 ds.write_raw(&data).unwrap();
5640 file.close().unwrap();
5641 }
5642 {
5643 let file = H5File::open(&path).unwrap();
5644 let ds = file.dataset("seq").unwrap();
5645 assert!(
5647 ds.is_chunked(),
5648 "auto-chunk did not produce chunked storage"
5649 );
5650 assert_eq!(ds.chunk_dims(), Some(vec![8]));
5651 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5652 }
5653 std::fs::remove_file(&path).ok();
5654 }
5655
5656 #[cfg(feature = "deflate")]
5660 #[test]
5661 fn an_edge_chunk_pads_with_zero_not_the_chunk_written_before_it() {
5662 let path = temp_path("edge_chunk_padding");
5668 let data: Vec<i32> = (1..=20).collect(); {
5670 let file = H5File::create(&path).unwrap();
5671 let ds = file
5672 .new_dataset::<i32>()
5673 .shape([4, 5])
5674 .chunk(&[2, 3])
5675 .create("grid")
5676 .unwrap();
5677 ds.write_raw(&data).unwrap();
5678 file.close().unwrap();
5679 }
5680 {
5681 let file = H5File::open(&path).unwrap();
5682 let ds = file.dataset("grid").unwrap();
5683 let as_i32 = |bytes: Vec<u8>| -> Vec<i32> {
5684 bytes
5685 .as_chunks::<4>()
5686 .0
5687 .iter()
5688 .map(|b| i32::from_le_bytes(*b))
5689 .collect()
5690 };
5691 assert_eq!(
5694 as_i32(ds.read_chunk_raw_at(&[0, 0]).unwrap().0),
5695 [1, 2, 3, 6, 7, 8]
5696 );
5697 assert_eq!(
5698 as_i32(ds.read_chunk_raw_at(&[0, 1]).unwrap().0),
5699 [4, 5, 0, 9, 10, 0]
5700 );
5701 assert_eq!(
5702 as_i32(ds.read_chunk_raw_at(&[1, 1]).unwrap().0),
5703 [14, 15, 0, 19, 20, 0]
5704 );
5705 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5706 }
5707 std::fs::remove_file(&path).ok();
5708 }
5709
5710 #[test]
5711 #[cfg(feature = "deflate")]
5712 fn write_raw_multichunk_edge_roundtrips() {
5713 let path = temp_path("multichunk_edge");
5714 let data: Vec<i32> = (0..35).collect(); {
5716 let file = H5File::create(&path).unwrap();
5717 let ds = file
5718 .new_dataset::<i32>()
5719 .shape([7, 5])
5720 .chunk(&[3, 2])
5721 .deflate(4)
5722 .create("grid")
5723 .unwrap();
5724 ds.write_raw(&data).unwrap();
5725 file.close().unwrap();
5726 }
5727 {
5728 let file = H5File::open(&path).unwrap();
5729 let ds = file.dataset("grid").unwrap();
5730 assert_eq!(ds.shape(), vec![7, 5]);
5731 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
5732 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5733 }
5734 std::fs::remove_file(&path).ok();
5735 }
5736
5737 #[test]
5740 fn write_raw_unfiltered_chunked_roundtrips() {
5741 let path = temp_path("chunked_unfiltered");
5742 let data: Vec<f64> = (0..12).map(|i| i as f64 * 1.5).collect(); {
5744 let file = H5File::create(&path).unwrap();
5745 let ds = file
5746 .new_dataset::<f64>()
5747 .shape([4, 3])
5748 .chunk(&[2, 2])
5749 .create("m")
5750 .unwrap();
5751 ds.write_raw(&data).unwrap();
5752 file.close().unwrap();
5753 }
5754 {
5755 let file = H5File::open(&path).unwrap();
5756 let ds = file.dataset("m").unwrap();
5757 assert_eq!(ds.chunk_dims(), Some(vec![2, 2]));
5758 assert_eq!(ds.read_raw::<f64>().unwrap(), data);
5759 }
5760 std::fs::remove_file(&path).ok();
5761 }
5762
5763 #[cfg(feature = "deflate")]
5768 #[test]
5769 fn write_raw_ea_compressed_roundtrips() {
5770 let path = temp_path("write_raw_ea_deflate");
5771 let data: Vec<i32> = (0..20).collect(); {
5773 let file = H5File::create(&path).unwrap();
5774 let ds = file
5775 .new_dataset::<i32>()
5776 .shape([5, 4])
5777 .chunk(&[2, 4])
5778 .max_shape(&[None, Some(4)]) .deflate(5)
5780 .create("stream")
5781 .unwrap();
5782 ds.write_raw(&data).unwrap();
5783 file.close().unwrap();
5784 }
5785 {
5786 let file = H5File::open(&path).unwrap();
5787 let ds = file.dataset("stream").unwrap();
5788 assert_eq!(ds.shape(), vec![5, 4]);
5789 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5790 }
5791 std::fs::remove_file(&path).ok();
5792 }
5793
5794 #[cfg(feature = "deflate")]
5799 #[test]
5800 fn read_full_3d_chunked_edge_roundtrips() {
5801 let path = temp_path("full_3d_chunked_edge");
5802 let total: usize = 5 * 4 * 3;
5805 let data: Vec<i32> = (0..total as i32).collect();
5806 {
5807 let file = H5File::create(&path).unwrap();
5808 let ds = file
5809 .new_dataset::<i32>()
5810 .shape([5, 4, 3])
5811 .chunk(&[2, 3, 2])
5812 .deflate(4)
5813 .create("vol")
5814 .unwrap();
5815 ds.write_raw(&data).unwrap();
5816 file.close().unwrap();
5817 }
5818 {
5819 let file = H5File::open(&path).unwrap();
5820 let ds = file.dataset("vol").unwrap();
5821 assert_eq!(ds.shape(), vec![5, 4, 3]);
5822 assert_eq!(ds.chunk_dims(), Some(vec![2, 3, 2]));
5823 assert_eq!(ds.read_raw::<i32>().unwrap(), data);
5824 }
5825 std::fs::remove_file(&path).ok();
5826 }
5827
5828 #[test]
5829 fn roundtrip_u8_1d() {
5830 let path = temp_path("rt_u8_1d");
5831 let data: Vec<u8> = (0..10).collect();
5832
5833 {
5834 let file = H5File::create(&path).unwrap();
5835 let ds = file.new_dataset::<u8>().shape([10]).create("seq").unwrap();
5836 ds.write_raw(&data).unwrap();
5837 file.close().unwrap();
5838 }
5839
5840 {
5841 let file = H5File::open(&path).unwrap();
5842 let ds = file.dataset("seq").unwrap();
5843 assert_eq!(ds.shape(), vec![10]);
5844 let readback = ds.read_raw::<u8>().unwrap();
5845 assert_eq!(readback, data);
5846 }
5847
5848 std::fs::remove_file(&path).ok();
5849 }
5850
5851 #[test]
5852 fn roundtrip_i32_2d() {
5853 let path = temp_path("rt_i32_2d");
5854 let data: Vec<i32> = vec![-1, 0, 1, 2, 3, 4];
5855
5856 {
5857 let file = H5File::create(&path).unwrap();
5858 let ds = file
5859 .new_dataset::<i32>()
5860 .shape([2, 3])
5861 .create("matrix")
5862 .unwrap();
5863 ds.write_raw(&data).unwrap();
5864 file.close().unwrap();
5865 }
5866
5867 {
5868 let file = H5File::open(&path).unwrap();
5869 let ds = file.dataset("matrix").unwrap();
5870 assert_eq!(ds.shape(), vec![2, 3]);
5871 let readback = ds.read_raw::<i32>().unwrap();
5872 assert_eq!(readback, data);
5873 }
5874
5875 std::fs::remove_file(&path).ok();
5876 }
5877
5878 #[test]
5879 fn roundtrip_f64_3d() {
5880 let path = temp_path("rt_f64_3d");
5881 let data: Vec<f64> = (0..24).map(|i| i as f64 * 0.5).collect();
5882
5883 {
5884 let file = H5File::create(&path).unwrap();
5885 let ds = file
5886 .new_dataset::<f64>()
5887 .shape([2, 3, 4])
5888 .create("cube")
5889 .unwrap();
5890 ds.write_raw(&data).unwrap();
5891 file.close().unwrap();
5892 }
5893
5894 {
5895 let file = H5File::open(&path).unwrap();
5896 let ds = file.dataset("cube").unwrap();
5897 assert_eq!(ds.shape(), vec![2, 3, 4]);
5898 let readback = ds.read_raw::<f64>().unwrap();
5899 assert_eq!(readback, data);
5900 }
5901
5902 std::fs::remove_file(&path).ok();
5903 }
5904
5905 #[test]
5906 fn cannot_read_in_write_mode() {
5907 let path = temp_path("no_read_write");
5908 let file = H5File::create(&path).unwrap();
5909 let ds = file.new_dataset::<u8>().shape([4]).create("x").unwrap();
5910 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
5911 let result = ds.read_raw::<u8>();
5912 assert!(result.is_err());
5913 std::fs::remove_file(&path).ok();
5914 }
5915
5916 #[test]
5917 fn cannot_write_in_read_mode() {
5918 let path = temp_path("no_write_read");
5919
5920 {
5921 let file = H5File::create(&path).unwrap();
5922 let ds = file.new_dataset::<u8>().shape([4]).create("x").unwrap();
5923 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
5924 file.close().unwrap();
5925 }
5926
5927 {
5928 let file = H5File::open(&path).unwrap();
5929 let ds = file.dataset("x").unwrap();
5930 let result = ds.write_raw(&[5u8, 6, 7, 8]);
5931 assert!(result.is_err());
5932 }
5933
5934 std::fs::remove_file(&path).ok();
5935 }
5936
5937 #[test]
5938 fn numeric_attr_roundtrip() {
5939 let path = temp_path("num_attr");
5940 {
5941 let file = H5File::create(&path).unwrap();
5942 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
5943 ds.write_raw(&[1.0f32; 4]).unwrap();
5944
5945 let a1 = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
5946 a1.write_numeric(&1.2345f64).unwrap();
5947
5948 let a2 = ds.new_attr::<i32>().shape(()).create("count").unwrap();
5949 a2.write_numeric(&42i32).unwrap();
5950
5951 file.close().unwrap();
5952 }
5953 {
5954 let file = H5File::open(&path).unwrap();
5955 let ds = file.dataset("data").unwrap();
5956
5957 let scale = ds.attr("scale").unwrap();
5958 let val: f64 = scale.read_numeric().unwrap();
5959 assert!((val - 1.2345).abs() < 1e-10);
5960
5961 let count = ds.attr("count").unwrap();
5962 let val: i32 = count.read_numeric().unwrap();
5963 assert_eq!(val, 42);
5964 }
5965 std::fs::remove_file(&path).ok();
5966 }
5967
5968 #[test]
5969 fn array_attr_roundtrip() {
5970 let path = temp_path("array_attr");
5971 let offsets = [10i32, -20, 30];
5972 {
5973 let file = H5File::create(&path).unwrap();
5974 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
5975 ds.write_raw(&[1.0f32; 4]).unwrap();
5976
5977 let a = ds
5979 .new_attr::<i32>()
5980 .shape([3])
5981 .create("dim_offset")
5982 .unwrap();
5983 a.write_array(&offsets).unwrap();
5984
5985 let bad = ds.new_attr::<i32>().shape([3]).create("bad").unwrap();
5987 assert!(bad.write_array(&[1i32, 2]).is_err());
5988
5989 file.close().unwrap();
5990 }
5991 {
5992 let file = H5File::open(&path).unwrap();
5993 let ds = file.dataset("data").unwrap();
5994 let a = ds.attr("dim_offset").unwrap();
5995 let raw = a.read_raw().unwrap();
5996 assert_eq!(raw.len(), 3 * 4);
5997 let got: Vec<i32> = raw
5998 .as_chunks::<4>()
5999 .0
6000 .iter()
6001 .map(|b| i32::from_le_bytes(*b))
6002 .collect();
6003 assert_eq!(got, offsets);
6004 }
6005 std::fs::remove_file(&path).ok();
6006 }
6007
6008 #[test]
6009 fn attr_datatype_exposes_class_and_sign() {
6010 use crate::format::messages::datatype::DatatypeMessage;
6014
6015 let path = temp_path("attr_datatype");
6016 {
6017 let file = H5File::create(&path).unwrap();
6018 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
6019 ds.new_attr::<f64>()
6020 .shape(())
6021 .create("scale")
6022 .unwrap()
6023 .write_numeric(&1.5f64)
6024 .unwrap();
6025 ds.new_attr::<i32>()
6026 .shape(())
6027 .create("count")
6028 .unwrap()
6029 .write_numeric(&7i32)
6030 .unwrap();
6031 file.close().unwrap();
6032 }
6033 {
6034 let file = H5File::open(&path).unwrap();
6035 let ds = file.dataset("data").unwrap();
6036
6037 match ds.attr("scale").unwrap().datatype().unwrap() {
6038 DatatypeMessage::FloatingPoint { size, .. } => assert_eq!(size, 8),
6039 other => panic!("expected FloatingPoint for f64 attr, got {other:?}"),
6040 }
6041
6042 match ds.attr("count").unwrap().datatype().unwrap() {
6043 DatatypeMessage::FixedPoint { size, signed, .. } => {
6044 assert_eq!(size, 4);
6045 assert!(signed, "i32 attr must be signed");
6046 }
6047 other => panic!("expected FixedPoint for i32 attr, got {other:?}"),
6048 }
6049 }
6050 std::fs::remove_file(&path).ok();
6051 }
6052
6053 #[test]
6054 fn attr_datatype_in_write_mode_errors() {
6055 let path = temp_path("attr_datatype_write_mode");
6056 let file = H5File::create(&path).unwrap();
6057 let ds = file.new_dataset::<f32>().shape([4]).create("data").unwrap();
6058 let attr = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
6059 assert!(attr.datatype().is_err());
6060 std::fs::remove_file(&path).ok();
6061 }
6062
6063 #[test]
6064 fn cannot_create_dataset_in_read_mode() {
6065 let path = temp_path("no_create_read");
6066
6067 {
6068 let _file = H5File::create(&path).unwrap();
6069 }
6070
6071 {
6072 let file = H5File::open(&path).unwrap();
6073 let result = file.new_dataset::<u8>().shape([4]).create("x");
6074 assert!(result.is_err());
6075 }
6076
6077 std::fs::remove_file(&path).ok();
6078 }
6079
6080 #[test]
6081 fn shape_accessor() {
6082 let path = temp_path("shape_acc");
6083
6084 let file = H5File::create(&path).unwrap();
6085 let ds = file
6086 .new_dataset::<f32>()
6087 .shape([5, 10, 3])
6088 .create("tensor")
6089 .unwrap();
6090 assert_eq!(ds.shape(), vec![5, 10, 3]);
6091
6092 std::fs::remove_file(&path).ok();
6093 }
6094
6095 #[test]
6096 fn slice_roundtrip_2d() {
6097 let path = temp_path("slice_2d");
6098
6099 let data: Vec<i32> = (0..20).collect();
6101 {
6102 let file = H5File::create(&path).unwrap();
6103 let ds = file
6104 .new_dataset::<i32>()
6105 .shape([4, 5])
6106 .create("mat")
6107 .unwrap();
6108 ds.write_raw(&data).unwrap();
6109 file.close().unwrap();
6110 }
6111 {
6112 let file = H5File::open(&path).unwrap();
6113 let ds = file.dataset("mat").unwrap();
6114 let slice = ds.read_slice::<i32>(&[1, 2], &[2, 2]).unwrap();
6116 assert_eq!(slice, vec![7, 8, 12, 13]);
6119 }
6120
6121 std::fs::remove_file(&path).ok();
6122 }
6123
6124 fn assert_into_matches<T>(ds: &super::H5Dataset, starts: &[usize], counts: &[usize])
6129 where
6130 T: crate::types::H5Type + Copy + std::fmt::Debug + PartialEq + Default,
6131 {
6132 let n: usize = ds.shape().iter().product();
6133 let want_full = ds.read_raw::<T>().unwrap();
6134 let mut got_full = vec![T::default(); n];
6135 ds.read_raw_into::<T>(&mut got_full).unwrap();
6136 assert_eq!(got_full, want_full, "read_raw_into != read_raw");
6137
6138 let want_slice = ds.read_slice::<T>(starts, counts).unwrap();
6139 let sn: usize = counts.iter().product();
6140 let mut got_slice = vec![T::default(); sn];
6141 ds.read_slice_into::<T>(&mut got_slice, starts, counts)
6142 .unwrap();
6143 assert_eq!(got_slice, want_slice, "read_slice_into != read_slice");
6144 }
6145
6146 #[test]
6147 fn read_into_matches_vec_contiguous() {
6148 let path = temp_path("into_contig");
6149 let data: Vec<i32> = (0..20).collect(); {
6151 let file = H5File::create(&path).unwrap();
6152 let ds = file
6153 .new_dataset::<i32>()
6154 .shape([4, 5])
6155 .create("mat")
6156 .unwrap();
6157 ds.write_raw(&data).unwrap();
6158 file.close().unwrap();
6159 }
6160 {
6161 let file = H5File::open(&path).unwrap();
6162 let ds = file.dataset("mat").unwrap();
6163 assert_eq!(ds.chunk_dims(), None);
6164 assert_into_matches::<i32>(&ds, &[1, 2], &[2, 2]);
6165 }
6166 std::fs::remove_file(&path).ok();
6167 }
6168
6169 #[test]
6170 fn read_into_matches_vec_chunked_unfiltered() {
6171 let path = temp_path("into_chunk");
6172 let data: Vec<f64> = (0..35).map(|i| i as f64 * 1.5).collect(); {
6174 let file = H5File::create(&path).unwrap();
6175 let ds = file
6176 .new_dataset::<f64>()
6177 .shape([7, 5])
6178 .chunk(&[3, 2]) .create("grid")
6180 .unwrap();
6181 ds.write_raw(&data).unwrap();
6182 file.close().unwrap();
6183 }
6184 {
6185 let file = H5File::open(&path).unwrap();
6186 let ds = file.dataset("grid").unwrap();
6187 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
6188 assert_into_matches::<f64>(&ds, &[2, 1], &[3, 3]);
6190 }
6191 std::fs::remove_file(&path).ok();
6192 }
6193
6194 #[test]
6195 fn read_into_matches_vec_single_chunk() {
6196 let path = temp_path("into_single_chunk");
6197 let data: Vec<i32> = (0..12).collect(); {
6199 let file = H5File::create(&path).unwrap();
6200 let ds = file
6201 .new_dataset::<i32>()
6202 .shape([3, 4])
6203 .chunk(&[3, 4]) .create("g")
6205 .unwrap();
6206 ds.write_raw(&data).unwrap();
6207 file.close().unwrap();
6208 }
6209 {
6210 let file = H5File::open(&path).unwrap();
6211 let ds = file.dataset("g").unwrap();
6212 assert_eq!(ds.chunk_dims(), Some(vec![3, 4]));
6213 assert_into_matches::<i32>(&ds, &[1, 1], &[2, 2]);
6214 }
6215 std::fs::remove_file(&path).ok();
6216 }
6217
6218 #[cfg(feature = "deflate")]
6219 #[test]
6220 fn read_into_matches_vec_chunked_deflate() {
6221 let path = temp_path("into_chunk_deflate");
6222 let data: Vec<i32> = (0..35).collect(); {
6224 let file = H5File::create(&path).unwrap();
6225 let ds = file
6226 .new_dataset::<i32>()
6227 .shape([7, 5])
6228 .chunk(&[3, 2])
6229 .deflate(4)
6230 .create("grid")
6231 .unwrap();
6232 ds.write_raw(&data).unwrap();
6233 file.close().unwrap();
6234 }
6235 {
6236 let file = H5File::open(&path).unwrap();
6237 let ds = file.dataset("grid").unwrap();
6238 assert_eq!(ds.chunk_dims(), Some(vec![3, 2]));
6239 assert_into_matches::<i32>(&ds, &[2, 1], &[3, 3]);
6240 }
6241 std::fs::remove_file(&path).ok();
6242 }
6243
6244 #[test]
6245 fn read_into_wrong_buffer_size_rejected() {
6246 let path = temp_path("into_badlen");
6247 let data: Vec<i32> = (0..20).collect(); {
6249 let file = H5File::create(&path).unwrap();
6250 let ds = file
6251 .new_dataset::<i32>()
6252 .shape([4, 5])
6253 .create("mat")
6254 .unwrap();
6255 ds.write_raw(&data).unwrap();
6256 file.close().unwrap();
6257 }
6258 {
6259 let file = H5File::open(&path).unwrap();
6260 let ds = file.dataset("mat").unwrap();
6261
6262 let mut small = vec![0i32; 19];
6264 assert!(ds.read_raw_into::<i32>(&mut small).is_err());
6265 let mut large = vec![0i32; 21];
6266 assert!(ds.read_raw_into::<i32>(&mut large).is_err());
6267
6268 let mut bad_slice = vec![0i32; 3];
6270 assert!(ds
6271 .read_slice_into::<i32>(&mut bad_slice, &[1, 2], &[2, 2])
6272 .is_err());
6273 let mut ok_slice = vec![0i32; 4];
6275 assert!(ds
6276 .read_slice_into::<i32>(&mut ok_slice, &[1, 2], &[2, 2])
6277 .is_ok());
6278 }
6279 std::fs::remove_file(&path).ok();
6280 }
6281
6282 #[test]
6283 fn read_into_wrong_element_size_rejected() {
6284 let path = temp_path("into_badtype");
6285 let data: Vec<i32> = (0..20).collect(); {
6287 let file = H5File::create(&path).unwrap();
6288 let ds = file
6289 .new_dataset::<i32>()
6290 .shape([4, 5])
6291 .create("mat")
6292 .unwrap();
6293 ds.write_raw(&data).unwrap();
6294 file.close().unwrap();
6295 }
6296 {
6297 let file = H5File::open(&path).unwrap();
6298 let ds = file.dataset("mat").unwrap();
6299 let mut as_u8 = vec![0u8; 20];
6302 assert!(matches!(
6303 ds.read_raw_into::<u8>(&mut as_u8),
6304 Err(crate::Hdf5Error::TypeMismatch(_))
6305 ));
6306 let mut as_i64 = vec![0i64; 20];
6307 assert!(matches!(
6308 ds.read_slice_into::<i64>(&mut as_i64, &[0, 0], &[4, 5]),
6309 Err(crate::Hdf5Error::TypeMismatch(_))
6310 ));
6311 }
6312 std::fs::remove_file(&path).ok();
6313 }
6314
6315 #[test]
6316 fn write_slice_2d() {
6317 let path = temp_path("write_slice_2d");
6318
6319 {
6320 let file = H5File::create(&path).unwrap();
6321 let ds = file
6322 .new_dataset::<f32>()
6323 .shape([3, 4])
6324 .create("data")
6325 .unwrap();
6326 ds.write_raw(&[0.0f32; 12]).unwrap();
6327 ds.write_slice(&[1, 1], &[2, 2], &[10.0f32, 20.0, 30.0, 40.0])
6329 .unwrap();
6330 file.close().unwrap();
6331 }
6332 {
6333 let file = H5File::open(&path).unwrap();
6334 let ds = file.dataset("data").unwrap();
6335 let full = ds.read_raw::<f32>().unwrap();
6336 assert_eq!(
6340 full,
6341 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,]
6342 );
6343 }
6344
6345 std::fs::remove_file(&path).ok();
6346 }
6347
6348 fn chunk_of(v: i32) -> Vec<u8> {
6350 (0..8).flat_map(|_| v.to_le_bytes()).collect()
6351 }
6352
6353 fn rewrite_chunk(
6357 tag: &str,
6358 rewrites: i32,
6359 build: impl Fn(&H5File) -> crate::H5Dataset,
6360 ) -> (u64, i32) {
6361 let path = temp_path(tag);
6362 {
6363 let file = H5File::create(&path).unwrap();
6364 let ds = build(&file);
6365 for v in 1..=rewrites {
6366 ds.write_chunk_at(&[0, 0], &chunk_of(v)).unwrap();
6367 }
6368 file.close().unwrap();
6369 }
6370 let size = std::fs::metadata(&path).unwrap().len();
6371 let first = {
6372 let file = H5File::open(&path).unwrap();
6373 file.dataset("d").unwrap().read_raw::<i32>().unwrap()[0]
6374 };
6375 std::fs::remove_file(&path).ok();
6376 (size, first)
6377 }
6378
6379 #[test]
6385 fn rewriting_an_unfiltered_extensible_array_chunk_stays_in_place() {
6386 let build = |f: &H5File| {
6387 f.new_dataset::<i32>()
6388 .shape([2, 4])
6389 .chunk(&[2, 4])
6390 .max_shape(&[None, Some(4)])
6391 .create("d")
6392 .unwrap()
6393 };
6394 let (once, _) = rewrite_chunk("rewrite_ea_1", 1, build);
6395 let (many, last) = rewrite_chunk("rewrite_ea_8", 8, build);
6396 assert_eq!(many, once, "8 rewrites grew the file past a single write");
6397 assert_eq!(last, 8, "the last write must be the one that survives");
6398 }
6399
6400 #[test]
6401 fn rewriting_an_unfiltered_fixed_array_chunk_stays_in_place() {
6402 let build = |f: &H5File| {
6403 f.new_dataset::<i32>()
6404 .shape([2, 4])
6405 .chunk(&[2, 4])
6406 .create("d")
6407 .unwrap()
6408 };
6409 let (once, _) = rewrite_chunk("rewrite_fa_1", 1, build);
6410 let (many, last) = rewrite_chunk("rewrite_fa_8", 8, build);
6411 assert_eq!(many, once, "8 rewrites grew the file past a single write");
6412 assert_eq!(last, 8);
6413 }
6414
6415 #[test]
6416 fn rewriting_an_unfiltered_btree_v2_chunk_stays_in_place() {
6417 let build = |f: &H5File| {
6418 f.new_dataset::<i32>()
6419 .shape([2, 4])
6420 .chunk(&[2, 4])
6421 .max_shape(&[None, None])
6422 .create("d")
6423 .unwrap()
6424 };
6425 let (once, _) = rewrite_chunk("rewrite_bt2_1", 1, build);
6426 let (many, last) = rewrite_chunk("rewrite_bt2_8", 8, build);
6427 assert_eq!(many, once, "8 rewrites grew the file past a single write");
6428 assert_eq!(last, 8);
6429 }
6430
6431 #[test]
6436 fn repeated_flushes_do_not_grow_a_btree_v2_index() {
6437 let flush_n = |label: &str, flushes: usize| -> u64 {
6438 let path = temp_path(label);
6439 {
6440 let file = H5File::create(&path).unwrap();
6441 let ds = file
6442 .new_dataset::<i32>()
6443 .shape([2, 4])
6444 .chunk(&[2, 4])
6445 .max_shape(&[None, None])
6446 .create("d")
6447 .unwrap();
6448 let bytes: Vec<u8> = (0..8i32).flat_map(|v| v.to_le_bytes()).collect();
6449 ds.write_chunk_at(&[0, 0], &bytes).unwrap();
6450 for _ in 0..flushes {
6451 ds.flush().unwrap();
6452 }
6453 file.close().unwrap();
6454 }
6455 let size = std::fs::metadata(&path).unwrap().len();
6456 {
6458 let file = H5File::open(&path).unwrap();
6459 let ds = file.dataset("d").unwrap();
6460 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..8).collect::<Vec<i32>>());
6461 }
6462 std::fs::remove_file(&path).ok();
6463 size
6464 };
6465 assert_eq!(
6466 flush_n("bt2_flush_8", 8),
6467 flush_n("bt2_flush_1", 1),
6468 "8 index flushes grew the file past a single one"
6469 );
6470 }
6471
6472 #[cfg(feature = "deflate")]
6478 #[test]
6479 fn rewriting_a_filtered_chunk_recycles_the_released_block() {
6480 let flat: Vec<u8> = (0..8).flat_map(|_| 7i32.to_le_bytes()).collect();
6483 let varied: Vec<u8> = (0..8i32)
6484 .flat_map(|i| i.wrapping_mul(0x5bd1_e995).to_le_bytes())
6485 .collect();
6486
6487 let sizes: Vec<u64> = [1usize, 8]
6488 .iter()
6489 .map(|&rounds| {
6490 let path = temp_path(&format!("rewrite_filtered_{rounds}"));
6491 {
6492 let file = H5File::create(&path).unwrap();
6493 let ds = file
6494 .new_dataset::<i32>()
6495 .shape([2, 4])
6496 .chunk(&[2, 4])
6497 .max_shape(&[None, Some(4)])
6498 .deflate(6)
6499 .create("d")
6500 .unwrap();
6501 for _ in 0..rounds {
6502 ds.write_chunk_at(&[0, 0], &flat).unwrap();
6503 ds.write_chunk_at(&[0, 0], &varied).unwrap();
6504 }
6505 file.close().unwrap();
6506 }
6507 let size = std::fs::metadata(&path).unwrap().len();
6508 {
6509 let file = H5File::open(&path).unwrap();
6510 let got = file.dataset("d").unwrap().read_raw::<i32>().unwrap();
6511 let want: Vec<i32> = (0..8i32).map(|i| i.wrapping_mul(0x5bd1_e995)).collect();
6512 assert_eq!(got, want, "the last write must survive the round trip");
6513 }
6514 std::fs::remove_file(&path).ok();
6515 size
6516 })
6517 .collect();
6518
6519 assert_eq!(
6520 sizes[1], sizes[0],
6521 "8 alternating rewrites grew the file past a single pair"
6522 );
6523 }
6524
6525 #[test]
6526 fn write_slice_out_of_bounds_rejected() {
6527 let path = temp_path("write_slice_oob");
6528 let file = H5File::create(&path).unwrap();
6529 let ds = file.new_dataset::<i32>().shape([4]).create("d").unwrap();
6530 ds.write_raw(&[0i32; 4]).unwrap();
6531 assert!(ds.write_slice(&[2], &[6], &[9i32; 6]).is_err());
6533 assert!(ds.write_slice(&[1], &[2], &[7i32, 8]).is_ok());
6535 std::fs::remove_file(&path).ok();
6536 }
6537
6538 #[test]
6539 fn duplicate_dataset_name_rejected() {
6540 let path = temp_path("dup_name");
6541 let file = H5File::create(&path).unwrap();
6542 let _ = file.new_dataset::<i32>().shape([2]).create("d").unwrap();
6543 assert!(file.new_dataset::<i32>().shape([2]).create("d").is_err());
6544 std::fs::remove_file(&path).ok();
6545 }
6546
6547 #[test]
6548 fn extend_cannot_shrink() {
6549 let path = temp_path("extend_shrink");
6550 let file = H5File::create(&path).unwrap();
6551 let ds = file
6552 .new_dataset::<i32>()
6553 .shape([0])
6554 .chunk(&[2])
6555 .max_shape(&[None])
6556 .create("d")
6557 .unwrap();
6558 ds.append(&[1i32, 2, 3, 4]).unwrap();
6559 assert!(ds.extend(&[2]).is_err());
6561 assert!(ds.extend(&[6]).is_ok());
6563 std::fs::remove_file(&path).ok();
6564 }
6565
6566 #[test]
6567 fn attr_read_roundtrip() {
6568 use crate::types::VarLenUnicode;
6569 let path = temp_path("attr_read");
6570
6571 {
6572 let file = H5File::create(&path).unwrap();
6573 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
6574 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
6575 let a1 = ds
6576 .new_attr::<VarLenUnicode>()
6577 .shape(())
6578 .create("units")
6579 .unwrap();
6580 a1.write_string("meters").unwrap();
6581 let a2 = ds
6582 .new_attr::<VarLenUnicode>()
6583 .shape(())
6584 .create("desc")
6585 .unwrap();
6586 a2.write_string("test data").unwrap();
6587 file.close().unwrap();
6588 }
6589 {
6590 let file = H5File::open(&path).unwrap();
6591 let ds = file.dataset("data").unwrap();
6592
6593 let names = ds.attr_names().unwrap();
6594 assert!(names.contains(&"units".to_string()));
6595 assert!(names.contains(&"desc".to_string()));
6596
6597 let units = ds.attr("units").unwrap();
6598 assert_eq!(units.read_string().unwrap(), "meters");
6599
6600 let desc = ds.attr("desc").unwrap();
6601 assert_eq!(desc.read_string().unwrap(), "test data");
6602 }
6603
6604 std::fs::remove_file(&path).ok();
6605 }
6606
6607 #[test]
6608 fn type_mismatch_element_size() {
6609 let path = temp_path("type_mismatch");
6610
6611 {
6612 let file = H5File::create(&path).unwrap();
6613 let ds = file.new_dataset::<f64>().shape([4]).create("data").unwrap();
6614 ds.write_raw(&[1.0f64, 2.0, 3.0, 4.0]).unwrap();
6615 file.close().unwrap();
6616 }
6617
6618 {
6619 let file = H5File::open(&path).unwrap();
6620 let ds = file.dataset("data").unwrap();
6621 let result = ds.read_raw::<u8>();
6623 assert!(result.is_err());
6624 }
6625
6626 std::fs::remove_file(&path).ok();
6627 }
6628
6629 #[test]
6630 fn dataset_survives_file_move() {
6631 let path = temp_path("ds_survives");
6632
6633 let ds = {
6634 let file = H5File::create(&path).unwrap();
6635 file.new_dataset::<u8>().shape([4]).create("x").unwrap()
6636 };
6637 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
6639 std::fs::remove_file(&path).ok();
6642 }
6643
6644 #[test]
6645 fn new_attr_scalar_string() {
6646 use crate::types::VarLenUnicode;
6647
6648 let path = temp_path("attr_scalar_string");
6649 {
6650 let file = H5File::create(&path).unwrap();
6651 let ds = file.new_dataset::<u8>().shape([4]).create("data").unwrap();
6652 ds.write_raw(&[1u8, 2, 3, 4]).unwrap();
6653
6654 let attr = ds
6655 .new_attr::<VarLenUnicode>()
6656 .shape(())
6657 .create("name")
6658 .unwrap();
6659 attr.write_scalar(&VarLenUnicode("test_value".to_string()))
6660 .unwrap();
6661
6662 file.close().unwrap();
6663 }
6664
6665 {
6667 use crate::format::messages::datatype::DatatypeMessage;
6668 let file = H5File::open(&path).unwrap();
6669 let ds = file.dataset("data").unwrap();
6670 assert_eq!(ds.shape(), vec![4]);
6671 let readback = ds.read_raw::<u8>().unwrap();
6672 assert_eq!(readback, vec![1u8, 2, 3, 4]);
6673
6674 let attr = ds.attr("name").unwrap();
6677 assert!(
6678 matches!(
6679 attr.datatype().unwrap(),
6680 DatatypeMessage::VarLenString { .. }
6681 ),
6682 "string attribute should have a variable-length string datatype"
6683 );
6684 assert_eq!(attr.read_string().unwrap(), "test_value");
6685 }
6686
6687 std::fs::remove_file(&path).ok();
6688 }
6689
6690 #[test]
6691 fn all_numeric_types_roundtrip() {
6692 let path = temp_path("all_types");
6693
6694 {
6695 let file = H5File::create(&path).unwrap();
6696
6697 let ds = file.new_dataset::<u8>().shape([2]).create("u8").unwrap();
6698 ds.write_raw(&[1u8, 2]).unwrap();
6699
6700 let ds = file.new_dataset::<i8>().shape([2]).create("i8").unwrap();
6701 ds.write_raw(&[-1i8, 1]).unwrap();
6702
6703 let ds = file.new_dataset::<u16>().shape([2]).create("u16").unwrap();
6704 ds.write_raw(&[100u16, 200]).unwrap();
6705
6706 let ds = file.new_dataset::<i16>().shape([2]).create("i16").unwrap();
6707 ds.write_raw(&[-100i16, 100]).unwrap();
6708
6709 let ds = file.new_dataset::<u32>().shape([2]).create("u32").unwrap();
6710 ds.write_raw(&[1000u32, 2000]).unwrap();
6711
6712 let ds = file.new_dataset::<i32>().shape([2]).create("i32").unwrap();
6713 ds.write_raw(&[-1000i32, 1000]).unwrap();
6714
6715 let ds = file.new_dataset::<u64>().shape([2]).create("u64").unwrap();
6716 ds.write_raw(&[10000u64, 20000]).unwrap();
6717
6718 let ds = file.new_dataset::<i64>().shape([2]).create("i64").unwrap();
6719 ds.write_raw(&[-10000i64, 10000]).unwrap();
6720
6721 let ds = file.new_dataset::<f32>().shape([2]).create("f32").unwrap();
6722 ds.write_raw(&[1.5f32, 2.5]).unwrap();
6723
6724 let ds = file.new_dataset::<f64>().shape([2]).create("f64").unwrap();
6725 ds.write_raw(&[1.23456f64, 7.89012]).unwrap();
6726
6727 file.close().unwrap();
6728 }
6729
6730 {
6731 let file = H5File::open(&path).unwrap();
6732
6733 assert_eq!(
6734 file.dataset("u8").unwrap().read_raw::<u8>().unwrap(),
6735 vec![1u8, 2]
6736 );
6737 assert_eq!(
6738 file.dataset("i8").unwrap().read_raw::<i8>().unwrap(),
6739 vec![-1i8, 1]
6740 );
6741 assert_eq!(
6742 file.dataset("u16").unwrap().read_raw::<u16>().unwrap(),
6743 vec![100u16, 200]
6744 );
6745 assert_eq!(
6746 file.dataset("i16").unwrap().read_raw::<i16>().unwrap(),
6747 vec![-100i16, 100]
6748 );
6749 assert_eq!(
6750 file.dataset("u32").unwrap().read_raw::<u32>().unwrap(),
6751 vec![1000u32, 2000]
6752 );
6753 assert_eq!(
6754 file.dataset("i32").unwrap().read_raw::<i32>().unwrap(),
6755 vec![-1000i32, 1000]
6756 );
6757 assert_eq!(
6758 file.dataset("u64").unwrap().read_raw::<u64>().unwrap(),
6759 vec![10000u64, 20000]
6760 );
6761 assert_eq!(
6762 file.dataset("i64").unwrap().read_raw::<i64>().unwrap(),
6763 vec![-10000i64, 10000]
6764 );
6765 assert_eq!(
6766 file.dataset("f32").unwrap().read_raw::<f32>().unwrap(),
6767 vec![1.5f32, 2.5]
6768 );
6769 assert_eq!(
6770 file.dataset("f64").unwrap().read_raw::<f64>().unwrap(),
6771 vec![1.23456f64, 7.89012]
6772 );
6773 }
6774
6775 std::fs::remove_file(&path).ok();
6776 }
6777
6778 #[test]
6779 fn append_chunked_roundtrip() {
6780 let path = temp_path("append_chunked");
6781
6782 {
6783 let file = H5File::create(&path).unwrap();
6784 let ds = file
6785 .new_dataset::<f64>()
6786 .shape([0, 3])
6787 .chunk(&[1, 3])
6788 .max_shape(&[None, Some(3)])
6789 .create("data")
6790 .unwrap();
6791
6792 ds.append(&[1.0f64, 2.0, 3.0]).unwrap();
6794 ds.append(&[4.0f64, 5.0, 6.0, 7.0, 8.0, 9.0]).unwrap();
6796
6797 file.close().unwrap();
6798 }
6799
6800 {
6801 let file = H5File::open(&path).unwrap();
6802 let ds = file.dataset("data").unwrap();
6803 assert_eq!(ds.shape(), vec![3, 3]);
6804 let all = ds.read_raw::<f64>().unwrap();
6805 assert_eq!(all, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
6806 }
6807
6808 std::fs::remove_file(&path).ok();
6809 }
6810
6811 #[test]
6812 fn append_1d_chunked() {
6813 let path = temp_path("append_1d");
6814
6815 {
6816 let file = H5File::create(&path).unwrap();
6817 let ds = file
6818 .new_dataset::<i32>()
6819 .shape([0])
6820 .chunk(&[4])
6821 .max_shape(&[None])
6822 .create("values")
6823 .unwrap();
6824
6825 ds.append(&[10i32, 20, 30]).unwrap(); ds.append(&[40i32]).unwrap(); ds.append(&[50i32, 60, 70, 80]).unwrap(); file.close().unwrap();
6830 }
6831
6832 {
6833 let file = H5File::open(&path).unwrap();
6834 let ds = file.dataset("values").unwrap();
6835 assert_eq!(ds.shape(), vec![8]);
6836 let all = ds.read_raw::<i32>().unwrap();
6837 assert_eq!(all, vec![10, 20, 30, 40, 50, 60, 70, 80]);
6838 }
6839
6840 std::fs::remove_file(&path).ok();
6841 }
6842
6843 #[test]
6844 fn append_partial_chunk_flushed_on_close() {
6845 let path = temp_path("append_partial_close");
6846
6847 {
6848 let file = H5File::create(&path).unwrap();
6849 let ds = file
6850 .new_dataset::<f64>()
6851 .shape([0])
6852 .chunk(&[4])
6853 .max_shape(&[None])
6854 .create("vals")
6855 .unwrap();
6856
6857 ds.append(&[1.0f64, 2.0, 3.0, 4.0, 5.0]).unwrap();
6859 file.close().unwrap();
6860 }
6861
6862 {
6863 let file = H5File::open(&path).unwrap();
6864 let ds = file.dataset("vals").unwrap();
6865 assert_eq!(ds.shape(), vec![5]);
6866 let all = ds.read_raw::<f64>().unwrap();
6867 assert_eq!(all.len(), 5);
6870 assert_eq!(all, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
6871 }
6872
6873 std::fs::remove_file(&path).ok();
6874 }
6875
6876 #[test]
6884 fn append_after_reopen_keeps_the_partial_chunk_it_lands_in() {
6885 let path = temp_path("append_reopen_partial");
6886
6887 {
6888 let file = H5File::create(&path).unwrap();
6889 let ds = file
6890 .new_dataset::<i32>()
6891 .shape([0, 3])
6892 .chunk(&[4, 3])
6893 .max_shape(&[None, Some(3)])
6894 .create("values")
6895 .unwrap();
6896 ds.append(&[1, 2, 3]).unwrap();
6897 file.close().unwrap();
6898 }
6899 for rows in [
6900 vec![4, 5, 6],
6901 vec![7, 8, 9, 10, 11, 12, 13, 14, 15],
6902 vec![16, 17, 18],
6903 ] {
6904 let file = H5File::open_rw(&path).unwrap();
6905 file.dataset_writer("values")
6906 .unwrap()
6907 .append(&rows)
6908 .unwrap();
6909 file.close().unwrap();
6910 }
6911
6912 let file = H5File::open(&path).unwrap();
6913 let ds = file.dataset("values").unwrap();
6914 assert_eq!(ds.shape(), vec![6, 3]);
6915 assert_eq!(
6916 ds.read_raw::<i32>().unwrap(),
6917 (1..=18).collect::<Vec<i32>>()
6918 );
6919 std::fs::remove_file(&path).ok();
6920 }
6921
6922 #[cfg(feature = "deflate")]
6923 #[test]
6924 fn vlen_append_after_reopen_filtered() {
6925 let path = temp_path("vlen_reopen_filtered");
6929 {
6930 let file = H5File::create(&path).unwrap();
6931 file.create_appendable_vlen_dataset(
6932 "strs",
6933 4,
6934 Some(crate::format::messages::filter::FilterPipeline::deflate(6)),
6935 )
6936 .unwrap();
6937 file.append_vlen_strings("strs", &["alpha", "beta", "gamma"])
6938 .unwrap();
6939 file.close().unwrap();
6940 }
6941 {
6942 let file = H5File::open_rw(&path).unwrap();
6943 file.append_vlen_strings("strs", &["delta"]).unwrap();
6944 file.close().unwrap();
6945 }
6946 {
6947 let file = H5File::open(&path).unwrap();
6948 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
6949 assert_eq!(
6950 got.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
6951 vec!["alpha", "beta", "gamma", "delta"]
6952 );
6953 }
6954 std::fs::remove_file(&path).ok();
6955 }
6956
6957 #[test]
6958 fn vlen_append_after_reopen_data_block() {
6959 let path = temp_path("vlen_reopen_datablk");
6963 let labels: Vec<String> = (0..9).map(|i| format!("s{i}")).collect();
6964 {
6965 let file = H5File::create(&path).unwrap();
6966 file.create_appendable_vlen_dataset("strs", 2, None)
6967 .unwrap();
6968 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
6969 file.append_vlen_strings("strs", &refs).unwrap();
6970 file.close().unwrap();
6971 }
6972 {
6973 let file = H5File::open_rw(&path).unwrap();
6974 file.append_vlen_strings("strs", &["s9"]).unwrap();
6975 file.close().unwrap();
6976 }
6977 {
6978 let file = H5File::open(&path).unwrap();
6979 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
6980 let want: Vec<String> = (0..10).map(|i| format!("s{i}")).collect();
6981 assert_eq!(got, want);
6982 }
6983 std::fs::remove_file(&path).ok();
6984 }
6985
6986 #[test]
6987 fn vlen_append_after_reopen_super_block() {
6988 let path = temp_path("vlen_reopen_super");
6996 let labels: Vec<String> = (0..489).map(|i| format!("v{i}")).collect();
6999 {
7000 let file = H5File::create(&path).unwrap();
7001 file.create_appendable_vlen_dataset("strs", 2, None)
7002 .unwrap();
7003 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
7004 file.append_vlen_strings("strs", &refs).unwrap();
7005 file.close().unwrap();
7006 }
7007 {
7008 let file = H5File::open_rw(&path).unwrap();
7009 file.append_vlen_strings("strs", &["v489"]).unwrap();
7010 file.close().unwrap();
7011 }
7012 {
7013 let file = H5File::open(&path).unwrap();
7014 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
7015 let want: Vec<String> = (0..490).map(|i| format!("v{i}")).collect();
7016 assert_eq!(got, want);
7017 }
7018 std::fs::remove_file(&path).ok();
7019 }
7020
7021 #[cfg(feature = "deflate")]
7022 #[test]
7023 fn vlen_append_after_reopen_filtered_data_block() {
7024 let path = temp_path("vlen_reopen_filt_datablk");
7027 let labels: Vec<String> = (0..9).map(|i| format!("item{i:02}")).collect();
7028 {
7029 let file = H5File::create(&path).unwrap();
7030 file.create_appendable_vlen_dataset(
7031 "strs",
7032 2,
7033 Some(crate::format::messages::filter::FilterPipeline::deflate(6)),
7034 )
7035 .unwrap();
7036 let refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
7037 file.append_vlen_strings("strs", &refs).unwrap();
7038 file.close().unwrap();
7039 }
7040 {
7041 let file = H5File::open_rw(&path).unwrap();
7042 file.append_vlen_strings("strs", &["item09"]).unwrap();
7043 file.close().unwrap();
7044 }
7045 {
7046 let file = H5File::open(&path).unwrap();
7047 let got = file.dataset("strs").unwrap().read_vlen_strings().unwrap();
7048 let want: Vec<String> = (0..10).map(|i| format!("item{i:02}")).collect();
7049 assert_eq!(got, want);
7050 }
7051 std::fs::remove_file(&path).ok();
7052 }
7053
7054 #[test]
7055 fn group_nx_class_attribute_roundtrip() {
7056 let path = temp_path("group_nx_class");
7059 {
7060 let file = H5File::create(&path).unwrap();
7061 let entry = file.create_group("entry").unwrap();
7062 entry.set_attr_string("NX_class", "NXentry").unwrap();
7063 let det = entry.create_group("detector").unwrap();
7064 det.set_attr_string("NX_class", "NXdetector").unwrap();
7065 det.set_attr_numeric("frame_count", &7i32).unwrap();
7066 det.new_dataset::<f32>()
7067 .shape([4])
7068 .create("data")
7069 .unwrap()
7070 .write_raw(&[1.0f32; 4])
7071 .unwrap();
7072 file.close().unwrap();
7073 }
7074 {
7075 let file = H5File::open(&path).unwrap();
7076 let entry = file.root_group().group("entry").unwrap();
7077 assert_eq!(entry.attr_string("NX_class").unwrap(), "NXentry");
7078 let det = entry.group("detector").unwrap();
7079 assert_eq!(det.attr_string("NX_class").unwrap(), "NXdetector");
7080 let names = det.attr_names().unwrap();
7081 assert!(names.contains(&"NX_class".to_string()));
7082 assert!(names.contains(&"frame_count".to_string()));
7083 }
7084 std::fs::remove_file(&path).ok();
7085 }
7086
7087 #[test]
7088 fn ea_super_block_roundtrip() {
7089 let path = temp_path("ea_super_rt");
7092 {
7093 let file = H5File::create(&path).unwrap();
7094 let ds = file
7095 .new_dataset::<i32>()
7096 .shape([0])
7097 .chunk(&[1])
7098 .max_shape(&[None])
7099 .create("v")
7100 .unwrap();
7101 ds.append(&(0..2000).collect::<Vec<i32>>()).unwrap();
7102 file.close().unwrap();
7103 }
7104 {
7105 let file = H5File::open(&path).unwrap();
7106 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7107 assert_eq!(v.len(), 2000);
7108 assert!(v.iter().enumerate().all(|(i, &x)| x == i as i32));
7109 }
7110 std::fs::remove_file(&path).ok();
7111 }
7112
7113 #[cfg(feature = "deflate")]
7114 #[test]
7115 fn ea_filtered_super_block_roundtrip() {
7116 let path = temp_path("ea_filt_super");
7118 {
7119 let file = H5File::create(&path).unwrap();
7120 let ds = file
7121 .new_dataset::<i32>()
7122 .shape([0])
7123 .chunk(&[1])
7124 .max_shape(&[None])
7125 .deflate(4)
7126 .create("v")
7127 .unwrap();
7128 ds.append(&(0..600).collect::<Vec<i32>>()).unwrap();
7129 file.close().unwrap();
7130 }
7131 {
7132 let file = H5File::open(&path).unwrap();
7133 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7134 assert_eq!(v, (0..600).collect::<Vec<i32>>());
7135 }
7136 std::fs::remove_file(&path).ok();
7137 }
7138
7139 #[test]
7140 fn ea_super_block_open_append() {
7141 let path = temp_path("ea_super_append");
7143 {
7144 let file = H5File::create(&path).unwrap();
7145 let ds = file
7146 .new_dataset::<i32>()
7147 .shape([0])
7148 .chunk(&[1])
7149 .max_shape(&[None])
7150 .create("v")
7151 .unwrap();
7152 ds.append(&(0..300).collect::<Vec<i32>>()).unwrap();
7153 file.close().unwrap();
7154 }
7155 {
7156 let w = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7157 let idx = w.dataset_index("v").unwrap();
7158 for c in 300..900u64 {
7159 w.write_chunk(idx, c, &(c as i32).to_le_bytes()).unwrap();
7160 }
7161 w.extend_dataset(idx, &[900]).unwrap();
7162 w.close().unwrap();
7163 }
7164 {
7165 let file = H5File::open(&path).unwrap();
7166 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7167 assert_eq!(v.len(), 900);
7168 assert!(v.iter().enumerate().all(|(i, &x)| x == i as i32));
7169 }
7170 std::fs::remove_file(&path).ok();
7171 }
7172
7173 #[cfg(feature = "deflate")]
7179 #[test]
7180 fn compressed_multi_unlimited_dataset_roundtrips() {
7181 let path = temp_path("bt2_filtered");
7182 {
7183 let file = H5File::create(&path).unwrap();
7184 let ds = file
7185 .new_dataset::<i32>()
7186 .shape([6, 8])
7187 .chunk(&[2, 4])
7188 .max_shape(&[None, None])
7189 .deflate(6)
7190 .create("d")
7191 .unwrap();
7192 ds.write_slice(&[0, 0], &[6, 8], &[7i32; 48]).unwrap();
7193 ds.write_slice(&[1, 1], &[2, 2], &[1i32, 2, 3, 4]).unwrap();
7196 file.close().unwrap();
7197 }
7198 {
7199 let file = H5File::open(&path).unwrap();
7200 let ds = file.dataset("d").unwrap();
7201 assert_eq!(ds.shape(), vec![6, 8]);
7202 let mut want = vec![7i32; 48];
7203 want[9] = 1;
7204 want[10] = 2;
7205 want[17] = 3;
7206 want[18] = 4;
7207 assert_eq!(ds.read_raw::<i32>().unwrap(), want);
7208 }
7209 std::fs::remove_file(&path).ok();
7210 }
7211
7212 #[test]
7213 fn btree_v2_multi_unlimited_roundtrip() {
7214 let path = temp_path("bt2_multi");
7217 {
7218 let file = H5File::create(&path).unwrap();
7219 let ds = file
7220 .new_dataset::<i32>()
7221 .shape([0, 0])
7222 .chunk(&[2, 2])
7223 .max_shape(&[None, None])
7224 .create("grid")
7225 .unwrap();
7226 assert!(ds.is_chunked());
7227 for cr in 0..2usize {
7229 for cc in 0..2usize {
7230 let mut bytes = Vec::new();
7231 for i in 0..2usize {
7232 for j in 0..2usize {
7233 let v = ((cr * 2 + i) * 4 + (cc * 2 + j)) as i32;
7234 bytes.extend_from_slice(&v.to_le_bytes());
7235 }
7236 }
7237 ds.write_chunk_at(&[cr, cc], &bytes).unwrap();
7238 }
7239 }
7240 file.close().unwrap();
7241 }
7242 {
7243 let file = H5File::open(&path).unwrap();
7244 let ds = file.dataset("grid").unwrap();
7245 assert_eq!(ds.shape(), vec![4, 4]);
7246 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..16).collect::<Vec<i32>>());
7247 }
7248 std::fs::remove_file(&path).ok();
7249 }
7250
7251 #[test]
7252 fn subframe_chunking_roundtrip() {
7253 let path = temp_path("subframe");
7257 {
7258 let file = H5File::create(&path).unwrap();
7259 let ds = file
7260 .new_dataset::<i32>()
7261 .shape([0, 8, 8])
7262 .chunk(&[1, 4, 4])
7263 .max_shape(&[None, Some(8), Some(8)])
7264 .create("v")
7265 .unwrap();
7266 for f in 0..3usize {
7267 for cr in 0..2usize {
7268 for cc in 0..2usize {
7269 let mut bytes = Vec::new();
7270 for i in 0..4usize {
7271 for j in 0..4usize {
7272 let v = (f * 64 + (cr * 4 + i) * 8 + (cc * 4 + j)) as i32;
7273 bytes.extend_from_slice(&v.to_le_bytes());
7274 }
7275 }
7276 ds.write_chunk_at(&[f, cr, cc], &bytes).unwrap();
7277 }
7278 }
7279 }
7280 file.close().unwrap();
7281 }
7282 {
7283 let file = H5File::open(&path).unwrap();
7284 let ds = file.dataset("v").unwrap();
7285 assert_eq!(ds.shape(), vec![3, 8, 8]);
7286 assert_eq!(
7287 ds.read_raw::<i32>().unwrap(),
7288 (0..192).collect::<Vec<i32>>()
7289 );
7290 }
7291 std::fs::remove_file(&path).ok();
7292 }
7293
7294 #[test]
7295 fn fill_value_contiguous_roundtrip() {
7296 let path = temp_path("fill_value_contig");
7297 {
7298 let file = H5File::create(&path).unwrap();
7299 let ds = file
7300 .new_dataset::<f32>()
7301 .shape([4])
7302 .fill_value(2.5f32)
7303 .create("data")
7304 .unwrap();
7305 ds.write_raw(&[1.0f32, 2.0, 3.0, 4.0]).unwrap();
7306 file.close().unwrap();
7307 }
7308 {
7310 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7311 let idx = writer.dataset_index("data").unwrap();
7312 assert_eq!(
7313 writer.ds(idx).lock().fill_value,
7314 Some(2.5f32.to_le_bytes().to_vec())
7315 );
7316 }
7317 {
7319 let file = H5File::open(&path).unwrap();
7320 let ds = file.dataset("data").unwrap();
7321 assert_eq!(ds.read_raw::<f32>().unwrap(), vec![1.0, 2.0, 3.0, 4.0]);
7322 }
7323 std::fs::remove_file(&path).ok();
7324 }
7325
7326 #[test]
7331 fn early_allocation_writes_the_implicit_index() {
7332 let path = temp_path("implicit_index");
7333 {
7334 let file = H5File::create(&path).unwrap();
7335 let ds = file
7336 .new_dataset::<i32>()
7337 .shape([16])
7338 .chunk(&[4])
7339 .early_allocation()
7340 .create("data")
7341 .unwrap();
7342 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap();
7343 file.close().unwrap();
7344 }
7345 let bytes = std::fs::read(&path).unwrap();
7346 for magic in [b"EAHD", b"EAIB", b"FAHD", b"FADB", b"BTHD", b"TREE"] {
7347 assert!(
7348 !bytes.windows(4).any(|w| w == magic),
7349 "{} appears in a file whose chunk index is supposed to be no \
7350 structure at all",
7351 String::from_utf8_lossy(magic)
7352 );
7353 }
7354 {
7355 let file = H5File::open(&path).unwrap();
7356 let ds = file.dataset("data").unwrap();
7357 assert_eq!(
7358 ds.read_raw::<i32>().unwrap(),
7359 (0..16i32).collect::<Vec<_>>()
7360 );
7361 }
7362 std::fs::remove_file(&path).ok();
7363 }
7364
7365 #[test]
7371 #[cfg(feature = "deflate")]
7372 fn early_allocation_only_picks_implicit_where_libhdf5_does() {
7373 for (which, magic) in [("unlimited", b"EAHD"), ("filtered", b"FAHD")] {
7376 let path = temp_path("implicit_not");
7377 {
7378 let file = H5File::create(&path).unwrap();
7379 let builder = file.new_dataset::<i32>().shape([16]);
7380 let builder = match which {
7381 "unlimited" => builder.chunk(&[4]).max_shape(&[None]),
7382 _ => builder.chunk(&[4]).deflate(6),
7383 };
7384 let ds = builder.early_allocation().create("data").unwrap();
7385 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap();
7386 file.close().unwrap();
7387 }
7388 let bytes = std::fs::read(&path).unwrap();
7389 assert!(
7390 bytes.windows(4).any(|w| w == magic),
7391 "{which}: expected a {} index",
7392 String::from_utf8_lossy(magic)
7393 );
7394 let file = H5File::open(&path).unwrap();
7395 assert_eq!(
7396 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
7397 (0..16i32).collect::<Vec<_>>(),
7398 "{which}"
7399 );
7400 std::fs::remove_file(&path).ok();
7401 }
7402 }
7403
7404 #[test]
7412 fn one_whole_dataset_chunk_writes_the_single_chunk_index() {
7413 for early in [false, true] {
7414 let path = temp_path("single_chunk_index");
7415 {
7416 let file = H5File::create(&path).unwrap();
7417 let builder = file.new_dataset::<i32>().shape([16]).chunk(&[16]);
7418 let builder = if early {
7419 builder.early_allocation()
7420 } else {
7421 builder
7422 };
7423 let ds = builder.create("data").unwrap();
7424 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap();
7425 file.close().unwrap();
7426 }
7427 let bytes = std::fs::read(&path).unwrap();
7428 for magic in [b"EAHD", b"EAIB", b"FAHD", b"FADB", b"BTHD", b"TREE"] {
7429 assert!(
7430 !bytes.windows(4).any(|w| w == magic),
7431 "early={early}: {} appears in a file whose chunk index is \
7432 supposed to be no structure at all",
7433 String::from_utf8_lossy(magic)
7434 );
7435 }
7436 let file = H5File::open(&path).unwrap();
7437 assert_eq!(
7438 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
7439 (0..16i32).collect::<Vec<_>>(),
7440 "early={early}"
7441 );
7442 std::fs::remove_file(&path).ok();
7443 }
7444 }
7445
7446 #[test]
7450 fn implicit_index_fills_every_chunk_at_create() {
7451 let path = temp_path("implicit_fill");
7452 {
7453 let file = H5File::create(&path).unwrap();
7454 let ds = file
7455 .new_dataset::<i32>()
7456 .shape([8])
7457 .chunk(&[4])
7458 .early_allocation()
7459 .fill_value(-3i32)
7460 .create("data")
7461 .unwrap();
7462 let chunk: Vec<u8> = [1i32, 2, 3, 4]
7464 .iter()
7465 .flat_map(|v| v.to_le_bytes())
7466 .collect();
7467 ds.write_chunk(0, &chunk).unwrap();
7468 file.close().unwrap();
7469 }
7470 let file = H5File::open(&path).unwrap();
7471 let ds = file.dataset("data").unwrap();
7472 assert_eq!(
7473 ds.read_raw::<i32>().unwrap(),
7474 vec![1, 2, 3, 4, -3, -3, -3, -3]
7475 );
7476 std::fs::remove_file(&path).ok();
7477 }
7478
7479 #[test]
7484 fn implicit_index_survives_a_reopen() {
7485 let path = temp_path("implicit_reopen");
7486 {
7487 let file = H5File::create(&path).unwrap();
7488 file.new_dataset::<i32>()
7489 .shape([8])
7490 .chunk(&[4])
7491 .early_allocation()
7492 .create("data")
7493 .unwrap()
7494 .write_raw(&[0i32, 1, 2, 3, 4, 5, 6, 7])
7495 .unwrap();
7496 file.close().unwrap();
7497 }
7498 {
7499 let file = H5File::open_rw(&path).unwrap();
7500 let ds = file.dataset_writer("data").unwrap();
7501 let chunk: Vec<u8> = [10i32, 11, 12, 13]
7502 .iter()
7503 .flat_map(|v| v.to_le_bytes())
7504 .collect();
7505 ds.write_chunk(1, &chunk).unwrap();
7506 file.close().unwrap();
7507 }
7508 let file = H5File::open(&path).unwrap();
7509 assert_eq!(
7510 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
7511 vec![0, 1, 2, 3, 10, 11, 12, 13]
7512 );
7513 std::fs::remove_file(&path).ok();
7514 }
7515
7516 #[test]
7517 fn fill_value_chunked_roundtrip() {
7518 let path = temp_path("fill_value_chunked");
7519 {
7520 let file = H5File::create(&path).unwrap();
7521 let ds = file
7522 .new_dataset::<i32>()
7523 .shape([0])
7524 .chunk(&[4])
7525 .max_shape(&[None])
7526 .fill_value(-7i32)
7527 .create("vals")
7528 .unwrap();
7529 ds.append(&[1i32, 2, 3, 4]).unwrap();
7530 file.close().unwrap();
7531 }
7532 {
7533 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7534 let idx = writer.dataset_index("vals").unwrap();
7535 assert_eq!(
7536 writer.ds(idx).lock().fill_value,
7537 Some((-7i32).to_le_bytes().to_vec())
7538 );
7539 }
7540 std::fs::remove_file(&path).ok();
7541 }
7542
7543 #[test]
7544 fn fill_value_bytes_takes_the_stored_type_in_its_declared_order() {
7545 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
7546 let be12 = DatatypeMessage::FixedPoint {
7547 size: 2,
7548 byte_order: ByteOrder::BigEndian,
7549 signed: false,
7550 bit_offset: 0,
7551 bit_precision: 12,
7552 };
7553 let path = temp_path("fill_value_bytes");
7554 {
7555 let file = H5File::create(&path).unwrap();
7556 file.new_dataset::<u16>()
7557 .shape([0])
7558 .chunk(&[4])
7559 .max_shape(&[None])
7560 .datatype(be12.clone())
7561 .fill_value_bytes(&0x0abcu16.to_ne_bytes())
7562 .create("vals")
7563 .unwrap();
7564 let err = file
7565 .new_dataset::<u16>()
7566 .shape([4])
7567 .datatype(be12)
7568 .fill_value_bytes(&[1, 2, 3])
7569 .create("short");
7570 assert!(matches!(err, Err(crate::Hdf5Error::TypeMismatch(_))));
7571 file.close().unwrap();
7572 }
7573 {
7574 let writer = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7575 let idx = writer.dataset_index("vals").unwrap();
7576 assert_eq!(writer.ds(idx).lock().fill_value, Some(vec![0x0a, 0xbc]));
7577 assert!(writer.dataset_index("short").is_none());
7578 }
7579 std::fs::remove_file(&path).ok();
7580 }
7581
7582 #[test]
7583 fn fill_value_read_missing_chunks() {
7584 fn i32_bytes(vals: &[i32]) -> Vec<u8> {
7587 vals.iter().flat_map(|v| v.to_le_bytes()).collect()
7588 }
7589 let path = temp_path("fill_value_read_missing");
7590 {
7591 let file = H5File::create(&path).unwrap();
7592 let ds = file
7593 .new_dataset::<i32>()
7594 .shape([0])
7595 .chunk(&[2])
7596 .max_shape(&[None])
7597 .fill_value(-1i32)
7598 .create("vals")
7599 .unwrap();
7600 ds.write_chunk(0, &i32_bytes(&[10, 20])).unwrap();
7602 ds.write_chunk(2, &i32_bytes(&[50, 60])).unwrap();
7603 ds.extend(&[6]).unwrap();
7604 file.close().unwrap();
7605 }
7606 {
7607 let file = H5File::open(&path).unwrap();
7608 let ds = file.dataset("vals").unwrap();
7609 let all = ds.read_raw::<i32>().unwrap();
7610 assert_eq!(all, vec![10, 20, -1, -1, 50, 60]);
7611 }
7612 std::fs::remove_file(&path).ok();
7613 }
7614
7615 #[test]
7616 fn fill_value_partial_chunk_padded_with_fill() {
7617 let path = temp_path("fill_value_partial_pad");
7621 {
7622 let file = H5File::create(&path).unwrap();
7623 let ds = file
7624 .new_dataset::<i32>()
7625 .shape([0])
7626 .chunk(&[4])
7627 .max_shape(&[None])
7628 .fill_value(-9i32)
7629 .create("vals")
7630 .unwrap();
7631 ds.append(&[1i32, 2, 3]).unwrap();
7633 file.close().unwrap();
7634 }
7635 let bytes = std::fs::read(&path).unwrap();
7636 let needle: Vec<u8> = [1i32, 2, 3].iter().flat_map(|v| v.to_le_bytes()).collect();
7638 let pos = bytes
7639 .windows(needle.len())
7640 .position(|w| w == needle)
7641 .expect("chunk data [1,2,3] not found in file");
7642 let pad = &bytes[pos + needle.len()..pos + needle.len() + 4];
7643 assert_eq!(
7644 pad,
7645 &(-9i32).to_le_bytes(),
7646 "partial chunk tail must be padded with fill value -9, got {:?}",
7647 pad
7648 );
7649 std::fs::remove_file(&path).ok();
7650 }
7651
7652 #[test]
7653 fn vlen_append_after_reopen_preserves_existing() {
7654 let path = temp_path("vlen_append_reopen");
7657 {
7658 let file = H5File::create(&path).unwrap();
7659 file.create_appendable_vlen_dataset("strs", 4, None)
7660 .unwrap();
7661 file.append_vlen_strings("strs", &["a", "b", "c"]).unwrap();
7663 file.close().unwrap();
7664 }
7665 {
7666 let file = H5File::open_rw(&path).unwrap();
7668 file.append_vlen_strings("strs", &["d"]).unwrap();
7669 file.close().unwrap();
7670 }
7671 {
7672 let file = H5File::open(&path).unwrap();
7673 let ds = file.dataset("strs").unwrap();
7674 let got = ds.read_vlen_strings().unwrap();
7675 assert_eq!(
7676 got.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
7677 vec!["a", "b", "c", "d"]
7678 );
7679 }
7680 std::fs::remove_file(&path).ok();
7681 }
7682
7683 #[test]
7684 fn fill_value_size_mismatch_errors() {
7685 let path = temp_path("fill_value_mismatch");
7686 let writer = crate::io::writer::Hdf5Writer::create(&path).unwrap();
7687 let dt = <f64 as crate::types::H5Type>::hdf5_type();
7688 let idx = writer.create_dataset("d", dt, &[4u64]).unwrap();
7689 assert!(writer.set_dataset_fill_value(idx, vec![0u8; 4]).is_err());
7691 writer.set_dataset_fill_value(idx, vec![0u8; 8]).unwrap();
7693 writer.close().unwrap();
7694 std::fs::remove_file(&path).ok();
7695 }
7696
7697 #[test]
7698 fn datatype_exposes_class_sign_and_byteorder() {
7699 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
7703
7704 let path = temp_path("datatype_accessor");
7705 {
7706 let file = H5File::create(&path).unwrap();
7707 file.new_dataset::<u8>().shape([3]).create("u8d").unwrap();
7708 file.new_dataset::<i8>().shape([3]).create("i8d").unwrap();
7709 file.new_dataset::<i32>().shape([3]).create("i32d").unwrap();
7710 file.new_dataset::<f32>().shape([3]).create("f32d").unwrap();
7711 file.close().unwrap();
7712 }
7713
7714 let file = H5File::open(&path).unwrap();
7715
7716 match file.dataset("u8d").unwrap().datatype().unwrap() {
7717 DatatypeMessage::FixedPoint {
7718 size,
7719 signed,
7720 byte_order,
7721 ..
7722 } => {
7723 assert_eq!(size, 1);
7724 assert!(!signed, "u8 must be unsigned");
7725 assert_eq!(byte_order, ByteOrder::LittleEndian);
7726 }
7727 other => panic!("expected FixedPoint for u8, got {other:?}"),
7728 }
7729
7730 match file.dataset("i8d").unwrap().datatype().unwrap() {
7731 DatatypeMessage::FixedPoint { size, signed, .. } => {
7732 assert_eq!(size, 1);
7733 assert!(signed, "i8 must be signed");
7734 }
7735 other => panic!("expected FixedPoint for i8, got {other:?}"),
7736 }
7737
7738 match file.dataset("i32d").unwrap().datatype().unwrap() {
7739 DatatypeMessage::FixedPoint { size, signed, .. } => {
7740 assert_eq!(size, 4);
7741 assert!(signed, "i32 must be signed");
7742 }
7743 other => panic!("expected FixedPoint for i32, got {other:?}"),
7744 }
7745
7746 match file.dataset("f32d").unwrap().datatype().unwrap() {
7747 DatatypeMessage::FloatingPoint { size, .. } => assert_eq!(size, 4),
7748 other => panic!("expected FloatingPoint for f32, got {other:?}"),
7749 }
7750
7751 std::fs::remove_file(&path).ok();
7752 }
7753
7754 #[test]
7755 fn datatype_in_write_mode_errors() {
7756 let path = temp_path("datatype_write_mode");
7757 let file = H5File::create(&path).unwrap();
7758 let ds = file.new_dataset::<f32>().shape([4]).create("d").unwrap();
7759 assert!(ds.datatype().is_err());
7760 std::fs::remove_file(&path).ok();
7761 }
7762
7763 #[cfg(feature = "deflate")]
7769 #[test]
7770 fn write_chunk_raw_ea_roundtrip_mask0() {
7771 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7772 let path = temp_path("wcr_ea_mask0");
7773 let original: Vec<i32> = (0..12).collect();
7774 {
7775 let file = H5File::create(&path).unwrap();
7776 let ds = file
7777 .new_dataset::<i32>()
7778 .shape([0])
7779 .chunk(&[4])
7780 .max_shape(&[None])
7781 .deflate(4)
7782 .create("v")
7783 .unwrap();
7784 assert!(ds.is_chunked());
7785 let pipeline = FilterPipeline::deflate(4);
7786 for c in 0..3usize {
7787 let raw: Vec<u8> = original[c * 4..c * 4 + 4]
7788 .iter()
7789 .flat_map(|v| v.to_le_bytes())
7790 .collect();
7791 let compressed = apply_filters(&pipeline, &raw).unwrap();
7792 ds.write_chunk_raw(c, &compressed, 0).unwrap();
7793 }
7794 ds.set_extent(&[12]).unwrap();
7795 file.close().unwrap();
7796 }
7797 {
7798 let file = H5File::open(&path).unwrap();
7799 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7800 assert_eq!(v, original);
7801 }
7802 std::fs::remove_file(&path).ok();
7803 }
7804
7805 #[cfg(feature = "deflate")]
7808 #[test]
7809 fn write_chunk_raw_fixed_array_roundtrip_mask0() {
7810 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7811 let path = temp_path("wcr_fa_mask0");
7812 let original: Vec<i32> = (0..12).collect();
7813 {
7814 let file = H5File::create(&path).unwrap();
7815 let ds = file
7816 .new_dataset::<i32>()
7817 .shape([12])
7818 .chunk(&[4])
7819 .deflate(4)
7820 .create("v")
7821 .unwrap();
7822 assert!(ds.is_chunked());
7823 let pipeline = FilterPipeline::deflate(4);
7824 for c in 0..3usize {
7825 let raw: Vec<u8> = original[c * 4..c * 4 + 4]
7826 .iter()
7827 .flat_map(|v| v.to_le_bytes())
7828 .collect();
7829 let compressed = apply_filters(&pipeline, &raw).unwrap();
7830 ds.write_chunk_raw(c, &compressed, 0).unwrap();
7831 }
7832 file.close().unwrap();
7833 }
7834 {
7835 let file = H5File::open(&path).unwrap();
7836 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
7837 assert_eq!(v, original);
7838 }
7839 std::fs::remove_file(&path).ok();
7840 }
7841
7842 #[cfg(feature = "deflate")]
7848 #[test]
7849 fn write_chunk_raw_records_filter_mask() {
7850 let path = temp_path("wcr_records_mask");
7851 let raw: Vec<u8> = [10i32, 20, 30, 40]
7852 .iter()
7853 .flat_map(|v| v.to_le_bytes())
7854 .collect();
7855 assert_eq!(raw.len(), 16);
7856 {
7857 let file = H5File::create(&path).unwrap();
7858 let ds = file
7859 .new_dataset::<i32>()
7860 .shape([0])
7861 .chunk(&[4])
7862 .max_shape(&[None])
7863 .deflate(4)
7864 .create("v")
7865 .unwrap();
7866 ds.write_chunk_raw(0, &raw, 1).unwrap();
7869 ds.set_extent(&[4]).unwrap();
7870 file.close().unwrap();
7871 }
7872 {
7875 let w = crate::io::writer::Hdf5Writer::open_append(&path).unwrap();
7876 let idx = w.dataset_index("v").unwrap();
7877 let ds = w.ds(idx);
7878 let m = ds.lock();
7879 let entry = &m
7880 .chunked
7881 .as_ref()
7882 .unwrap()
7883 .filt_iblk
7884 .as_ref()
7885 .unwrap()
7886 .elements[0];
7887 assert_eq!(entry.filter_mask, 1, "filter_mask must round-trip to disk");
7888 assert_eq!(entry.nbytes, 16, "uncompressed chunk stored verbatim");
7889 }
7890 std::fs::remove_file(&path).ok();
7891 }
7892
7893 #[cfg(feature = "deflate")]
7904 #[test]
7905 fn a_chunk_that_decodes_short_of_its_image_reads_as_fill() {
7906 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7907 let path = temp_path("short_chunk_image_is_fill");
7908 let pipeline = FilterPipeline::deflate(4);
7909 {
7910 let file = H5File::create(&path).unwrap();
7911 let ds = file
7912 .new_dataset::<i32>()
7913 .shape([0])
7914 .chunk(&[4])
7915 .max_shape(&[None])
7916 .fill_value(-1i32)
7917 .deflate(4)
7918 .create("v")
7919 .unwrap();
7920 let short: Vec<u8> = [7i32, 8].iter().flat_map(|v| v.to_le_bytes()).collect();
7922 ds.write_chunk_raw(0, &apply_filters(&pipeline, &short).unwrap(), 0)
7923 .unwrap();
7924 let whole: Vec<u8> = [9i32, 10, 11, 12]
7926 .iter()
7927 .flat_map(|v| v.to_le_bytes())
7928 .collect();
7929 ds.write_chunk_raw(2, &apply_filters(&pipeline, &whole).unwrap(), 0)
7930 .unwrap();
7931 ds.set_extent(&[12]).unwrap();
7932 file.close().unwrap();
7933 }
7934 {
7935 let file = H5File::open(&path).unwrap();
7936 let ds = file.dataset("v").unwrap();
7937 assert_eq!(
7938 ds.read_raw::<i32>().unwrap(),
7939 vec![-1, -1, -1, -1, -1, -1, -1, -1, 9, 10, 11, 12]
7940 );
7941 assert_eq!(ds.read_slice::<i32>(&[0], &[4]).unwrap(), vec![-1; 4]);
7944 assert_eq!(
7945 ds.read_slice::<i32>(&[8], &[4]).unwrap(),
7946 vec![9, 10, 11, 12]
7947 );
7948 }
7949 std::fs::remove_file(&path).ok();
7950 }
7951
7952 #[cfg(feature = "deflate")]
7958 #[test]
7959 fn a_staged_chunk_carries_only_what_its_stream_decoded() {
7960 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7961 let path = temp_path("short_chunk_image_staged");
7962 let pipeline = FilterPipeline::deflate(4);
7963 {
7964 let file = H5File::create(&path).unwrap();
7965 let ds = file
7966 .new_dataset::<i32>()
7967 .shape([0])
7968 .chunk(&[4])
7969 .max_shape(&[None])
7970 .fill_value(-1i32)
7971 .deflate(4)
7972 .create("v")
7973 .unwrap();
7974 let short: Vec<u8> = [7i32, 8].iter().flat_map(|v| v.to_le_bytes()).collect();
7975 ds.write_chunk_raw(0, &apply_filters(&pipeline, &short).unwrap(), 0)
7976 .unwrap();
7977 ds.set_extent(&[4]).unwrap();
7978 file.close().unwrap();
7979 }
7980 {
7981 let file = H5File::open(&path).unwrap();
7982 let ds = file.dataset("v").unwrap();
7983 assert_eq!(ds.read_slice::<i32>(&[0], &[2]).unwrap(), vec![7, 8]);
7985 assert_eq!(ds.read_slice::<i32>(&[1], &[2]).unwrap(), vec![-1, -1]);
7987 }
7988 std::fs::remove_file(&path).ok();
7989 }
7990
7991 #[cfg(feature = "deflate")]
7992 #[test]
7993 fn write_chunk_raw_ea_per_chunk_mask_roundtrip() {
7994 use crate::format::messages::filter::{apply_filters, FilterPipeline};
7995 let path = temp_path("wcr_ea_per_chunk_mask");
7996 let original: Vec<i32> = (0..8).collect();
7997 let pipeline = FilterPipeline::deflate(4);
7998 {
7999 let file = H5File::create(&path).unwrap();
8000 let ds = file
8001 .new_dataset::<i32>()
8002 .shape([0])
8003 .chunk(&[4])
8004 .max_shape(&[None])
8005 .deflate(4)
8006 .create("v")
8007 .unwrap();
8008 let raw0: Vec<u8> = original[0..4]
8009 .iter()
8010 .flat_map(|v| v.to_le_bytes())
8011 .collect();
8012 ds.write_chunk_raw(0, &apply_filters(&pipeline, &raw0).unwrap(), 0)
8014 .unwrap();
8015 let raw1: Vec<u8> = original[4..8]
8016 .iter()
8017 .flat_map(|v| v.to_le_bytes())
8018 .collect();
8019 ds.write_chunk_raw(1, &raw1, 1).unwrap();
8021 ds.set_extent(&[8]).unwrap();
8022 file.close().unwrap();
8023 }
8024 {
8025 let file = H5File::open(&path).unwrap();
8026 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
8027 assert_eq!(v, original);
8028 }
8029 std::fs::remove_file(&path).ok();
8030 }
8031
8032 #[cfg(feature = "deflate")]
8035 #[test]
8036 fn write_chunk_raw_fixed_array_per_chunk_mask_roundtrip() {
8037 use crate::format::messages::filter::{apply_filters, FilterPipeline};
8038 let path = temp_path("wcr_fa_per_chunk_mask");
8039 let original: Vec<i32> = (0..8).collect();
8040 let pipeline = FilterPipeline::deflate(4);
8041 {
8042 let file = H5File::create(&path).unwrap();
8043 let ds = file
8044 .new_dataset::<i32>()
8045 .shape([8])
8046 .chunk(&[4])
8047 .deflate(4)
8048 .create("v")
8049 .unwrap();
8050 let raw0: Vec<u8> = original[0..4]
8051 .iter()
8052 .flat_map(|v| v.to_le_bytes())
8053 .collect();
8054 ds.write_chunk_raw(0, &apply_filters(&pipeline, &raw0).unwrap(), 0)
8055 .unwrap();
8056 let raw1: Vec<u8> = original[4..8]
8057 .iter()
8058 .flat_map(|v| v.to_le_bytes())
8059 .collect();
8060 ds.write_chunk_raw(1, &raw1, 1).unwrap();
8061 file.close().unwrap();
8062 }
8063 {
8064 let file = H5File::open(&path).unwrap();
8065 let v = file.dataset("v").unwrap().read_raw::<i32>().unwrap();
8066 assert_eq!(v, original);
8067 }
8068 std::fs::remove_file(&path).ok();
8069 }
8070
8071 #[test]
8074 fn write_chunk_raw_rejects_unfiltered() {
8075 let path = temp_path("wcr_unfiltered");
8076 let file = H5File::create(&path).unwrap();
8077 let ds = file
8078 .new_dataset::<i32>()
8079 .shape([0])
8080 .chunk(&[4])
8081 .max_shape(&[None])
8082 .create("v")
8083 .unwrap();
8084 let err = ds.write_chunk_raw(0, &[0u8; 16], 0).unwrap_err();
8085 assert!(
8086 err.to_string().contains("filtered dataset"),
8087 "expected a filtered-dataset error, got: {err}"
8088 );
8089 std::fs::remove_file(&path).ok();
8090 }
8091
8092 #[test]
8096 fn write_chunk_raw_sends_btree_v2_to_the_coordinate_form() {
8097 let path = temp_path("wcr_btree2");
8098 let file = H5File::create(&path).unwrap();
8099 let ds = file
8100 .new_dataset::<i32>()
8101 .shape([0, 0])
8102 .chunk(&[2, 2])
8103 .max_shape(&[None, None])
8104 .create("grid")
8105 .unwrap();
8106 let err = ds.write_chunk_raw(0, &[0u8; 16], 0).unwrap_err();
8107 assert!(
8108 err.to_string().contains("write_chunk_raw_at"),
8109 "expected a pointer to the coordinate form, got: {err}"
8110 );
8111 std::fs::remove_file(&path).ok();
8112 }
8113
8114 #[cfg(feature = "deflate")]
8119 #[test]
8120 fn write_chunk_raw_at_round_trips_on_btree_v2() {
8121 use crate::format::messages::filter::{apply_filters, FilterPipeline};
8122
8123 let path = temp_path("wcr_at_btree2");
8124 let raw0: Vec<u8> = (0..4i32).flat_map(|v| v.to_le_bytes()).collect();
8125 let raw1: Vec<u8> = (100..104i32).flat_map(|v| v.to_le_bytes()).collect();
8126 {
8127 let file = H5File::create(&path).unwrap();
8128 let ds = file
8129 .new_dataset::<i32>()
8130 .shape([0, 0])
8131 .chunk(&[2, 2])
8132 .max_shape(&[None, None])
8133 .deflate(6)
8134 .create("grid")
8135 .unwrap();
8136 let pipeline = FilterPipeline::deflate(6);
8137 ds.write_chunk_raw_at(&[0, 0], &apply_filters(&pipeline, &raw0).unwrap(), 0)
8139 .unwrap();
8140 ds.write_chunk_raw_at(&[1, 1], &raw1, 1).unwrap();
8142 file.close().unwrap();
8143 }
8144 let file = H5File::open(&path).unwrap();
8145 let ds = file.dataset("grid").unwrap();
8146 assert_eq!(ds.shape(), vec![4, 4]);
8147 let all = ds.read_raw::<i32>().unwrap();
8148 assert_eq!([all[0], all[1], all[4], all[5]], [0, 1, 2, 3]);
8150 assert_eq!([all[10], all[11], all[14], all[15]], [100, 101, 102, 103]);
8152 drop(file);
8153 std::fs::remove_file(&path).ok();
8154 }
8155
8156 #[cfg(feature = "deflate")]
8159 #[test]
8160 fn write_chunk_raw_at_round_trips_on_the_array_indexes() {
8161 for (label, max_shape) in [
8162 ("wcr_at_ea", Some(vec![None, Some(4usize)])),
8163 ("wcr_at_fa", None),
8164 ] {
8165 let path = temp_path(label);
8166 let raw: Vec<u8> = (0..8i32).flat_map(|v| v.to_le_bytes()).collect();
8167 {
8168 let file = H5File::create(&path).unwrap();
8169 let mut b = file
8170 .new_dataset::<i32>()
8171 .shape([4usize, 4])
8172 .chunk(&[2, 4])
8173 .deflate(6);
8174 if let Some(ref ms) = max_shape {
8175 b = b.max_shape(ms);
8176 }
8177 let ds = b.create("grid").unwrap();
8178 ds.write_chunk_raw_at(&[1, 0], &raw, 1).unwrap();
8180 file.close().unwrap();
8181 }
8182 let file = H5File::open(&path).unwrap();
8183 let ds = file.dataset("grid").unwrap();
8184 let all = ds.read_raw::<i32>().unwrap();
8185 assert_eq!(&all[8..16], &(0..8).collect::<Vec<i32>>()[..], "{label}");
8186 drop(file);
8187 std::fs::remove_file(&path).ok();
8188 }
8189 }
8190
8191 #[cfg(feature = "deflate")]
8195 #[test]
8196 fn write_chunk_raw_at_rejects_an_oversized_btree_v2_chunk() {
8197 let path = temp_path("wcr_at_oversized");
8198 let file = H5File::create(&path).unwrap();
8199 let ds = file
8200 .new_dataset::<i32>()
8201 .shape([0, 0])
8202 .chunk(&[1, 1])
8203 .max_shape(&[None, None])
8204 .deflate(4)
8205 .create("grid")
8206 .unwrap();
8207 let err = ds
8208 .write_chunk_raw_at(&[0, 0], &vec![0u8; 70000], 0)
8209 .unwrap_err();
8210 assert!(
8211 err.to_string().contains("does not fit"),
8212 "expected a chunk-size-field overflow error, got: {err}"
8213 );
8214 std::fs::remove_file(&path).ok();
8215 }
8216
8217 #[test]
8220 fn write_chunk_raw_at_rejects_an_unfiltered_btree_v2() {
8221 let path = temp_path("wcr_at_unfiltered");
8222 let file = H5File::create(&path).unwrap();
8223 let ds = file
8224 .new_dataset::<i32>()
8225 .shape([0, 0])
8226 .chunk(&[2, 2])
8227 .max_shape(&[None, None])
8228 .create("grid")
8229 .unwrap();
8230 let err = ds.write_chunk_raw_at(&[0, 0], &[0u8; 16], 0).unwrap_err();
8231 assert!(
8232 err.to_string().contains("filtered dataset"),
8233 "expected a filtered-dataset error, got: {err}"
8234 );
8235 std::fs::remove_file(&path).ok();
8236 }
8237
8238 #[cfg(feature = "deflate")]
8243 #[test]
8244 fn write_chunk_raw_rejects_oversized_chunk() {
8245 let path = temp_path("wcr_oversized");
8246 let file = H5File::create(&path).unwrap();
8247 let ds = file
8248 .new_dataset::<i32>()
8249 .shape([0])
8250 .chunk(&[1])
8251 .max_shape(&[None])
8252 .deflate(4)
8253 .create("v")
8254 .unwrap();
8255 let err = ds.write_chunk_raw(0, &vec![0u8; 70000], 0).unwrap_err();
8256 assert!(
8257 err.to_string().contains("does not fit"),
8258 "expected a chunk-size-field overflow error, got: {err}"
8259 );
8260 std::fs::remove_file(&path).ok();
8261 }
8262
8263 use crate::format::messages::datatype::{CompoundMember, DatatypeMessage};
8266
8267 fn write_fixed_string_dataset(
8270 path: &std::path::Path,
8271 dt: DatatypeMessage,
8272 width: usize,
8273 elems: &[&[u8]],
8274 compressed: bool,
8275 ) {
8276 let mut raw = Vec::with_capacity(elems.len() * width);
8277 for e in elems {
8278 assert!(e.len() <= width);
8279 raw.extend_from_slice(e);
8280 raw.resize(raw.len() + (width - e.len()), 0);
8281 }
8282 let file = H5File::create(path).unwrap();
8283 let mut b = file.new_dataset::<u8>().datatype(dt).shape([elems.len()]);
8284 if compressed {
8285 b = b.chunk(&[2]).deflate(6);
8286 }
8287 let ds = b.create("labels").unwrap();
8288 ds.write_raw_bytes(&raw).unwrap();
8289 file.close().unwrap();
8290 }
8291
8292 #[test]
8295 fn read_strings_handles_any_fixed_width() {
8296 for width in [4usize, 24, 100] {
8297 let path = temp_path(&format!("fixed_str_{width}"));
8298 write_fixed_string_dataset(
8299 &path,
8300 DatatypeMessage::fixed_string(width as u32),
8301 width,
8302 &[b"ab", b"cde", b""],
8303 false,
8304 );
8305 let file = H5File::open(&path).unwrap();
8306 let got = file.dataset("labels").unwrap().read_strings().unwrap();
8307 assert_eq!(got, vec!["ab", "cde", ""], "width {width}");
8308 std::fs::remove_file(&path).ok();
8309 }
8310 }
8311
8312 #[test]
8322 fn read_strings_honors_every_padding_rule() {
8323 let elem: &[u8] = b"ab\0X\0\0";
8325 for (padding, want) in [(0u8, "ab"), (1, "ab")] {
8326 let path = temp_path(&format!("fixed_pad_{padding}"));
8327 write_fixed_string_dataset(
8328 &path,
8329 DatatypeMessage::FixedString {
8330 size: 6,
8331 padding,
8332 charset: 0,
8333 },
8334 6,
8335 &[elem],
8336 false,
8337 );
8338 let file = H5File::open(&path).unwrap();
8339 let got = file.dataset("labels").unwrap().read_strings().unwrap();
8340 assert_eq!(got, vec![want.to_string()], "padding {padding}");
8341 std::fs::remove_file(&path).ok();
8342 }
8343 let path = temp_path("fixed_pad_2");
8345 write_fixed_string_dataset(
8346 &path,
8347 DatatypeMessage::FixedString {
8348 size: 8,
8349 padding: 2,
8350 charset: 0,
8351 },
8352 8,
8353 &[b"a b "],
8354 false,
8355 );
8356 let file = H5File::open(&path).unwrap();
8357 assert_eq!(
8358 file.dataset("labels").unwrap().read_strings().unwrap(),
8359 vec!["a b".to_string()]
8360 );
8361 std::fs::remove_file(&path).ok();
8362
8363 let path = temp_path("fixed_pad_2_nul");
8365 write_fixed_string_dataset(
8366 &path,
8367 DatatypeMessage::FixedString {
8368 size: 8,
8369 padding: 2,
8370 charset: 0,
8371 },
8372 8,
8373 &[b"a\0b "],
8374 false,
8375 );
8376 let file = H5File::open(&path).unwrap();
8377 assert_eq!(
8378 file.dataset("labels").unwrap().read_strings().unwrap(),
8379 vec!["a\0b".to_string()]
8380 );
8381 std::fs::remove_file(&path).ok();
8382 }
8383
8384 #[test]
8387 fn read_strings_rejects_reserved_datatype_codes() {
8388 for (padding, charset, want) in [(3u8, 0u8, "padding rule 3"), (0, 7, "character set 7")] {
8389 let path = temp_path(&format!("fixed_reserved_{padding}_{charset}"));
8390 write_fixed_string_dataset(
8391 &path,
8392 DatatypeMessage::FixedString {
8393 size: 4,
8394 padding,
8395 charset,
8396 },
8397 4,
8398 &[b"ab"],
8399 false,
8400 );
8401 let file = H5File::open(&path).unwrap();
8402 let err = file
8403 .dataset("labels")
8404 .unwrap()
8405 .read_strings()
8406 .unwrap_err()
8407 .to_string();
8408 assert!(err.contains(want), "got: {err}");
8409 std::fs::remove_file(&path).ok();
8410 }
8411 }
8412
8413 #[test]
8417 fn to_host_byte_order_converts_scalars_and_refuses_composites() {
8418 use crate::dataset::{to_host_byte_order, HOST_BYTE_ORDER};
8419 use crate::format::messages::datatype::ByteOrder;
8420
8421 let foreign = match HOST_BYTE_ORDER {
8422 ByteOrder::LittleEndian => ByteOrder::BigEndian,
8423 ByteOrder::BigEndian => ByteOrder::LittleEndian,
8424 };
8425 let int = |order, size| DatatypeMessage::FixedPoint {
8426 size,
8427 byte_order: order,
8428 signed: false,
8429 bit_offset: 0,
8430 bit_precision: (size * 8) as u16,
8431 };
8432
8433 let mut buf = [1u8, 2, 3, 4, 5, 6, 7, 8];
8435 to_host_byte_order(&mut buf, &int(foreign, 4), 4).unwrap();
8436 assert_eq!(buf, [4, 3, 2, 1, 8, 7, 6, 5]);
8437
8438 let mut buf = [1u8, 2, 3, 4];
8440 to_host_byte_order(&mut buf, &int(HOST_BYTE_ORDER, 4), 4).unwrap();
8441 assert_eq!(buf, [1, 2, 3, 4]);
8442
8443 let mut buf = [1u8, 2, 3, 4];
8445 to_host_byte_order(&mut buf, &int(foreign, 1), 1).unwrap();
8446 assert_eq!(buf, [1, 2, 3, 4]);
8447
8448 let mut buf = [1u8, 2];
8450 let enumeration = DatatypeMessage::Enum {
8451 base: Box::new(int(foreign, 2)),
8452 members: Vec::new(),
8453 };
8454 to_host_byte_order(&mut buf, &enumeration, 2).unwrap();
8455 assert_eq!(buf, [2, 1]);
8456
8457 let mut buf = *b"abcd";
8459 to_host_byte_order(&mut buf, &DatatypeMessage::fixed_string(4), 4).unwrap();
8460 assert_eq!(&buf, b"abcd");
8461
8462 let compound = |order| DatatypeMessage::Compound {
8464 size: 4,
8465 members: vec![CompoundMember {
8466 name: "x".into(),
8467 offset: 0,
8468 datatype: int(order, 4),
8469 }],
8470 };
8471 let mut buf = [1u8, 2, 3, 4];
8472 to_host_byte_order(&mut buf, &compound(HOST_BYTE_ORDER), 4).unwrap();
8473 assert_eq!(buf, [1, 2, 3, 4]);
8474
8475 let mut buf = [1u8, 2, 3, 4];
8477 let err = to_host_byte_order(&mut buf, &compound(foreign), 4)
8478 .expect_err("a foreign-order compound was reinterpreted")
8479 .to_string();
8480 assert!(err.contains("read_raw_bytes"), "got: {err}");
8481 assert_eq!(buf, [1, 2, 3, 4], "the refused image is left alone");
8482 }
8483
8484 #[test]
8488 fn to_stored_byte_order_converts_scalars_and_refuses_composites() {
8489 use crate::dataset::{to_stored_byte_order, FOREIGN_BYTE_ORDER, HOST_BYTE_ORDER};
8490 use std::borrow::Cow;
8491
8492 let int = |order, size| DatatypeMessage::FixedPoint {
8493 size,
8494 byte_order: order,
8495 signed: false,
8496 bit_offset: 0,
8497 bit_precision: (size * 8) as u16,
8498 };
8499
8500 let host = [1u8, 2, 3, 4, 5, 6, 7, 8];
8502 let stored = to_stored_byte_order(&host, &int(FOREIGN_BYTE_ORDER, 4), 4).unwrap();
8503 assert_eq!(&*stored, &[4, 3, 2, 1, 8, 7, 6, 5]);
8504 assert!(matches!(stored, Cow::Owned(_)), "a swap needs its own copy");
8505
8506 let stored = to_stored_byte_order(&host, &int(HOST_BYTE_ORDER, 4), 4).unwrap();
8508 assert!(matches!(stored, Cow::Borrowed(_)), "no copy without a swap");
8509 assert_eq!(&*stored, &host);
8510
8511 let stored = to_stored_byte_order(&host, &int(FOREIGN_BYTE_ORDER, 1), 1).unwrap();
8513 assert_eq!(&*stored, &host);
8514
8515 let enumeration = DatatypeMessage::Enum {
8517 base: Box::new(int(FOREIGN_BYTE_ORDER, 2)),
8518 members: Vec::new(),
8519 };
8520 let stored = to_stored_byte_order(&[1u8, 2], &enumeration, 2).unwrap();
8521 assert_eq!(&*stored, &[2, 1]);
8522
8523 let compound = |order| DatatypeMessage::Compound {
8526 size: 4,
8527 members: vec![CompoundMember {
8528 name: "x".into(),
8529 offset: 0,
8530 datatype: int(order, 4),
8531 }],
8532 };
8533 let stored = to_stored_byte_order(&[1u8, 2, 3, 4], &compound(HOST_BYTE_ORDER), 4).unwrap();
8534 assert_eq!(&*stored, &[1, 2, 3, 4]);
8535 let err = to_stored_byte_order(&[1u8, 2, 3, 4], &compound(FOREIGN_BYTE_ORDER), 4)
8536 .expect_err("a foreign-order compound was written from host bytes")
8537 .to_string();
8538 assert!(err.contains("write_raw_bytes"), "got: {err}");
8539 }
8540
8541 #[test]
8545 fn read_strings_enforces_the_character_set_and_lossy_does_not() {
8546 for (charset, bytes, want) in [
8549 (0u8, b"caf\xe9".as_slice(), "ASCII character set"),
8550 (1, b"a\xff".as_slice(), "not valid UTF-8"),
8551 ] {
8552 let path = temp_path(&format!("fixed_charset_{charset}"));
8553 write_fixed_string_dataset(
8554 &path,
8555 DatatypeMessage::FixedString {
8556 size: 6,
8557 padding: 1,
8558 charset,
8559 },
8560 6,
8561 &[b"ok", bytes],
8562 false,
8563 );
8564 let file = H5File::open(&path).unwrap();
8565 let ds = file.dataset("labels").unwrap();
8566 let err = ds.read_strings().unwrap_err().to_string();
8567 assert!(err.contains(want) && err.contains("string 1"), "got: {err}");
8568 let lossy = ds.read_strings_lossy().unwrap();
8569 assert_eq!(lossy[0], "ok");
8570 assert_eq!(
8571 lossy[1].chars().next().unwrap(),
8572 if charset == 0 { 'c' } else { 'a' }
8573 );
8574 std::fs::remove_file(&path).ok();
8575 }
8576 }
8577
8578 #[test]
8581 fn read_strings_reads_utf8_fixed_strings() {
8582 let path = temp_path("fixed_utf8");
8583 write_fixed_string_dataset(
8584 &path,
8585 DatatypeMessage::fixed_string_utf8(12),
8586 12,
8587 &["héllo".as_bytes(), "안녕".as_bytes()],
8588 false,
8589 );
8590 let file = H5File::open(&path).unwrap();
8591 assert_eq!(
8592 file.dataset("labels").unwrap().read_strings().unwrap(),
8593 vec!["héllo".to_string(), "안녕".to_string()]
8594 );
8595 std::fs::remove_file(&path).ok();
8596 }
8597
8598 #[cfg(feature = "deflate")]
8601 #[test]
8602 fn read_strings_reads_a_compressed_fixed_string_dataset() {
8603 let path = temp_path("fixed_str_deflate");
8604 write_fixed_string_dataset(
8605 &path,
8606 DatatypeMessage::fixed_string(16),
8607 16,
8608 &[b"alpha", b"beta", b"gamma", b"delta", b"epsilon"],
8609 true,
8610 );
8611 let file = H5File::open(&path).unwrap();
8612 assert_eq!(
8613 file.dataset("labels").unwrap().read_strings().unwrap(),
8614 vec!["alpha", "beta", "gamma", "delta", "epsilon"]
8615 );
8616 std::fs::remove_file(&path).ok();
8617 }
8618
8619 #[test]
8622 fn read_strings_also_reads_variable_length_strings() {
8623 let path = temp_path("read_strings_vlen");
8624 {
8625 let file = H5File::create(&path).unwrap();
8626 file.write_vlen_strings("names", &["alpha", "", "안녕"])
8627 .unwrap();
8628 file.close().unwrap();
8629 }
8630 let file = H5File::open(&path).unwrap();
8631 assert_eq!(
8632 file.dataset("names").unwrap().read_strings().unwrap(),
8633 vec!["alpha".to_string(), String::new(), "안녕".to_string()]
8634 );
8635 std::fs::remove_file(&path).ok();
8636 }
8637
8638 #[test]
8643 fn read_strings_rejects_a_zero_width_fixed_string_dataset() {
8644 use crate::format::checksum::checksum_metadata;
8645 use crate::format::object_header::OHDR_SIGNATURE;
8646
8647 let path = temp_path("fixed_str_zero_width");
8648 write_fixed_string_dataset(
8649 &path,
8650 DatatypeMessage::fixed_string(37),
8651 37,
8652 &[b"ab", b"cd"],
8653 false,
8654 );
8655
8656 let mut bytes = std::fs::read(&path).unwrap();
8660 let needle = [0x13u8, 0, 0, 0, 37, 0, 0, 0];
8661 let at = bytes
8662 .windows(needle.len())
8663 .position(|w| w == needle)
8664 .expect("encoded fixed-string datatype message");
8665 assert!(
8666 !bytes[at + 1..].windows(needle.len()).any(|w| w == needle),
8667 "the datatype message pattern is not unique in the file"
8668 );
8669
8670 let ohdr = bytes[..at]
8674 .windows(4)
8675 .rposition(|w| w == OHDR_SIGNATURE)
8676 .expect("enclosing object header");
8677 let cksum_at = (at + needle.len()..bytes.len() - 4)
8678 .find(|&e| {
8679 u32::from_le_bytes(bytes[e..e + 4].try_into().unwrap())
8680 == checksum_metadata(&bytes[ohdr..e])
8681 })
8682 .expect("object header checksum");
8683
8684 bytes[at + 4..at + 8].copy_from_slice(&0u32.to_le_bytes());
8685 let fixed = checksum_metadata(&bytes[ohdr..cksum_at]);
8686 bytes[cksum_at..cksum_at + 4].copy_from_slice(&fixed.to_le_bytes());
8687 std::fs::write(&path, &bytes).unwrap();
8688
8689 let file = H5File::open(&path).unwrap();
8690 let err = file
8691 .dataset("labels")
8692 .unwrap()
8693 .read_strings()
8694 .unwrap_err()
8695 .to_string();
8696 assert!(err.contains("zero width"), "got: {err}");
8697 std::fs::remove_file(&path).ok();
8698 }
8699
8700 #[test]
8702 fn read_strings_rejects_a_non_string_dataset() {
8703 let path = temp_path("read_strings_numeric");
8704 {
8705 let file = H5File::create(&path).unwrap();
8706 let ds = file.new_dataset::<i32>().shape([3]).create("nums").unwrap();
8707 ds.write_raw(&[1i32, 2, 3]).unwrap();
8708 file.close().unwrap();
8709 }
8710 let file = H5File::open(&path).unwrap();
8711 let err = file
8712 .dataset("nums")
8713 .unwrap()
8714 .read_strings()
8715 .unwrap_err()
8716 .to_string();
8717 assert!(err.contains("only for string datasets"), "got: {err}");
8718 std::fs::remove_file(&path).ok();
8719 }
8720
8721 #[test]
8726 fn write_vlen_strings_slice_replaces_one_element() {
8727 let path = temp_path("vlen_slice_contig");
8728 {
8729 let file = H5File::create(&path).unwrap();
8730 file.write_vlen_strings("notes", &["a", "b", "c", "d"])
8731 .unwrap();
8732 file.close().unwrap();
8733 }
8734 {
8735 let file = H5File::open_rw(&path).unwrap();
8736 file.dataset_writer("notes")
8737 .unwrap()
8738 .write_vlen_strings_slice(1, &["replacement"])
8739 .unwrap();
8740 file.close().unwrap();
8741 }
8742 let file = H5File::open(&path).unwrap();
8743 let ds = file.dataset("notes").unwrap();
8744 assert_eq!(ds.shape(), vec![4]);
8745 assert_eq!(
8746 ds.read_vlen_strings().unwrap(),
8747 vec!["a", "replacement", "c", "d"]
8748 );
8749 std::fs::remove_file(&path).ok();
8750 }
8751
8752 #[test]
8755 fn write_vlen_strings_slice_spans_chunks_after_reopen() {
8756 let path = temp_path("vlen_slice_chunked");
8757 {
8758 let file = H5File::create(&path).unwrap();
8759 file.create_appendable_vlen_dataset("notes", 2, None)
8760 .unwrap();
8761 let all: Vec<String> = (0..6).map(|i| format!("v{i}")).collect();
8762 let refs: Vec<&str> = all.iter().map(|s| s.as_str()).collect();
8763 file.append_vlen_strings("notes", &refs).unwrap();
8764 file.close().unwrap();
8765 }
8766 {
8767 let file = H5File::open_rw(&path).unwrap();
8769 file.dataset_writer("notes")
8770 .unwrap()
8771 .write_vlen_strings_slice(1, &["x", "y", "z"])
8772 .unwrap();
8773 file.close().unwrap();
8774 }
8775 let file = H5File::open(&path).unwrap();
8776 let ds = file.dataset("notes").unwrap();
8777 assert_eq!(ds.shape(), vec![6]);
8778 assert_eq!(
8779 ds.read_vlen_strings().unwrap(),
8780 vec!["v0", "x", "y", "z", "v4", "v5"]
8781 );
8782 std::fs::remove_file(&path).ok();
8783 }
8784
8785 #[test]
8789 fn write_vlen_strings_slice_updates_buffered_elements() {
8790 let path = temp_path("vlen_slice_buffered");
8791 {
8792 let file = H5File::create(&path).unwrap();
8793 file.create_appendable_vlen_dataset("notes", 4, None)
8794 .unwrap();
8795 file.append_vlen_strings("notes", &["a", "b", "c"]).unwrap();
8797 file.dataset_writer("notes")
8798 .unwrap()
8799 .write_vlen_strings_slice(1, &["patched"])
8800 .unwrap();
8801 file.close().unwrap();
8802 }
8803 let file = H5File::open(&path).unwrap();
8804 assert_eq!(
8805 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8806 vec!["a", "patched", "c"]
8807 );
8808 std::fs::remove_file(&path).ok();
8809 }
8810
8811 #[test]
8815 fn write_vlen_strings_slice_checks_its_range() {
8816 let build = |name: &str, empty_call: bool| {
8817 let path = temp_path(name);
8818 let file = H5File::create(&path).unwrap();
8819 file.write_vlen_strings("notes", &["a", "b"]).unwrap();
8820 let ds = file.dataset_writer("notes").unwrap();
8821 let err = ds
8822 .write_vlen_strings_slice(1, &["x", "y"])
8823 .unwrap_err()
8824 .to_string();
8825 assert!(
8826 err.contains("outside the dataset's 2 elements"),
8827 "got: {err}"
8828 );
8829 if empty_call {
8830 ds.write_vlen_strings_slice(0, &[]).unwrap();
8831 }
8832 file.close().unwrap();
8833 path
8834 };
8835
8836 let with_empty = build("vlen_slice_range", true);
8837 let control = build("vlen_slice_range_control", false);
8838 assert_eq!(
8839 std::fs::metadata(&with_empty).unwrap().len(),
8840 std::fs::metadata(&control).unwrap().len(),
8841 "the rejected and empty calls must leave the file untouched"
8842 );
8843
8844 let file = H5File::open(&with_empty).unwrap();
8845 assert_eq!(
8846 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8847 vec!["a", "b"]
8848 );
8849 std::fs::remove_file(&with_empty).ok();
8850 std::fs::remove_file(&control).ok();
8851 }
8852
8853 #[test]
8856 fn write_vlen_strings_slice_rejects_a_multidimensional_dataset() {
8857 let path = temp_path("vlen_slice_2d");
8858 let file = H5File::create(&path).unwrap();
8859 let ds = file
8860 .new_dataset::<u8>()
8861 .datatype(DatatypeMessage::vlen_string_utf8())
8862 .shape([2, 3])
8863 .create("grid")
8864 .unwrap();
8865 let err = ds
8866 .write_vlen_strings_slice(0, &["x"])
8867 .unwrap_err()
8868 .to_string();
8869 assert!(err.contains("1-dimension datasets"), "got: {err}");
8870 file.close().unwrap();
8871 std::fs::remove_file(&path).ok();
8872 }
8873
8874 #[test]
8877 fn write_vlen_strings_slice_enforces_the_ascii_character_set() {
8878 let path = temp_path("vlen_slice_ascii");
8879 let file = H5File::create(&path).unwrap();
8880 let ds = file
8881 .new_dataset::<u8>()
8882 .datatype(DatatypeMessage::vlen_string_ascii())
8883 .shape([3])
8884 .create("notes")
8885 .unwrap();
8886 let err = ds
8887 .write_vlen_strings_slice(0, &["ok", "안녕"])
8888 .unwrap_err()
8889 .to_string();
8890 assert!(
8891 err.contains("string 1") && err.contains("is not ASCII"),
8892 "got: {err}"
8893 );
8894 ds.write_vlen_strings_slice(0, &["ok", "fine"]).unwrap();
8895 file.close().unwrap();
8896 std::fs::remove_file(&path).ok();
8897 }
8898
8899 #[test]
8902 fn write_vlen_strings_slice_rejects_a_non_vlen_dataset() {
8903 let path = temp_path("vlen_slice_numeric");
8904 let file = H5File::create(&path).unwrap();
8905 let ds = file.new_dataset::<i32>().shape([3]).create("nums").unwrap();
8906 ds.write_raw(&[1i32, 2, 3]).unwrap();
8907 let err = ds
8908 .write_vlen_strings_slice(0, &["x"])
8909 .unwrap_err()
8910 .to_string();
8911 assert!(
8912 err.contains("only for variable-length string datasets"),
8913 "got: {err}"
8914 );
8915 file.close().unwrap();
8916 std::fs::remove_file(&path).ok();
8917 }
8918
8919 #[test]
8926 fn write_vlen_strings_slice_reuses_the_freed_heap_block() {
8927 let size_after = |updates: usize| {
8928 let path = temp_path(&format!("vlen_slice_heap_reuse_{updates}"));
8929 let file = H5File::create(&path).unwrap();
8930 file.write_vlen_strings("notes", &["a", "b"]).unwrap();
8931 let ds = file.dataset_writer("notes").unwrap();
8932 for i in 0..updates {
8933 ds.write_vlen_strings_slice(0, &[&format!("update {i}")])
8934 .unwrap();
8935 }
8936 file.close().unwrap();
8937 let n = std::fs::metadata(&path).unwrap().len();
8938 let read = H5File::open(&path).unwrap();
8939 assert_eq!(
8940 read.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8941 vec![format!("update {}", updates - 1), "b".to_string()]
8942 );
8943 drop(read);
8944 std::fs::remove_file(&path).ok();
8945 n
8946 };
8947
8948 let settled = size_after(3);
8951 assert_eq!(size_after(20), settled, "20 updates against 3");
8952 assert_eq!(size_after(50), settled, "50 updates against 3");
8953 }
8954
8955 #[test]
8959 fn write_vlen_strings_slice_frees_an_empty_strings_object() {
8960 let size_after = |updates: usize| {
8961 let path = temp_path(&format!("vlen_slice_empty_reuse_{updates}"));
8962 let file = H5File::create(&path).unwrap();
8963 file.write_vlen_strings("notes", &["", "b"]).unwrap();
8964 let ds = file.dataset_writer("notes").unwrap();
8965 for _ in 0..updates {
8966 ds.write_vlen_strings_slice(0, &[""]).unwrap();
8967 }
8968 file.close().unwrap();
8969 let n = std::fs::metadata(&path).unwrap().len();
8970 let read = H5File::open(&path).unwrap();
8971 assert_eq!(
8972 read.dataset("notes").unwrap().read_vlen_strings().unwrap(),
8973 vec!["".to_string(), "b".to_string()]
8974 );
8975 drop(read);
8976 std::fs::remove_file(&path).ok();
8977 n
8978 };
8979
8980 let settled = size_after(3);
8981 assert_eq!(size_after(20), settled, "20 empty updates against 3");
8982 assert_eq!(size_after(50), settled, "50 empty updates against 3");
8983 }
8984
8985 #[test]
8988 fn write_vlen_strings_slice_keeps_the_untouched_strings_readable() {
8989 let path = temp_path("vlen_slice_heap_survivors");
8990 {
8991 let file = H5File::create(&path).unwrap();
8992 file.write_vlen_strings("notes", &["a", "b", "c", "d"])
8993 .unwrap();
8994 let ds = file.dataset_writer("notes").unwrap();
8995 ds.write_vlen_strings_slice(1, &["B"]).unwrap();
8998 ds.write_vlen_strings_slice(3, &["D"]).unwrap();
8999 file.close().unwrap();
9000 }
9001 let file = H5File::open(&path).unwrap();
9002 assert_eq!(
9003 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
9004 vec!["a", "B", "c", "D"]
9005 );
9006 std::fs::remove_file(&path).ok();
9007 }
9008
9009 #[test]
9013 fn write_vlen_strings_slice_frees_an_emptied_collection() {
9014 let path = temp_path("vlen_slice_heap_emptied");
9015 {
9016 let file = H5File::create(&path).unwrap();
9017 file.create_appendable_vlen_dataset("notes", 2, None)
9018 .unwrap();
9019 file.append_vlen_strings("notes", &["p", "q", "r", "s"])
9020 .unwrap();
9021 file.close().unwrap();
9022 }
9023 {
9024 let file = H5File::open_rw(&path).unwrap();
9025 file.dataset_writer("notes")
9026 .unwrap()
9027 .write_vlen_strings_slice(0, &["w", "x", "y", "z"])
9028 .unwrap();
9029 file.close().unwrap();
9030 }
9031 let file = H5File::open(&path).unwrap();
9032 assert_eq!(
9033 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
9034 vec!["w", "x", "y", "z"]
9035 );
9036 std::fs::remove_file(&path).ok();
9037 }
9038
9039 #[test]
9045 fn write_vlen_strings_slice_keeps_an_oversized_collections_block_whole() {
9046 let big = |tag: char| std::iter::repeat_n(tag, 2000).collect::<String>();
9047 let size_after = |cycles: usize| {
9048 let path = temp_path(&format!("vlen_slice_heap_big_{cycles}"));
9049 let file = H5File::create(&path).unwrap();
9050 let seed: Vec<String> = "abcd".chars().map(big).collect();
9051 let refs: Vec<&str> = seed.iter().map(|s| s.as_str()).collect();
9052 file.write_vlen_strings("notes", &refs).unwrap();
9055 let ds = file.dataset_writer("notes").unwrap();
9056 for _ in 0..cycles {
9057 let head = big('x');
9060 ds.write_vlen_strings_slice(0, &[&head]).unwrap();
9061 let tail: Vec<String> = "yzw".chars().map(big).collect();
9062 let tail_refs: Vec<&str> = tail.iter().map(|s| s.as_str()).collect();
9063 ds.write_vlen_strings_slice(1, &tail_refs).unwrap();
9064 }
9065 file.close().unwrap();
9066 let n = std::fs::metadata(&path).unwrap().len();
9067 let read = H5File::open(&path).unwrap();
9068 assert_eq!(
9069 read.dataset("notes").unwrap().read_vlen_strings().unwrap(),
9070 vec![big('x'), big('y'), big('z'), big('w')]
9071 );
9072 drop(read);
9073 std::fs::remove_file(&path).ok();
9074 n
9075 };
9076
9077 let settled = size_after(4);
9078 assert_eq!(size_after(30), settled, "30 cycles against 4");
9079 }
9080
9081 #[test]
9084 fn write_vlen_strings_slice_releases_a_buffered_elements_object() {
9085 let path = temp_path("vlen_slice_heap_buffered");
9086 let file = H5File::create(&path).unwrap();
9087 file.create_appendable_vlen_dataset("notes", 4, None)
9088 .unwrap();
9089 file.append_vlen_strings("notes", &["a", "b", "c"]).unwrap();
9090 let ds = file.dataset_writer("notes").unwrap();
9091 for i in 0..20 {
9092 ds.write_vlen_strings_slice(1, &[&format!("patch {i}")])
9093 .unwrap();
9094 }
9095 file.close().unwrap();
9096
9097 let file = H5File::open(&path).unwrap();
9098 assert_eq!(
9099 file.dataset("notes").unwrap().read_vlen_strings().unwrap(),
9100 vec!["a", "patch 19", "c"]
9101 );
9102 let size = std::fs::metadata(&path).unwrap().len();
9103 std::fs::remove_file(&path).ok();
9104 assert!(
9105 size < 20 * 4096,
9106 "20 buffered updates left {size} bytes, one collection per update"
9107 );
9108 }
9109
9110 #[test]
9115 fn write_slice_into_the_buffered_tail_survives_close() {
9116 let path = temp_path("slice_into_buffered_tail");
9117 {
9118 let file = H5File::create(&path).unwrap();
9119 let ds = file
9120 .new_dataset::<i32>()
9121 .shape([0])
9122 .chunk(&[4])
9123 .max_shape(&[None])
9124 .create("d")
9125 .unwrap();
9126 ds.append(&[10, 11, 12, 13, 50, 51]).unwrap();
9128 ds.write_slice(&[4], &[1], &[99]).unwrap();
9129 file.close().unwrap();
9130 }
9131 {
9132 let file = H5File::open(&path).unwrap();
9133 let ds = file.dataset("d").unwrap();
9134 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![10, 11, 12, 13, 99, 51]);
9135 }
9136 std::fs::remove_file(&path).ok();
9137 }
9138
9139 #[test]
9144 fn extend_does_not_move_buffered_appends() {
9145 let path = temp_path("extend_keeps_buffered_rows");
9146 {
9147 let file = H5File::create(&path).unwrap();
9148 let ds = file
9149 .new_dataset::<i32>()
9150 .shape([0])
9151 .chunk(&[4])
9152 .max_shape(&[None])
9153 .create("d")
9154 .unwrap();
9155 ds.append(&[10, 11, 12, 13, 50, 51]).unwrap(); ds.extend(&[10]).unwrap();
9157 file.close().unwrap();
9158 }
9159 {
9160 let file = H5File::open(&path).unwrap();
9161 let ds = file.dataset("d").unwrap();
9162 assert_eq!(
9163 ds.read_raw::<i32>().unwrap(),
9164 vec![10, 11, 12, 13, 50, 51, 0, 0, 0, 0]
9165 );
9166 }
9167 std::fs::remove_file(&path).ok();
9168 }
9169
9170 #[test]
9176 fn append_to_a_btree_v2_dataset_survives_close() {
9177 let path = temp_path("append_bt2_close");
9178 {
9179 let file = H5File::create(&path).unwrap();
9180 let ds = file
9181 .new_dataset::<i32>()
9182 .shape([0, 3])
9183 .chunk(&[4, 3])
9184 .max_shape(&[None, None])
9185 .create("d")
9186 .unwrap();
9187 ds.append(&[1, 2, 3]).unwrap();
9191 ds.append(&(4..=15).collect::<Vec<i32>>()).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![5, 3]);
9198 assert_eq!(
9199 ds.read_raw::<i32>().unwrap(),
9200 (1..=15).collect::<Vec<i32>>()
9201 );
9202 }
9203 std::fs::remove_file(&path).ok();
9204 }
9205
9206 #[test]
9210 fn append_scatters_frames_across_narrow_chunk_tiles() {
9211 let path = temp_path("append_narrow_chunks");
9212 {
9213 let file = H5File::create(&path).unwrap();
9214 let ds = file
9215 .new_dataset::<i32>()
9216 .shape([0, 8])
9217 .chunk(&[2, 4])
9218 .max_shape(&[None, Some(8)])
9219 .create("d")
9220 .unwrap();
9221 ds.append(&(0..24).collect::<Vec<i32>>()).unwrap();
9224 file.close().unwrap();
9225 }
9226 {
9227 let file = H5File::open(&path).unwrap();
9228 let ds = file.dataset("d").unwrap();
9229 assert_eq!(ds.shape(), vec![3, 8]);
9230 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..24).collect::<Vec<i32>>());
9231 }
9232 std::fs::remove_file(&path).ok();
9233 }
9234
9235 #[test]
9239 fn append_to_a_full_fixed_array_dataset_errors() {
9240 let path = temp_path("append_fa_errors");
9241 let file = H5File::create(&path).unwrap();
9242 let ds = file
9243 .new_dataset::<i32>()
9244 .shape([4, 3])
9245 .chunk(&[2, 3])
9246 .create("d")
9247 .unwrap();
9248 let err = ds.append(&(0..6).collect::<Vec<i32>>()).unwrap_err();
9249 assert!(
9250 err.to_string().contains("chunk grid"),
9251 "unexpected error: {err}"
9252 );
9253 file.close().unwrap();
9254 std::fs::remove_file(&path).ok();
9255 }
9256
9257 #[test]
9263 fn fixed_array_with_a_larger_max_shape_grows_and_survives_close() {
9264 let path = temp_path("fa_growable_dim0");
9265 {
9266 let file = H5File::create(&path).unwrap();
9267 let ds = file
9268 .new_dataset::<i32>()
9269 .shape([4, 3])
9270 .chunk(&[2, 3])
9271 .max_shape(&[Some(10), Some(3)])
9272 .create("d")
9273 .unwrap();
9274 ds.write_raw(&(0..12).collect::<Vec<i32>>()).unwrap();
9275 ds.append(&(12..18).collect::<Vec<i32>>()).unwrap();
9276 file.close().unwrap();
9277 }
9278 {
9279 let file = H5File::open(&path).unwrap();
9280 let ds = file.dataset("d").unwrap();
9281 assert_eq!(ds.shape(), vec![6, 3]);
9282 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..18).collect::<Vec<i32>>());
9283 }
9284 std::fs::remove_file(&path).ok();
9285 }
9286
9287 #[test]
9293 fn fixed_array_growable_inner_dimension_keeps_chunk_slots() {
9294 let path = temp_path("fa_growable_dim1");
9295 {
9296 let file = H5File::create(&path).unwrap();
9297 let ds = file
9298 .new_dataset::<i32>()
9299 .shape([4, 3])
9300 .chunk(&[2, 3])
9301 .max_shape(&[Some(4), Some(9)])
9302 .create("d")
9303 .unwrap();
9304 ds.write_raw(&(0..12).collect::<Vec<i32>>()).unwrap();
9305 ds.extend(&[4, 6]).unwrap();
9306 ds.write_slice(&[0, 3], &[4, 3], &(12..24).collect::<Vec<i32>>())
9307 .unwrap();
9308 file.close().unwrap();
9309 }
9310 {
9311 let file = H5File::open(&path).unwrap();
9312 let ds = file.dataset("d").unwrap();
9313 assert_eq!(ds.shape(), vec![4, 6]);
9314 let mut expect = Vec::new();
9317 for r in 0i32..4 {
9318 expect.extend((r * 3)..(r * 3 + 3));
9319 expect.extend((12 + r * 3)..(12 + r * 3 + 3));
9320 }
9321 assert_eq!(ds.read_raw::<i32>().unwrap(), expect);
9322 }
9323 std::fs::remove_file(&path).ok();
9324 }
9325
9326 #[test]
9330 fn extend_beyond_the_maximum_is_rejected() {
9331 let path = temp_path("extend_beyond_max");
9332 let file = H5File::create(&path).unwrap();
9333 let ds = file
9334 .new_dataset::<i32>()
9335 .shape([4, 3])
9336 .chunk(&[2, 3])
9337 .max_shape(&[Some(6), Some(3)])
9338 .create("d")
9339 .unwrap();
9340 ds.extend(&[6, 3]).unwrap();
9341 let err = ds.extend(&[8, 3]).unwrap_err();
9342 assert!(
9343 err.to_string().contains("exceeds the maximum"),
9344 "unexpected error: {err}"
9345 );
9346 file.close().unwrap();
9347 std::fs::remove_file(&path).ok();
9348 }
9349
9350 #[test]
9356 fn builder_accepts_an_unlimited_inner_dimension() {
9357 let path = temp_path("unlimited_inner_dim");
9358 let file = H5File::create(&path).unwrap();
9359 let ds = file
9360 .new_dataset::<i32>()
9361 .shape([4, 0])
9362 .chunk(&[2, 2])
9363 .max_shape(&[Some(4), None])
9364 .create("d")
9365 .unwrap();
9366 assert_eq!(ds.shape(), vec![4, 0]);
9367
9368 ds.extend(&[4, 2]).unwrap();
9373 ds.write_slice(&[0, 0], &[4, 2], &[1, 2, 3, 4, 5, 6, 7, 8])
9374 .unwrap();
9375 ds.extend(&[4, 4]).unwrap();
9376 ds.write_slice(&[0, 2], &[4, 2], &[9, 10, 11, 12, 13, 14, 15, 16])
9377 .unwrap();
9378
9379 file.close().unwrap();
9380 let file = H5File::open(&path).unwrap();
9381 let ds = file.dataset("d").unwrap();
9382 assert_eq!(
9383 ds.read_slice::<i32>(&[0, 0], &[4, 4]).unwrap(),
9384 vec![1, 2, 9, 10, 3, 4, 11, 12, 5, 6, 13, 14, 7, 8, 15, 16]
9385 );
9386 std::fs::remove_file(&path).ok();
9387 }
9388
9389 #[test]
9394 fn builder_rejects_a_chunk_wider_than_a_fixed_max_dimension() {
9395 let path = temp_path("builder_chunk_wider_than_max");
9396 let file = H5File::create(&path).unwrap();
9397 let err = match file
9398 .new_dataset::<i32>()
9399 .shape([0, 2])
9400 .chunk(&[2, 4])
9401 .max_shape(&[None, Some(2)])
9402 .create("v5")
9403 {
9404 Ok(_) => panic!("create accepted a chunk wider than the fixed max dimension"),
9405 Err(e) => e,
9406 };
9407 assert!(
9408 err.to_string().contains("maximum dimension size"),
9409 "unexpected error: {err}"
9410 );
9411 file.close().unwrap();
9412 std::fs::remove_file(&path).ok();
9413 }
9414
9415 #[test]
9418 fn numeric_int_checked_conversion_boundaries() {
9419 let path = temp_path("numeric_int_bounds");
9420 {
9421 let file = H5File::create(&path).unwrap();
9422 let ds = file.new_dataset::<u64>().shape([2]).create("u").unwrap();
9423 ds.write_raw(&[1u64, u64::MAX]).unwrap();
9424 let ds = file.new_dataset::<i32>().shape([2]).create("i").unwrap();
9425 ds.write_raw(&[-1i32, 5]).unwrap();
9426 file.close().unwrap();
9427 }
9428 let file = H5File::open(&path).unwrap();
9429
9430 let u = file.dataset("u").unwrap();
9431 assert_eq!(u.read_numeric_as::<u64>().unwrap(), vec![1, u64::MAX]);
9432 assert_eq!(
9433 u.read_numeric_as::<i128>().unwrap(),
9434 vec![1, i128::from(u64::MAX)]
9435 );
9436 let err = u.read_numeric_as::<i64>().unwrap_err();
9437 assert!(
9438 err.to_string()
9439 .contains("value 18446744073709551615 at element 1 does not fit in i64"),
9440 "unexpected error: {err}"
9441 );
9442
9443 let i = file.dataset("i").unwrap();
9444 assert_eq!(i.read_numeric_as::<i64>().unwrap(), vec![-1, 5]);
9445 let err = i.read_numeric_as::<u32>().unwrap_err();
9446 assert!(
9447 err.to_string()
9448 .contains("value -1 at element 0 does not fit in u32"),
9449 "unexpected error: {err}"
9450 );
9451 std::fs::remove_file(&path).ok();
9452 }
9453
9454 #[test]
9456 fn numeric_float_widening_only() {
9457 let path = temp_path("numeric_float");
9458 {
9459 let file = H5File::create(&path).unwrap();
9460 let ds = file.new_dataset::<f32>().shape([2]).create("f4").unwrap();
9461 ds.write_raw(&[1.5f32, -2.25]).unwrap();
9462 let ds = file.new_dataset::<f64>().shape([1]).create("f8").unwrap();
9463 ds.write_raw(&[3.75f64]).unwrap();
9464 file.close().unwrap();
9465 }
9466 let file = H5File::open(&path).unwrap();
9467
9468 let f4 = file.dataset("f4").unwrap();
9469 assert_eq!(f4.read_numeric_as::<f32>().unwrap(), vec![1.5, -2.25]);
9470 assert_eq!(f4.read_numeric_as::<f64>().unwrap(), vec![1.5, -2.25]);
9471
9472 let f8 = file.dataset("f8").unwrap();
9473 assert_eq!(f8.read_numeric_as::<f64>().unwrap(), vec![3.75]);
9474 let err = f8.read_numeric_as::<f32>().unwrap_err();
9475 assert!(
9476 err.to_string().contains("narrowing"),
9477 "unexpected error: {err}"
9478 );
9479 std::fs::remove_file(&path).ok();
9480 }
9481
9482 #[test]
9485 fn numeric_cross_class_and_non_numeric_rejected() {
9486 let path = temp_path("numeric_cross_class");
9487 {
9488 let file = H5File::create(&path).unwrap();
9489 let ds = file.new_dataset::<f64>().shape([1]).create("f8").unwrap();
9490 ds.write_raw(&[1.0f64]).unwrap();
9491 let ds = file.new_dataset::<i32>().shape([1]).create("i4").unwrap();
9492 ds.write_raw(&[7i32]).unwrap();
9493 file.write_vlen_strings("s", &["a", "b"]).unwrap();
9494 file.close().unwrap();
9495 }
9496 let file = H5File::open(&path).unwrap();
9497
9498 let err = file
9499 .dataset("f8")
9500 .unwrap()
9501 .read_numeric_as::<i64>()
9502 .unwrap_err();
9503 assert!(
9504 err.to_string().contains("floating-point dataset as i64"),
9505 "unexpected error: {err}"
9506 );
9507 let err = file
9508 .dataset("i4")
9509 .unwrap()
9510 .read_numeric_as::<f64>()
9511 .unwrap_err();
9512 assert!(
9513 err.to_string().contains("integer dataset as f64"),
9514 "unexpected error: {err}"
9515 );
9516 let err = file
9517 .dataset("s")
9518 .unwrap()
9519 .read_numeric_as::<i64>()
9520 .unwrap_err();
9521 assert!(
9522 err.to_string().contains("is not numeric"),
9523 "unexpected error: {err}"
9524 );
9525 std::fs::remove_file(&path).ok();
9526 }
9527
9528 #[test]
9532 fn numeric_big_endian_decode() {
9533 use super::numeric;
9534 use crate::format::messages::datatype::{ByteOrder, DatatypeMessage};
9535
9536 let dt = DatatypeMessage::FixedPoint {
9537 size: 4,
9538 byte_order: ByteOrder::BigEndian,
9539 signed: true,
9540 bit_offset: 0,
9541 bit_precision: 32,
9542 };
9543 let mut raw = Vec::new();
9544 raw.extend_from_slice(&(-2i32).to_be_bytes());
9545 raw.extend_from_slice(&(100_000i32).to_be_bytes());
9546 let kind = numeric::classify(&dt).unwrap();
9547 assert_eq!(
9548 numeric::convert::<i64>(kind, &raw).unwrap(),
9549 vec![-2, 100_000]
9550 );
9551
9552 let dt = DatatypeMessage::FloatingPoint {
9553 size: 8,
9554 byte_order: ByteOrder::BigEndian,
9555 sign_location: 63,
9556 bit_offset: 0,
9557 bit_precision: 64,
9558 exponent_location: 52,
9559 exponent_size: 11,
9560 mantissa_location: 0,
9561 mantissa_size: 52,
9562 exponent_bias: 1023,
9563 };
9564 let raw = (-2.25f64).to_be_bytes();
9565 let kind = numeric::classify(&dt).unwrap();
9566 assert_eq!(numeric::convert::<f64>(kind, &raw).unwrap(), vec![-2.25]);
9567 }
9568
9569 #[test]
9574 fn attr_read_numeric_validates_datatype() {
9575 use crate::types::{Complex64, HBool, VarLenUnicode};
9576 let path = temp_path("attr_read_numeric_validate");
9577 {
9578 let file = H5File::create(&path).unwrap();
9579 let ds = file.new_dataset::<f32>().shape([2]).create("d").unwrap();
9580 ds.write_raw(&[1.0f32; 2]).unwrap();
9581 let a = ds.new_attr::<f64>().shape(()).create("f8").unwrap();
9582 a.write_numeric(&1.5f64).unwrap();
9583 let a = ds.new_attr::<i32>().shape(()).create("i4").unwrap();
9584 a.write_numeric(&-7i32).unwrap();
9585 let a = ds.new_attr::<HBool>().shape(()).create("b").unwrap();
9586 a.write_numeric(&HBool::from(true)).unwrap();
9587 let a = ds.new_attr::<Complex64>().shape(()).create("z").unwrap();
9588 a.write_numeric(&Complex64 { re: 1.0, im: -2.0 }).unwrap();
9589 let a = ds
9590 .new_attr::<VarLenUnicode>()
9591 .shape(())
9592 .create("s")
9593 .unwrap();
9594 a.write_scalar(&VarLenUnicode("text".into())).unwrap();
9595 file.close().unwrap();
9596 }
9597 let file = H5File::open(&path).unwrap();
9598 let ds = file.dataset("d").unwrap();
9599
9600 let f8 = ds.attr("f8").unwrap();
9601 assert_eq!(f8.read_numeric::<f64>().unwrap(), 1.5);
9602 let err = f8.read_numeric::<f32>().unwrap_err();
9604 assert!(
9605 err.to_string().contains("read_numeric_as"),
9606 "unexpected error: {err}"
9607 );
9608 assert!(f8.read_numeric::<i64>().is_err());
9609
9610 let i4 = ds.attr("i4").unwrap();
9611 assert_eq!(i4.read_numeric::<i32>().unwrap(), -7);
9612 assert!(i4.read_numeric::<u32>().is_err());
9613
9614 assert!(bool::from(
9615 ds.attr("b").unwrap().read_numeric::<HBool>().unwrap()
9616 ));
9617 let z = ds.attr("z").unwrap().read_numeric::<Complex64>().unwrap();
9618 assert_eq!((z.re, z.im), (1.0, -2.0));
9619
9620 let s = ds.attr("s").unwrap();
9623 assert!(s.read_numeric::<f64>().is_err());
9624 assert!(s.read_numeric_as::<f64>().is_err());
9625 std::fs::remove_file(&path).ok();
9626 }
9627
9628 #[test]
9632 fn attr_read_numeric_as_converts() {
9633 let path = temp_path("attr_read_numeric_as");
9634 {
9635 let file = H5File::create(&path).unwrap();
9636 let ds = file.new_dataset::<f32>().shape([2]).create("d").unwrap();
9637 ds.write_raw(&[1.0f32; 2]).unwrap();
9638 let a = ds.new_attr::<i32>().shape(()).create("i4").unwrap();
9639 a.write_numeric(&-7i32).unwrap();
9640 let a = ds.new_attr::<u64>().shape(()).create("u8max").unwrap();
9641 a.write_numeric(&u64::MAX).unwrap();
9642 let a = ds.new_attr::<i16>().shape([3]).create("arr").unwrap();
9643 a.write_array(&[1i16, -2, 3]).unwrap();
9644 file.close().unwrap();
9645 }
9646 let file = H5File::open(&path).unwrap();
9647 let ds = file.dataset("d").unwrap();
9648 assert_eq!(
9649 ds.attr("i4").unwrap().read_numeric_as::<i64>().unwrap(),
9650 vec![-7]
9651 );
9652 let err = ds
9653 .attr("u8max")
9654 .unwrap()
9655 .read_numeric_as::<i64>()
9656 .unwrap_err();
9657 assert!(
9658 err.to_string().contains("does not fit in i64"),
9659 "unexpected error: {err}"
9660 );
9661 assert_eq!(
9662 ds.attr("arr").unwrap().read_numeric_as::<i32>().unwrap(),
9663 vec![1, -2, 3]
9664 );
9665 assert!(ds.attr("i4").unwrap().read_numeric_as::<f64>().is_err());
9666 std::fs::remove_file(&path).ok();
9667 }
9668
9669 #[test]
9671 fn numeric_slice_conversion() {
9672 let path = temp_path("numeric_slice");
9673 {
9674 let file = H5File::create(&path).unwrap();
9675 let ds = file.new_dataset::<i16>().shape([2, 3]).create("m").unwrap();
9676 ds.write_raw(&[1i16, 2, 3, 4, 5, 6]).unwrap();
9677 file.close().unwrap();
9678 }
9679 let file = H5File::open(&path).unwrap();
9680 let m = file.dataset("m").unwrap();
9681 assert_eq!(
9682 m.read_numeric_slice_as::<i32>(&[0, 1], &[2, 2]).unwrap(),
9683 vec![2, 3, 5, 6]
9684 );
9685 std::fs::remove_file(&path).ok();
9686 }
9687
9688 #[test]
9693 fn null_dataspace_distinct_from_scalar() {
9694 let path = temp_path("null_vs_scalar");
9695 {
9696 let file = H5File::create(&path).unwrap();
9697 file.new_dataset::<i32>().null().create("empty").unwrap();
9698 let scalar = file.new_dataset::<i32>().scalar().create("scalar").unwrap();
9699 scalar.write_raw(&[42i32]).unwrap();
9700 file.close().unwrap();
9701 }
9702 let file = H5File::open(&path).unwrap();
9703
9704 let empty = file.dataset("empty").unwrap();
9705 assert!(empty.is_null());
9706 assert_eq!(empty.shape(), Vec::<usize>::new());
9707 assert_eq!(empty.total_elements(), 0);
9708 assert_eq!(empty.read_raw_bytes().unwrap(), Vec::<u8>::new());
9709
9710 let scalar = file.dataset("scalar").unwrap();
9711 assert!(!scalar.is_null());
9712 assert_eq!(scalar.shape(), Vec::<usize>::new());
9713 assert_eq!(scalar.total_elements(), 1);
9714 assert_eq!(scalar.read_raw::<i32>().unwrap(), vec![42]);
9715
9716 std::fs::remove_file(&path).ok();
9717 }
9718
9719 #[test]
9723 fn null_dataspace_rejects_writes() {
9724 let path = temp_path("null_write_rejected");
9725 let file = H5File::create(&path).unwrap();
9726 let ds = file.new_dataset::<i32>().null().create("empty").unwrap();
9727 assert!(ds.write_raw(&[1i32]).is_err());
9728 assert!(ds.write_raw_bytes(&[0u8; 4]).is_err());
9729 file.close().unwrap();
9730 std::fs::remove_file(&path).ok();
9731 }
9732
9733 #[test]
9737 fn null_dataspace_rejects_chunking_and_fill_value() {
9738 let path = temp_path("null_chunk_rejected");
9739 let file = H5File::create(&path).unwrap();
9740 assert!(file
9741 .new_dataset::<i32>()
9742 .null()
9743 .chunk(&[4])
9744 .create("a")
9745 .is_err());
9746 assert!(file
9747 .new_dataset::<i32>()
9748 .null()
9749 .fill_value(7i32)
9750 .create("b")
9751 .is_err());
9752 file.close().unwrap();
9753 std::fs::remove_file(&path).ok();
9754 }
9755
9756 #[test]
9761 fn a_committed_type_that_cannot_be_shared_creates_no_dataset() {
9762 use crate::format::messages::datatype::DatatypeMessage;
9763
9764 let path = temp_path("committed_refused");
9765 let file = H5File::create(&path).unwrap();
9766 file.commit_datatype("t", DatatypeMessage::i32_type())
9767 .unwrap();
9768
9769 assert!(file
9770 .new_dataset::<i32>()
9771 .committed_type("absent")
9772 .shape([2usize])
9773 .create("a")
9774 .is_err());
9775 assert!(file
9776 .new_dataset::<u64>()
9777 .committed_type("t")
9778 .object_references()
9779 .shape([2usize])
9780 .create("b")
9781 .is_err());
9782 file.new_dataset::<i32>()
9785 .shape([2usize])
9786 .create("taken")
9787 .unwrap();
9788 assert!(file
9789 .commit_datatype("taken", DatatypeMessage::i32_type())
9790 .is_err());
9791 assert!(file
9792 .commit_datatype("t", DatatypeMessage::f64_type())
9793 .is_err());
9794
9795 assert_eq!(file.dataset_names(), vec!["taken".to_string()]);
9796 assert_eq!(file.named_datatype_names(), vec!["t".to_string()]);
9797 file.close().unwrap();
9798 std::fs::remove_file(&path).ok();
9799 }
9800
9801 #[test]
9805 fn deleting_a_group_takes_the_committed_datatypes_it_named() {
9806 use crate::format::messages::datatype::DatatypeMessage;
9807
9808 let path = temp_path("committed_group_deleted");
9809 {
9810 let file = H5File::create(&path).unwrap();
9811 let types = file.create_group("types").unwrap();
9812 types
9813 .commit_datatype("t", DatatypeMessage::i32_type())
9814 .unwrap();
9815 assert_eq!(file.named_datatype_names(), vec!["types/t".to_string()]);
9816
9817 file.delete_group("types").unwrap();
9818 assert!(file.named_datatype_names().is_empty());
9819 assert!(file
9820 .new_dataset::<i32>()
9821 .committed_type("types/t")
9822 .shape([2usize])
9823 .create("d")
9824 .is_err());
9825
9826 let types = file.create_group("types").unwrap();
9828 types
9829 .commit_datatype("t", DatatypeMessage::f64_type())
9830 .unwrap();
9831 file.close().unwrap();
9832 }
9833 let file = H5File::open(&path).unwrap();
9834 assert_eq!(file.named_datatype_names(), vec!["types/t".to_string()]);
9835 assert_eq!(
9836 file.named_datatype("types/t").unwrap().datatype().unwrap(),
9837 crate::format::messages::datatype::DatatypeMessage::f64_type()
9838 );
9839 drop(file);
9840 std::fs::remove_file(&path).ok();
9841 }
9842
9843 fn compact_image(path: &std::path::Path, name: &str) -> Option<Vec<u8>> {
9847 use crate::format::messages::data_layout::DataLayoutMessage;
9848 let mut reader = crate::io::reader::Hdf5Reader::open(path).unwrap();
9849 match &reader.dataset_info(name).unwrap().layout {
9850 DataLayoutMessage::Compact { data } => Some(data.clone()),
9851 other => panic!("{name}: expected a compact layout, got {other:?}"),
9852 }
9853 }
9854
9855 #[test]
9859 fn a_compact_dataset_stores_its_data_in_the_layout_message() {
9860 let path = temp_path("compact_roundtrip");
9861 let values: Vec<i32> = (0..16).collect();
9862 {
9863 let file = H5File::create(&path).unwrap();
9864 file.new_dataset::<i32>()
9865 .shape([16usize])
9866 .compact()
9867 .create("d")
9868 .unwrap()
9869 .write_raw(&values)
9870 .unwrap();
9871 file.close().unwrap();
9872 }
9873
9874 let image = compact_image(&path, "d").unwrap();
9875 assert_eq!(
9876 image,
9877 values
9878 .iter()
9879 .flat_map(|v| v.to_le_bytes())
9880 .collect::<Vec<_>>()
9881 );
9882
9883 let file = H5File::open(&path).unwrap();
9884 let ds = file.dataset("d").unwrap();
9885 assert_eq!(ds.shape(), vec![16]);
9886 assert_eq!(ds.read_raw::<i32>().unwrap(), values);
9887 std::fs::remove_file(&path).ok();
9888 }
9889
9890 #[test]
9894 fn a_compact_dataset_lands_in_its_group() {
9895 let path = temp_path("compact_in_group");
9896 {
9897 let file = H5File::create(&path).unwrap();
9898 let g = file.root_group().create_group("g").unwrap();
9899 g.new_dataset::<u8>()
9900 .shape([4usize])
9901 .compact()
9902 .create("d")
9903 .unwrap()
9904 .write_raw(&[1u8, 2, 3, 4])
9905 .unwrap();
9906 file.close().unwrap();
9907 }
9908 assert_eq!(compact_image(&path, "/g/d").unwrap(), vec![1u8, 2, 3, 4]);
9909
9910 let file = H5File::open(&path).unwrap();
9911 assert_eq!(
9912 file.dataset("/g/d").unwrap().read_raw::<u8>().unwrap(),
9913 vec![1u8, 2, 3, 4]
9914 );
9915 std::fs::remove_file(&path).ok();
9916 }
9917
9918 #[test]
9922 fn an_unwritten_compact_dataset_reads_back_as_its_fill_value() {
9923 let path = temp_path("compact_fill");
9924 {
9925 let file = H5File::create(&path).unwrap();
9926 file.new_dataset::<i32>()
9927 .shape([4usize])
9928 .compact()
9929 .fill_value(-7i32)
9930 .create("d")
9931 .unwrap();
9932 file.close().unwrap();
9933 }
9934 let file = H5File::open(&path).unwrap();
9935 assert_eq!(
9936 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
9937 vec![-7i32; 4]
9938 );
9939 std::fs::remove_file(&path).ok();
9940 }
9941
9942 #[test]
9947 fn a_reopened_compact_dataset_keeps_its_image() {
9948 let path = temp_path("compact_reopen");
9949 let values: Vec<i32> = (100..108).collect();
9950 {
9951 let file = H5File::create(&path).unwrap();
9952 file.new_dataset::<i32>()
9953 .shape([8usize])
9954 .compact()
9955 .create("d")
9956 .unwrap()
9957 .write_raw(&values)
9958 .unwrap();
9959 file.close().unwrap();
9960 }
9961 {
9962 let file = H5File::open_rw(&path).unwrap();
9963 file.dataset_writer("d")
9964 .unwrap()
9965 .new_attr::<i32>()
9966 .shape(())
9967 .create("note")
9968 .unwrap()
9969 .write_numeric(&1i32)
9970 .unwrap();
9971 file.close().unwrap();
9972 }
9973 assert_eq!(
9974 compact_image(&path, "d").unwrap(),
9975 values
9976 .iter()
9977 .flat_map(|v| v.to_le_bytes())
9978 .collect::<Vec<_>>()
9979 );
9980
9981 let file = H5File::open(&path).unwrap();
9982 assert_eq!(
9983 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
9984 values
9985 );
9986 std::fs::remove_file(&path).ok();
9987 }
9988
9989 #[test]
9993 fn the_compact_ceiling_is_checked_in_bytes() {
9994 let path = temp_path("compact_ceiling");
9995 let file = H5File::create(&path).unwrap();
9996
9997 let fits = crate::MAX_COMPACT_DATA / 4;
9998 file.new_dataset::<i32>()
9999 .shape([fits])
10000 .compact()
10001 .create("fits")
10002 .unwrap();
10003
10004 let over = fits + 1;
10005 let err = match file
10006 .new_dataset::<i32>()
10007 .shape([over])
10008 .compact()
10009 .create("over")
10010 {
10011 Err(e) => e.to_string(),
10012 Ok(_) => panic!("an image {} bytes wide must be refused", over * 4),
10013 };
10014 assert!(
10015 err.contains(&(over * 4).to_string())
10016 && err.contains(&crate::MAX_COMPACT_DATA.to_string()),
10017 "the error must name both sizes: {err}"
10018 );
10019
10020 file.close().unwrap();
10021 std::fs::remove_file(&path).ok();
10022 }
10023
10024 #[test]
10028 fn compact_rejects_chunking_filters_growth_and_a_null_dataspace() {
10029 let path = temp_path("compact_rejects");
10030 let file = H5File::create(&path).unwrap();
10031 assert!(file
10032 .new_dataset::<i32>()
10033 .shape([4usize])
10034 .compact()
10035 .chunk(&[4])
10036 .create("a")
10037 .is_err());
10038 assert!(file
10039 .new_dataset::<i32>()
10040 .shape([4usize])
10041 .compact()
10042 .deflate(4)
10043 .create("b")
10044 .is_err());
10045 assert!(file
10046 .new_dataset::<i32>()
10047 .shape([4usize])
10048 .compact()
10049 .max_shape(&[None])
10050 .create("c")
10051 .is_err());
10052 assert!(file
10053 .new_dataset::<i32>()
10054 .shape([4usize])
10055 .compact()
10056 .max_shape(&[Some(8)])
10057 .create("d")
10058 .is_err());
10059 assert!(file
10060 .new_dataset::<i32>()
10061 .null()
10062 .compact()
10063 .create("e")
10064 .is_err());
10065 file.close().unwrap();
10066 std::fs::remove_file(&path).ok();
10067 }
10068
10069 fn stored_pipeline(
10071 path: &std::path::Path,
10072 name: &str,
10073 ) -> crate::format::messages::filter::FilterPipeline {
10074 let mut reader = crate::io::reader::Hdf5Reader::open(path).unwrap();
10075 reader
10076 .dataset_info(name)
10077 .unwrap()
10078 .filter_pipeline
10079 .clone()
10080 .unwrap_or_else(|| panic!("{name}: no filter pipeline"))
10081 }
10082
10083 #[test]
10087 fn shuffle_alone_is_a_filter_pipeline() {
10088 use crate::format::messages::filter::{FilterPipeline, FILTER_SHUFFLE};
10089 let path = temp_path("shuffle_alone");
10090 let values: Vec<i32> = (0..64).collect();
10091 {
10092 let file = H5File::create(&path).unwrap();
10093 file.new_dataset::<i32>()
10094 .shape([64usize])
10095 .chunk(&[16])
10096 .shuffle()
10097 .create("d")
10098 .unwrap()
10099 .write_raw(&values)
10100 .unwrap();
10101 file.close().unwrap();
10102 }
10103 let pipeline = stored_pipeline(&path, "d");
10104 assert_eq!(pipeline, FilterPipeline::shuffle(4));
10105 assert_eq!(pipeline.filters[0].id, FILTER_SHUFFLE);
10106
10107 let file = H5File::open(&path).unwrap();
10108 assert_eq!(
10109 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
10110 values
10111 );
10112 std::fs::remove_file(&path).ok();
10113 }
10114
10115 #[cfg(feature = "deflate")]
10119 #[test]
10120 fn shuffle_composes_with_deflate() {
10121 let path = temp_path("shuffle_then_deflate");
10122 let values: Vec<i32> = (0..64).collect();
10123 {
10124 let file = H5File::create(&path).unwrap();
10125 for (name, ds) in [
10126 ("split", file.new_dataset::<i32>().shuffle().deflate(6)),
10127 ("combined", file.new_dataset::<i32>().shuffle_deflate(6)),
10128 ] {
10129 ds.shape([64usize])
10130 .chunk(&[16])
10131 .create(name)
10132 .unwrap()
10133 .write_raw(&values)
10134 .unwrap();
10135 }
10136 file.close().unwrap();
10137 }
10138 assert_eq!(
10139 stored_pipeline(&path, "split"),
10140 crate::format::messages::filter::FilterPipeline::shuffle_deflate(4, 6)
10141 );
10142 assert_eq!(
10143 stored_pipeline(&path, "split"),
10144 stored_pipeline(&path, "combined")
10145 );
10146
10147 let file = H5File::open(&path).unwrap();
10148 for name in ["split", "combined"] {
10149 assert_eq!(
10150 file.dataset(name).unwrap().read_raw::<i32>().unwrap(),
10151 values,
10152 "{name}"
10153 );
10154 }
10155 std::fs::remove_file(&path).ok();
10156 }
10157
10158 #[test]
10163 fn shuffle_records_the_stored_element_width() {
10164 use crate::format::messages::datatype::DatatypeMessage;
10165 use crate::format::messages::filter::FilterPipeline;
10166 let path = temp_path("shuffle_override_width");
10167 let bytes: Vec<u8> = (0..16u8).collect();
10168 {
10169 let file = H5File::create(&path).unwrap();
10170 file.new_dataset::<u8>()
10171 .datatype(DatatypeMessage::i32_type())
10172 .shape([4usize])
10173 .chunk(&[4])
10174 .shuffle()
10175 .create("d")
10176 .unwrap()
10177 .write_raw_bytes(&bytes)
10178 .unwrap();
10179 file.close().unwrap();
10180 }
10181 assert_eq!(stored_pipeline(&path, "d"), FilterPipeline::shuffle(4));
10182
10183 let file = H5File::open(&path).unwrap();
10184 assert_eq!(
10185 file.dataset("d").unwrap().read_raw::<i32>().unwrap(),
10186 bytes
10187 .chunks(4)
10188 .map(|c| i32::from_le_bytes(c.try_into().unwrap()))
10189 .collect::<Vec<_>>()
10190 );
10191 std::fs::remove_file(&path).ok();
10192 }
10193
10194 #[test]
10199 fn a_virtual_dataset_refuses_every_write() {
10200 use crate::Selection;
10201 let path = temp_path("vds_write_refused");
10202 let file = H5File::create(&path).unwrap();
10203 let ds = file
10204 .new_dataset::<i32>()
10205 .shape([16usize])
10206 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
10207 .create("vds")
10208 .unwrap();
10209 for err in [
10210 ds.write_raw(&(0..16i32).collect::<Vec<_>>()).unwrap_err(),
10211 ds.write_slice(&[0], &[2], &[1i32, 2]).unwrap_err(),
10212 ] {
10213 let msg = err.to_string();
10214 assert!(msg.contains("virtual dataset"), "{msg}");
10215 }
10216 file.close().unwrap();
10217 std::fs::remove_file(&path).ok();
10218 }
10219
10220 #[test]
10227 fn a_printf_source_name_needs_the_mapping_shape_that_uses_it() {
10228 use crate::Selection;
10229 let path = temp_path("vds_printf");
10230 let file = H5File::create(&path).unwrap();
10231 for (f, d) in [("src_%b.h5", "src"), ("src.h5", "block_%b")] {
10232 let err = match file
10233 .new_dataset::<i32>()
10234 .shape([16usize])
10235 .virtual_mapping(Selection::All, f, d, Selection::All)
10236 .create("vds")
10237 {
10238 Ok(_) => panic!("a bounded mapping has one block, so %b names nothing"),
10239 Err(e) => e.to_string(),
10240 };
10241 assert!(err.contains("printf specifier"), "{err}");
10242 }
10243 let err = match file
10245 .new_dataset::<i32>()
10246 .shape([1usize, 2])
10247 .max_shape(&[None, Some(2)])
10248 .virtual_mapping(unlimited_rows(), "src_%z.h5", "src", Selection::All)
10249 .create("vds_bad")
10250 {
10251 Ok(_) => panic!("%z is not a legal conversion"),
10252 Err(e) => e.to_string(),
10253 };
10254 assert!(err.contains("invalid format specifier"), "{err}");
10255 file.close().unwrap();
10256 std::fs::remove_file(&path).ok();
10257 }
10258
10259 #[test]
10264 fn a_printf_mapping_stitches_one_source_per_block() {
10265 use crate::Selection;
10266 let path = temp_path("vds_printf_blocks");
10267 {
10268 let file = H5File::create(&path).unwrap();
10269 for (b, base) in [(0, 0i32), (1, 100), (3, 300)] {
10270 file.new_dataset::<i32>()
10271 .shape([2usize])
10272 .create(&format!("block{b}"))
10273 .unwrap()
10274 .write_raw(&[base, base + 1])
10275 .unwrap();
10276 }
10277 file.new_dataset::<i32>()
10278 .shape([1usize, 2])
10279 .max_shape(&[None, Some(2)])
10280 .virtual_mapping(unlimited_rows(), ".", "block%b", Selection::All)
10281 .create("vds")
10282 .unwrap();
10283 file.close().unwrap();
10284 }
10285 let file = H5File::open(&path).unwrap();
10286 let ds = file.dataset("vds").unwrap();
10287 assert_eq!(ds.shape(), vec![2, 2]);
10289 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10290 drop(file);
10291 std::fs::remove_file(&path).ok();
10292 }
10293
10294 #[test]
10300 fn a_source_that_cannot_be_opened_reads_as_the_fill_value() {
10301 use crate::{Hyperslab, HyperslabBlock, Selection};
10302 let block = |start: u64, end: u64| Selection::Hyperslab {
10303 rank: 1,
10304 form: Hyperslab::Blocks(vec![HyperslabBlock {
10305 start: vec![start],
10306 end: vec![end],
10307 }]),
10308 };
10309 let path = temp_path("vds_absent_source");
10310 {
10311 let file = H5File::create(&path).unwrap();
10312 file.new_dataset::<i32>()
10313 .shape([4usize])
10314 .create("here")
10315 .unwrap()
10316 .write_raw(&[1i32, 2, 3, 4])
10317 .unwrap();
10318 file.new_dataset::<i32>()
10319 .shape([12usize])
10320 .fill_value(-3i32)
10321 .virtual_mapping(block(0, 3), ".", "here", block(0, 3))
10322 .virtual_mapping(block(4, 7), ".", "absent", block(0, 3))
10324 .virtual_mapping(block(8, 11), "no_such_vds_source.h5", "src", block(0, 3))
10326 .create("vds")
10327 .unwrap();
10328 file.close().unwrap();
10329 }
10330 let file = H5File::open(&path).unwrap();
10331 let ds = file.dataset("vds").unwrap();
10332 assert_eq!(
10333 ds.read_raw::<i32>().unwrap(),
10334 vec![1, 2, 3, 4, -3, -3, -3, -3, -3, -3, -3, -3]
10335 );
10336 assert_eq!(
10339 ds.read_slice::<i32>(&[2], &[6]).unwrap(),
10340 vec![3, 4, -3, -3, -3, -3]
10341 );
10342 drop(file);
10343 std::fs::remove_file(&path).ok();
10344 }
10345
10346 #[test]
10350 fn an_escaped_percent_is_a_literal_in_a_source_name() {
10351 use crate::Selection;
10352 let path = temp_path("vds_escaped_pct");
10353 {
10354 let file = H5File::create(&path).unwrap();
10355 file.new_dataset::<i32>()
10356 .shape([4usize])
10357 .create("od%d")
10358 .unwrap()
10359 .write_raw(&[5i32, 6, 7, 8])
10360 .unwrap();
10361 file.new_dataset::<i32>()
10362 .shape([4usize])
10363 .virtual_mapping(Selection::All, ".", "od%%d", Selection::All)
10364 .create("vds")
10365 .unwrap();
10366 file.close().unwrap();
10367 }
10368 let file = H5File::open(&path).unwrap();
10369 let ds = file.dataset("vds").unwrap();
10370 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![5, 6, 7, 8]);
10371 assert_eq!(ds.virtual_mappings().unwrap()[0].source_dset_name, "od%%d");
10373 drop(file);
10374 std::fs::remove_file(&path).ok();
10375 }
10376
10377 #[test]
10383 fn an_unlimited_mapping_takes_its_extent_from_its_source() {
10384 let path = temp_path("vds_unlimited");
10385 {
10386 let file = H5File::create(&path).unwrap();
10387 file.new_dataset::<i32>()
10388 .shape([5usize, 2])
10389 .chunk(&[5, 2])
10390 .max_shape(&[None, Some(2)])
10391 .create("src")
10392 .unwrap()
10393 .write_raw(&(0..10i32).collect::<Vec<_>>())
10394 .unwrap();
10395 file.new_dataset::<i32>()
10396 .shape([1usize, 2])
10397 .max_shape(&[None, Some(2)])
10398 .virtual_mapping(unlimited_rows(), ".", "src", unlimited_rows())
10399 .create("vds")
10400 .unwrap();
10401 file.close().unwrap();
10402 }
10403 let file = H5File::open(&path).unwrap();
10404 let ds = file.dataset("vds").unwrap();
10405 assert_eq!(ds.shape(), vec![5, 2]);
10406 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..10).collect::<Vec<i32>>());
10407 drop(file);
10408 std::fs::remove_file(&path).ok();
10409 }
10410
10411 fn printf_gap_file(tag: &str) -> std::path::PathBuf {
10415 use crate::Selection;
10416 let path = temp_path(tag);
10417 let file = H5File::create(&path).unwrap();
10418 for (b, base) in [(0, 0i32), (1, 100), (3, 300)] {
10419 file.new_dataset::<i32>()
10420 .shape([2usize])
10421 .create(&format!("block{b}"))
10422 .unwrap()
10423 .write_raw(&[base, base + 1])
10424 .unwrap();
10425 }
10426 file.new_dataset::<i32>()
10427 .shape([1usize, 2])
10428 .max_shape(&[None, Some(2)])
10429 .fill_value(-7i32)
10430 .virtual_mapping(unlimited_rows(), ".", "block%b", Selection::All)
10431 .create("vds")
10432 .unwrap();
10433 file.close().unwrap();
10434 path
10435 }
10436
10437 #[test]
10443 fn a_printf_gap_looks_past_the_missing_block() {
10444 use crate::DatasetAccess;
10445 let path = printf_gap_file("vds_printf_gap");
10446 let file = H5File::open(&path).unwrap();
10447 for (gap, shape, data) in [
10448 (0u64, vec![2usize, 2], vec![0i32, 1, 100, 101]),
10449 (1, vec![4, 2], vec![0, 1, 100, 101, -7, -7, 300, 301]),
10450 (2, vec![4, 2], vec![0, 1, 100, 101, -7, -7, 300, 301]),
10451 ] {
10452 let ds = file
10453 .dataset_with("vds", DatasetAccess::new().virtual_printf_gap(gap))
10454 .unwrap();
10455 assert_eq!(ds.shape(), shape, "gap {gap}");
10456 assert_eq!(ds.read_raw::<i32>().unwrap(), data, "gap {gap}");
10457 }
10458 let ds = file.dataset("vds").unwrap();
10461 assert_eq!(ds.shape(), vec![2, 2]);
10462 assert_eq!(ds.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10463 drop(file);
10464 std::fs::remove_file(&path).ok();
10465 }
10466
10467 #[test]
10476 fn a_relative_source_resolves_next_to_the_virtual_dataset() {
10477 use crate::Selection;
10478 let dir = std::env::temp_dir().join(format!(
10479 "rust_hdf5_vds_beside_{}_{:?}",
10480 std::process::id(),
10481 std::thread::current().id()
10482 ));
10483 std::fs::create_dir_all(&dir).unwrap();
10484 {
10485 let file = H5File::create(dir.join("src.h5")).unwrap();
10486 file.new_dataset::<i32>()
10487 .shape([2usize, 4])
10488 .create("data")
10489 .unwrap()
10490 .write_raw(&(0..8i32).collect::<Vec<_>>())
10491 .unwrap();
10492 file.close().unwrap();
10493 }
10494 {
10495 let file = H5File::create(dir.join("v.h5")).unwrap();
10496 file.new_dataset::<i32>()
10497 .shape([2usize, 4])
10498 .fill_value(-9i32)
10499 .virtual_mapping(Selection::All, "src.h5", "data", Selection::All)
10502 .create("v")
10503 .unwrap();
10504 file.close().unwrap();
10505 }
10506 assert_ne!(std::env::current_dir().unwrap(), dir);
10509 let file = H5File::open(dir.join("v.h5")).unwrap();
10510 let ds = file.dataset("v").unwrap();
10511 assert_eq!(ds.read_raw::<i32>().unwrap(), (0..8i32).collect::<Vec<_>>());
10512 drop(file);
10513 std::fs::remove_dir_all(&dir).ok();
10514 }
10515
10516 #[test]
10528 fn the_first_open_of_a_virtual_dataset_fixes_the_properties_for_later_opens() {
10529 use crate::DatasetAccess;
10530 let path = printf_gap_file("vds_printf_first_open");
10531 let file = H5File::open(&path).unwrap();
10532 let gap = |g: u64| DatasetAccess::new().virtual_printf_gap(g);
10533 {
10534 let a = file.dataset_with("vds", gap(0)).unwrap();
10535 let b = file.dataset_with("vds", gap(1)).unwrap();
10536 assert_eq!(a.shape(), vec![2, 2]);
10537 assert_eq!(b.shape(), vec![2, 2]);
10538 assert_eq!(b.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10539 }
10540 {
10541 let c = file.dataset_with("vds", gap(1)).unwrap();
10542 let d = file.dataset_with("vds", gap(0)).unwrap();
10543 assert_eq!(c.shape(), vec![4, 2]);
10544 assert_eq!(d.shape(), vec![4, 2]);
10545 assert_eq!(
10546 d.read_raw::<i32>().unwrap(),
10547 vec![0, 1, 100, 101, -7, -7, 300, 301]
10548 );
10549 }
10550 let e = file.dataset_with("vds", gap(0)).unwrap();
10551 assert_eq!(e.shape(), vec![2, 2]);
10552 assert_eq!(e.read_raw::<i32>().unwrap(), vec![0, 1, 100, 101]);
10553 drop(e);
10554 drop(file);
10555 std::fs::remove_file(&path).ok();
10556 }
10557
10558 #[test]
10563 fn the_first_missing_view_ignores_the_printf_gap() {
10564 use crate::{DatasetAccess, VirtualView};
10565 let path = printf_gap_file("vds_printf_first_missing");
10566 let file = H5File::open(&path).unwrap();
10567 for gap in [0u64, 2] {
10568 let ds = file
10569 .dataset_with(
10570 "vds",
10571 DatasetAccess::new()
10572 .virtual_view(VirtualView::FirstMissing)
10573 .virtual_printf_gap(gap),
10574 )
10575 .unwrap();
10576 assert_eq!(ds.shape(), vec![2, 2], "gap {gap}");
10577 assert_eq!(
10578 ds.read_raw::<i32>().unwrap(),
10579 vec![0, 1, 100, 101],
10580 "gap {gap}"
10581 );
10582 }
10583 drop(file);
10584 std::fs::remove_file(&path).ok();
10585 }
10586
10587 #[test]
10595 fn the_view_decides_whether_a_trailing_gap_is_inside_the_extent() {
10596 use crate::format::selection::UNLIMITED;
10597 use crate::{DatasetAccess, Hyperslab, RegularHyperslab, Selection, VirtualView};
10598 let strided = || Selection::Hyperslab {
10599 rank: 2,
10600 form: Hyperslab::Regular(RegularHyperslab {
10601 start: vec![0, 0],
10602 stride: vec![3, 1],
10603 count: vec![UNLIMITED, 1],
10604 block: vec![2, 2],
10605 }),
10606 };
10607 let path = temp_path("vds_view_trail");
10608 {
10609 let file = H5File::create(&path).unwrap();
10610 file.new_dataset::<i32>()
10611 .shape([3usize, 2])
10612 .max_shape(&[None, Some(2)])
10613 .chunk(&[1, 2])
10614 .create("src")
10615 .unwrap()
10616 .write_raw(&(0..6i32).collect::<Vec<_>>())
10617 .unwrap();
10618 file.new_dataset::<i32>()
10619 .shape([1usize, 2])
10620 .max_shape(&[None, Some(2)])
10621 .fill_value(-9i32)
10622 .virtual_mapping(strided(), ".", "src", strided())
10623 .create("vds")
10624 .unwrap();
10625 file.close().unwrap();
10626 }
10627 let file = H5File::open(&path).unwrap();
10628 {
10629 let last = file.dataset("vds").unwrap();
10630 assert_eq!(last.shape(), vec![2, 2]);
10631 assert_eq!(last.read_raw::<i32>().unwrap(), vec![0, 1, 2, 3]);
10632 }
10633 let first = file
10635 .dataset_with(
10636 "vds",
10637 DatasetAccess::new().virtual_view(VirtualView::FirstMissing),
10638 )
10639 .unwrap();
10640 assert_eq!(first.shape(), vec![3, 2]);
10641 assert_eq!(first.read_raw::<i32>().unwrap(), vec![0, 1, 2, 3, -9, -9]);
10642 drop(file);
10643 std::fs::remove_file(&path).ok();
10644 }
10645
10646 #[test]
10651 fn the_access_property_list_reads_back_and_refuses_hsize_undef() {
10652 use crate::{DatasetAccess, VirtualView};
10653 let plist = DatasetAccess::new();
10654 assert_eq!(plist.view(), VirtualView::LastAvailable);
10655 assert_eq!(plist.printf_gap(), 0);
10656 let set = plist
10657 .virtual_view(VirtualView::FirstMissing)
10658 .virtual_printf_gap(4);
10659 assert_eq!(set.view(), VirtualView::FirstMissing);
10660 assert_eq!(set.printf_gap(), 4);
10663
10664 let path = printf_gap_file("vds_printf_gap_undef");
10665 let file = H5File::open(&path).unwrap();
10666 let err = match file.dataset_with("vds", DatasetAccess::new().virtual_printf_gap(u64::MAX))
10667 {
10668 Ok(_) => panic!("HSIZE_UNDEF is not a valid printf gap size"),
10669 Err(e) => e.to_string(),
10670 };
10671 assert!(err.contains("HSIZE_UNDEF"), "{err}");
10672 drop(file);
10673 std::fs::remove_file(&path).ok();
10674 }
10675
10676 fn unlimited_rows() -> crate::Selection {
10679 use crate::format::selection::UNLIMITED;
10680 use crate::{Hyperslab, RegularHyperslab, Selection};
10681 Selection::Hyperslab {
10682 rank: 2,
10683 form: Hyperslab::Regular(RegularHyperslab {
10684 start: vec![0, 0],
10685 stride: vec![1, 1],
10686 count: vec![UNLIMITED, 1],
10687 block: vec![1, 2],
10688 }),
10689 }
10690 }
10691
10692 #[test]
10697 fn an_unlimited_virtual_selection_over_a_limited_source_is_refused() {
10698 use crate::Selection;
10699 let path = temp_path("vds_unlim_limited_src");
10700 let file = H5File::create(&path).unwrap();
10701 let err = match file
10702 .new_dataset::<i32>()
10703 .shape([1usize, 2])
10704 .max_shape(&[None, Some(2)])
10705 .virtual_mapping(unlimited_rows(), "src.h5", "src", Selection::All)
10706 .create("vds")
10707 {
10708 Ok(_) => panic!("no printf substitution names the mapping's later blocks"),
10709 Err(e) => e.to_string(),
10710 };
10711 assert!(err.contains("printf"), "{err}");
10712 file.close().unwrap();
10713 std::fs::remove_file(&path).ok();
10714 }
10715
10716 #[test]
10719 fn a_virtual_dataset_cannot_also_be_chunked_or_external() {
10720 use crate::Selection;
10721 let path = temp_path("vds_exclusive");
10722 let file = H5File::create(&path).unwrap();
10723 let builder = || {
10724 file.new_dataset::<i32>().shape([16usize]).virtual_mapping(
10725 Selection::All,
10726 "src.h5",
10727 "src",
10728 Selection::All,
10729 )
10730 };
10731 for (which, res) in [
10732 ("chunked", builder().chunk(&[4]).create("a")),
10733 ("compact", builder().compact().create("b")),
10734 (
10735 "external",
10736 builder().external(&[("x.raw", 0, 64)]).create("c"),
10737 ),
10738 ("references", builder().object_references().create("d")),
10739 ] {
10740 match res {
10741 Ok(_) => panic!("a virtual dataset cannot also be {which}"),
10742 Err(e) => assert!(e.to_string().contains("virtual dataset"), "{which}: {e}"),
10743 }
10744 }
10745 file.close().unwrap();
10746 std::fs::remove_file(&path).ok();
10747 }
10748
10749 #[test]
10754 fn deleting_a_virtual_dataset_frees_its_mapping_list() {
10755 use crate::Selection;
10756 let path = temp_path("vds_delete");
10757 let baseline = {
10758 let file = H5File::create(&path).unwrap();
10759 file.new_dataset::<i32>()
10760 .shape([16usize])
10761 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
10762 .create("vds")
10763 .unwrap();
10764 file.delete_dataset("vds").unwrap();
10765 file.close().unwrap();
10766 std::fs::metadata(&path).unwrap().len()
10767 };
10768 std::fs::remove_file(&path).ok();
10769
10770 let file = H5File::create(&path).unwrap();
10774 for i in 0..10 {
10775 let name = format!("vds{i}");
10776 file.new_dataset::<i32>()
10777 .shape([16usize])
10778 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
10779 .create(&name)
10780 .unwrap();
10781 file.delete_dataset(&name).unwrap();
10782 }
10783 file.close().unwrap();
10784 assert_eq!(std::fs::metadata(&path).unwrap().len(), baseline);
10785 std::fs::remove_file(&path).ok();
10786 }
10787
10788 #[test]
10791 fn storage_layout_reports_each_class() {
10792 use crate::StorageLayout;
10793 let path = temp_path("storage_layout");
10794 {
10795 let file = H5File::create(&path).unwrap();
10796 file.new_dataset::<i32>()
10797 .shape([4usize])
10798 .create("contig")
10799 .unwrap();
10800 file.new_dataset::<i32>()
10801 .shape([4usize])
10802 .compact()
10803 .create("compact")
10804 .unwrap();
10805 file.new_dataset::<i32>()
10806 .shape([8usize])
10807 .chunk(&[4])
10808 .create("chunked")
10809 .unwrap();
10810 file.close().unwrap();
10811 }
10812 let file = H5File::open(&path).unwrap();
10813 assert_eq!(
10814 file.dataset("contig").unwrap().storage_layout().unwrap(),
10815 StorageLayout::Contiguous
10816 );
10817 assert_eq!(
10818 file.dataset("compact").unwrap().storage_layout().unwrap(),
10819 StorageLayout::Compact
10820 );
10821 assert_eq!(
10822 file.dataset("chunked").unwrap().storage_layout().unwrap(),
10823 StorageLayout::Chunked
10824 );
10825 }
10826
10827 #[test]
10829 fn storage_layout_errors_in_write_mode() {
10830 let path = temp_path("storage_layout_write_mode");
10831 let file = H5File::create(&path).unwrap();
10832 let ds = file
10833 .new_dataset::<i32>()
10834 .shape([4usize])
10835 .create("data")
10836 .unwrap();
10837 assert!(ds.storage_layout().is_err());
10838 file.close().unwrap();
10839 }
10840
10841 #[test]
10846 fn chunk_index_reports_the_stored_kind() {
10847 use crate::ChunkIndex;
10848 let path = temp_path("chunk_index");
10849 {
10850 let file = H5File::create(&path).unwrap();
10851 file.new_dataset::<i32>()
10852 .shape([4usize])
10853 .create("contig")
10854 .unwrap();
10855 file.new_dataset::<i32>()
10856 .shape([16usize])
10857 .chunk(&[4])
10858 .max_shape(&[None])
10859 .create("earray")
10860 .unwrap();
10861 file.set_libver_latest(false).unwrap();
10862 file.new_dataset::<i32>()
10863 .shape([8usize])
10864 .chunk(&[4])
10865 .max_shape(&[None])
10866 .create("btree1")
10867 .unwrap();
10868 file.close().unwrap();
10869 }
10870 let file = H5File::open(&path).unwrap();
10871 assert_eq!(file.dataset("contig").unwrap().chunk_index().unwrap(), None);
10872 assert_eq!(
10873 file.dataset("earray").unwrap().chunk_index().unwrap(),
10874 Some(ChunkIndex::ExtensibleArray)
10875 );
10876 assert_eq!(
10877 file.dataset("btree1").unwrap().chunk_index().unwrap(),
10878 Some(ChunkIndex::BtreeV1)
10879 );
10880 }
10881
10882 #[test]
10886 fn filters_reports_the_stored_pipeline() {
10887 use crate::format::messages::filter::{FILTER_DEFLATE, FILTER_SHUFFLE, FLAG_OPTIONAL};
10888 let path = temp_path("filters");
10889 {
10890 let file = H5File::create(&path).unwrap();
10891 file.new_dataset::<i32>()
10892 .shape([16usize])
10893 .create("unfiltered")
10894 .unwrap();
10895 file.new_dataset::<i32>()
10896 .shape([16usize])
10897 .chunk(&[4])
10898 .shuffle()
10899 .deflate(6)
10900 .create("filtered")
10901 .unwrap();
10902 file.close().unwrap();
10903 }
10904 let file = H5File::open(&path).unwrap();
10905 assert_eq!(
10906 file.dataset("unfiltered").unwrap().filters().unwrap(),
10907 Vec::new()
10908 );
10909 let filters = file.dataset("filtered").unwrap().filters().unwrap();
10910 assert_eq!(filters.len(), 2);
10911 assert_eq!(filters[0].id, FILTER_SHUFFLE);
10912 assert_eq!(filters[0].flags, FLAG_OPTIONAL);
10913 assert_eq!(filters[1].id, FILTER_DEFLATE);
10914 assert_eq!(filters[1].cd_values, vec![6]);
10915 }
10916
10917 #[test]
10923 fn fill_value_reports_the_stored_value() {
10924 use crate::FillValue;
10925 let path = temp_path("fill_value");
10926 {
10927 let file = H5File::create(&path).unwrap();
10928 file.new_dataset::<i32>()
10929 .shape([4usize])
10930 .create("unset")
10931 .unwrap();
10932 file.new_dataset::<i32>()
10933 .shape([4usize])
10934 .fill_value(-7i32)
10935 .create("set")
10936 .unwrap();
10937 file.close().unwrap();
10938 }
10939 let file = H5File::open(&path).unwrap();
10940 assert_eq!(
10941 file.dataset("unset").unwrap().fill_value().unwrap(),
10942 FillValue::Default
10943 );
10944 assert_eq!(
10945 file.dataset("set").unwrap().fill_value().unwrap(),
10946 FillValue::UserDefined((-7i32).to_le_bytes().to_vec())
10947 );
10948 }
10949
10950 #[test]
10955 fn the_unlimited_external_slot_keeps_its_three_rules() {
10956 use crate::format::messages::external_file_list::UNLIMITED;
10957 let path = temp_path("efl_unlim_rules");
10958 let file = H5File::create(&path).unwrap();
10959
10960 let err = match file
10963 .new_dataset::<i32>()
10964 .shape([8usize])
10965 .external(&[("a.raw", 0, UNLIMITED), ("b.raw", 0, 32)])
10966 .create("mid")
10967 {
10968 Ok(_) => panic!("an unlimited slot absorbs everything behind it"),
10969 Err(e) => e.to_string(),
10970 };
10971 assert!(err.contains("only be the last"), "{err}");
10972
10973 let err = match file
10975 .new_dataset::<i32>()
10976 .shape([8usize])
10977 .max_shape(&[None])
10978 .external(&[("a.raw", 0, 32)])
10979 .create("finite")
10980 {
10981 Ok(_) => panic!("no finite reservation covers an unlimited extent"),
10982 Err(e) => e.to_string(),
10983 };
10984 assert!(err.contains("unlimited dataspace"), "{err}");
10985
10986 let err = match file
10988 .new_dataset::<i32>()
10989 .shape([2usize, 4])
10990 .max_shape(&[Some(2), None])
10991 .external(&[("a.raw", 0, UNLIMITED)])
10992 .create("dim1")
10993 {
10994 Ok(_) => panic!("only the slowest-varying dimension may be extendible"),
10995 Err(e) => e.to_string(),
10996 };
10997 assert!(err.contains("only the first dimension"), "{err}");
10998
10999 file.new_dataset::<i32>()
11002 .shape([8usize])
11003 .max_shape(&[None])
11004 .external(&[("a.raw", 0, 16), ("b.raw", 0, UNLIMITED)])
11005 .create("ok")
11006 .unwrap()
11007 .write_raw(&(0..8i32).collect::<Vec<_>>())
11008 .unwrap();
11009 file.close().unwrap();
11010 std::fs::remove_file(&path).ok();
11011 for raw in ["a.raw", "b.raw"] {
11012 std::fs::remove_file(raw).ok();
11013 }
11014 }
11015
11016 #[test]
11021 fn an_unlimited_external_slot_reads_zero_past_the_end_of_its_file() {
11022 use crate::format::messages::external_file_list::UNLIMITED;
11023 let dir = std::env::temp_dir().join(format!("rh5_efl_short_{}", std::process::id()));
11024 std::fs::create_dir_all(&dir).unwrap();
11025 let path = dir.join("f.h5");
11026 let raw = dir.join("short.raw");
11027 std::fs::write(&raw, [0u8; 16]).unwrap();
11029 {
11030 let file = H5File::create(&path).unwrap();
11031 file.new_dataset::<i32>()
11032 .shape([8usize])
11033 .max_shape(&[None])
11034 .external(&[(raw.to_str().unwrap(), 0, UNLIMITED)])
11035 .create("data")
11036 .unwrap();
11037 file.close().unwrap();
11038 }
11039 let file = H5File::open(&path).unwrap();
11040 assert_eq!(
11041 file.dataset("data").unwrap().read_raw::<i32>().unwrap(),
11042 vec![0i32; 8]
11043 );
11044 drop(file);
11045 std::fs::remove_dir_all(&dir).ok();
11046 }
11047
11048 #[test]
11052 fn external_files_reports_the_stored_segments() {
11053 let path = temp_path("external_files");
11054 {
11055 let file = H5File::create(&path).unwrap();
11056 file.new_dataset::<i32>()
11057 .shape([4usize])
11058 .create("contig")
11059 .unwrap();
11060 file.new_dataset::<i32>()
11061 .shape([16usize])
11062 .external(&[("a.raw", 0, 32), ("b.raw", 8, 32)])
11063 .create("external")
11064 .unwrap();
11065 file.close().unwrap();
11066 }
11067 let file = H5File::open(&path).unwrap();
11068 assert_eq!(
11069 file.dataset("contig").unwrap().external_files().unwrap(),
11070 Vec::new()
11071 );
11072 let segments = file.dataset("external").unwrap().external_files().unwrap();
11073 assert_eq!(segments.len(), 2);
11074 assert_eq!(segments[0].name, "a.raw");
11075 assert_eq!(segments[0].offset, 0);
11076 assert_eq!(segments[0].size, 32);
11077 assert_eq!(segments[1].name, "b.raw");
11078 assert_eq!(segments[1].offset, 8);
11079 assert_eq!(segments[1].size, 32);
11080 }
11081
11082 #[test]
11087 fn max_shape_reports_unlimited_and_fixed_axes() {
11088 let path = temp_path("max_shape");
11089 {
11090 let file = H5File::create(&path).unwrap();
11091 file.new_dataset::<i32>()
11092 .shape([4usize, 8])
11093 .create("fixed")
11094 .unwrap();
11095 file.new_dataset::<i32>()
11096 .shape([4usize, 8])
11097 .chunk(&[2, 8])
11098 .max_shape(&[None, Some(8)])
11099 .create("unlimited")
11100 .unwrap();
11101 file.close().unwrap();
11102 }
11103 let file = H5File::open(&path).unwrap();
11104 assert_eq!(
11105 file.dataset("fixed").unwrap().max_shape().unwrap(),
11106 vec![Some(4), Some(8)]
11107 );
11108 assert_eq!(
11109 file.dataset("unlimited").unwrap().max_shape().unwrap(),
11110 vec![None, Some(8)]
11111 );
11112 }
11113
11114 #[test]
11118 fn virtual_mappings_reports_the_stored_mappings() {
11119 use crate::Selection;
11120 let path = temp_path("virtual_mappings");
11121 {
11122 let file = H5File::create(&path).unwrap();
11123 file.new_dataset::<i32>()
11124 .shape([4usize])
11125 .create("plain")
11126 .unwrap();
11127 file.new_dataset::<i32>()
11128 .shape([16usize])
11129 .virtual_mapping(Selection::All, "src.h5", "src", Selection::All)
11130 .create("vds")
11131 .unwrap();
11132 file.close().unwrap();
11133 }
11134 let file = H5File::open(&path).unwrap();
11135 assert_eq!(
11136 file.dataset("plain").unwrap().virtual_mappings().unwrap(),
11137 Vec::new()
11138 );
11139 let mappings = file.dataset("vds").unwrap().virtual_mappings().unwrap();
11140 assert_eq!(mappings.len(), 1);
11141 assert_eq!(mappings[0].source_file_name, "src.h5");
11142 assert_eq!(mappings[0].source_dset_name, "src");
11143 assert_eq!(mappings[0].source_selection, Selection::All);
11144 assert_eq!(mappings[0].virtual_selection, Selection::All);
11145 }
11146}