writer: order dense name indexes by hash, then name

libhdf5 compares the name when two hashes are equal; the writer broke
ties by insertion order, and libhdf5 could not find one of two names
whose lookup3 hashes collide (k69209 / k155448). Test fails before the
fix with h5py's KeyError.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-26 10:03:22 -05:00
co-authored by Claude Opus 5.5
parent de2a53f613
commit 7acfb79584
3 changed files with 77 additions and 16 deletions
@@ -627,6 +627,45 @@ fn non_ascii_names_are_utf8() {
assert_eq!(f.dataset("größe/wert").unwrap().read_i32().unwrap(), [1]);
}
#[test]
fn names_whose_hashes_collide_are_found_by_name() {
skip_if_no_python!();
// "k69209" and "k155448" have the same lookup3 hash (0x3a0b13e6). The
// dense name indexes (links: type 5, attributes: type 8) are ordered by
// hash and then by name, and libhdf5's lookup relies on it. The writer
// broke ties by insertion order, so with "k69209" added first libhdf5
// could not open "k155448" by name.
let dir = tempfile::tempdir().unwrap();
let mut b = FileBuilder::new();
let mut g = b.create_group("g");
for (i, n) in ["k69209", "k155448"]
.into_iter()
.chain((0..10).map(|_| ""))
.enumerate()
{
let name = if n.is_empty() { format!("d{i}") } else { n.into() };
g.create_dataset(&name).with_i32_data(&[i as i32]);
}
b.add_group(g.finish());
let x = b.create_dataset("x");
x.with_i32_data(&[0]);
x.set_attr("k69209", AttrValue::I64(1));
x.set_attr("k155448", AttrValue::I64(2));
for i in 0..10 {
x.set_attr(&format!("a{i}"), AttrValue::I64(10 + i));
}
let path = write(&dir, "collide.h5", b);
let out = h5py(
&path,
"with h5py.File(path, 'r') as f:\n\
\x20 g, a = f['g'], f['x'].attrs\n\
\x20 print(json.dumps([int(g['k69209'][0]), int(g['k155448'][0]), 'k155448' in g,\n\
\x20 int(a['k69209']), int(a['k155448']), 'k155448' in a]))",
);
assert_eq!(out, "[0, 1, true, 1, 2, true]");
h5dump_ok(&path);
}
#[test]
fn a_group_attribute_set_again_takes_the_new_value() {
skip_if_no_python!();