fix(format): read datasets and attributes that use committed datatypes
A dataset created from a committed (named) datatype stores only a shared-
message reference to it. The facade parsed those reference bytes as the
datatype itself, producing `Time { size: 0 }` and unreadable data, and an
attribute using a committed datatype was silently dropped.
- shared_message::parse_shared_ref had the encoding wrong: it skipped six
reserved bytes for version 2 (only version 1 has them) and had the version 3
types inverted (1 is the SOHM heap, 2 is "committed, in another object
header"). Verified against h5py 3.16 / HDF5 2.0, which writes
`02 02 <address>` under both default and latest libver bounds. Resolution
now dispatches on which field the reference carries.
- New shared_message::message_data resolves a header message through the
indirection; the reader, lazy and mmap facades use it for datatype,
dataspace and filter-pipeline messages.
- AttributeMessage honours the v2/v3 flags (bit 0 datatype shared, bit 1
dataspace shared) via the new parse_in_file, used everywhere file data is
available. Parsing a shared attribute without file access is now
FormatError::UnresolvedSharedMessage instead of a garbage datatype.
- h5py interop test covering both libver settings.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
0eca8574f5
commit
81e8294048
@@ -565,3 +565,52 @@ print("OK")
|
||||
);
|
||||
assert_eq!(run_python_output(&script), "OK");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// h5py uses committed (named) datatypes -> clawhdf5 reads
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A dataset or attribute created from a committed datatype stores only a
|
||||
/// *shared message* reference to it. These used to be parsed as the datatype
|
||||
/// itself (yielding `Time { size: 0 }` and unreadable data) and the attribute
|
||||
/// was silently dropped.
|
||||
#[test]
|
||||
fn h5py_committed_datatypes_clawhdf5_reads() {
|
||||
skip_if_no_python!();
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
for (tag, kwargs) in [("default", ""), ("latest", ", libver='latest'")] {
|
||||
let path = dir.path().join(format!("committed_{tag}.h5"));
|
||||
let path_str = path.display().to_string();
|
||||
let script = format!(
|
||||
r#"
|
||||
import h5py, numpy as np
|
||||
with h5py.File("{path_str}", "w"{kwargs}) as f:
|
||||
f["f8type"] = np.dtype("<f8")
|
||||
f["cmpd"] = np.dtype([("a", "<i4"), ("b", "<f8")])
|
||||
f.create_dataset("d", data=np.arange(6, dtype="<f8"), dtype=f["f8type"])
|
||||
f.create_dataset("c", data=np.array([(1, 2.5), (3, 4.5)], dtype=f["cmpd"].dtype), dtype=f["cmpd"])
|
||||
f["d"].attrs.create("att", 7.0, dtype=f["f8type"])
|
||||
"#
|
||||
);
|
||||
run_python(&script);
|
||||
|
||||
let file = File::open(&path).unwrap();
|
||||
let d = file.dataset("d").unwrap();
|
||||
assert_eq!(d.dtype().unwrap(), DType::F64, "{tag}");
|
||||
assert_eq!(
|
||||
d.read_f64().unwrap(),
|
||||
vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
|
||||
"{tag}"
|
||||
);
|
||||
assert!(
|
||||
matches!(d.attrs().unwrap().get("att"), Some(AttrValue::F64(v)) if *v == 7.0),
|
||||
"{tag}: attribute with a committed datatype"
|
||||
);
|
||||
let c = file.dataset("c").unwrap();
|
||||
assert_eq!(
|
||||
c.dtype().unwrap(),
|
||||
DType::Compound(vec![("a".into(), DType::I32), ("b".into(), DType::F64)]),
|
||||
"{tag}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user