Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ jobs:
cargo r --example continuously_rw_1d --features ${{ env.FEATURES }}
cargo r --example committed_datatype --features ${{ env.FEATURES }}
cargo r --example swmr --features ${{ env.FEATURES }}
cargo r --example link_order --features ${{ env.FEATURES }}
cargo r --example creation_order --features ${{ env.FEATURES }}
if: matrix.rust != 'stable-gnu'
env:
FEATURES: hdf5-sys/static,hdf5-sys/zlib,lzf,blosc-all
Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
- Added more variants to `LibraryVersion`. If you specified `Latest` before you may start generating files which are no longer compatible with earlier versions of `hdf5`
- Exported `IterationOrder` and `IndexType`, the arguments of `Group::iter_visit`
- Changed `Group::iter_visit` and `Group::iter_visit_default` to take an `FnMut(&str, LinkInfo) -> Result<()>` closure instead of an accumulator and a `bool` closure, and `LinkInfo::is_utf8` to `LinkInfo::char_encoding` (breaking change). An error returned by the closure is propagated, and `Group::groups`, `Group::datasets`, `Group::committed_datatypes` and `Group::member_names` now fail instead of returning a truncated list when a link cannot be resolved
- Added `Group::member_names_by` and `Group::links` to list the links along an index type in an order, `Group::find_link` to stop a link iteration with a value, and `Group::iter_visit_from` with `LinkCursor` to skip links and resume a stopped iteration
- Added `Group::member_names_by` and `Group::links` to list the links along an index type in an order, `Group::find_link` to stop a link iteration with a value, and `Group::iter_visit_from` with `IterationCursor` to skip links and resume a stopped iteration
- Added `FileCreateBuilder::link_creation_order`, `GroupCreateBuilder::link_creation_order`, the matching getters and `LinkCreationOrder`, so a group can track and index link creation order
- Changed `AttrCreationOrder` from bitflags to an enum with `Untracked`, `Tracked` and `Indexed`, matching `LinkCreationOrder` (breaking change)
- Added `GroupCreateBuilder::attr_creation_order` and `GroupCreateBuilder::attr_phase_change` with the matching `GroupCreate` getters
- Added `Location::iter_attrs`, `Location::find_attr` and `Location::iter_attrs_from` with `AttrInfo`, and `Location::attr_names_by` and `Location::attrs`, to iterate attributes by name or creation order in either direction, sharing `IndexType`, `IterationOrder` and `IterationCursor` with link iteration
- Added `Location::attr_by_index` and `Location::attr_info`
- Added `Group::info` with `GroupInfo` and `GroupStorageType`, exposing `H5Gget_info`
## hdf5-derive unreleased
## hdf5-types unreleased
## hdf5-sys unreleased
Expand Down
46 changes: 36 additions & 10 deletions hdf5/examples/link_order.rs → hdf5/examples/creation_order.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
//! List the links of a group in the order they were created
//! List the links and attributes of a group in the order they were created
//!
//! HDF5 keeps the links of a group in a name index. A group can also record the order in which its
//! links were created, which is what h5py does with `track_order=True` and what netCDF-4 does for
//! every group, so a reader sees the variables in the order the writer added them. Tracking has to
//! be requested when the group is created and cannot be turned on later.
//! HDF5 keeps the links of a group and the attributes of an object in a name index. Both can also
//! record the order in which they were created, which is what h5py does with `track_order=True`
//! and what netCDF-4 does for every group, so a reader sees variables and attributes in the order
//! the writer added them. Tracking has to be requested when the object is created and cannot be
//! turned on later.

use hdf5::plist::group_create::LinkCreationOrder;
use hdf5::{File, IndexType, IterationOrder, LinkCursor, LinkType, MajorErrorCode, Result};
use hdf5::plist::group_create::{AttrCreationOrder, LinkCreationOrder};
use hdf5::{File, IndexType, IterationCursor, IterationOrder, LinkType, MajorErrorCode, Result};
use hdf5_metno as hdf5;

const FILE_NAME: &str = "link_order.h5";
const FILE_NAME: &str = "creation_order.h5";
const VARIABLES: [&str; 5] = ["time", "latitude", "longitude", "temperature", "pressure"];
const ATTRIBUTES: [&str; 3] = ["title", "history", "Conventions"];
const PAGE_SIZE: usize = 3;

fn write() -> Result<()> {
let file = File::create(FILE_NAME)?;

let tracked = file
.create_group_builder()
.with_gcpl(|gcpl| gcpl.link_creation_order(LinkCreationOrder::Tracked))
.with_gcpl(|gcpl| {
gcpl.link_creation_order(LinkCreationOrder::Tracked)
.attr_creation_order(AttrCreationOrder::Tracked)
})
.create("tracked")?;
for name in VARIABLES {
tracked.new_dataset::<f64>().shape(24).create(name)?;
}
tracked.link_soft("temperature", "temp")?;
for name in ATTRIBUTES {
tracked.new_attr::<f64>().create(name)?;
}

// A group created with the defaults has only the name index.
let untracked = file.create_group("untracked")?;
Expand Down Expand Up @@ -59,7 +67,7 @@ fn read() -> Result<()> {
assert_eq!(first_soft, Some("temp".to_owned()));

// A cursor resumes a stopped iteration, here to read the links in pages.
let mut cursor = LinkCursor::start(IndexType::CreationOrder, IterationOrder::Increasing);
let mut cursor = IterationCursor::start(IndexType::CreationOrder, IterationOrder::Increasing);
let mut page = vec![];
while let Some(((), next)) = tracked.iter_visit_from(cursor, |name, _| {
page.push(name.to_owned());
Expand All @@ -70,6 +78,24 @@ fn read() -> Result<()> {
cursor = next;
}

// Attributes follow the same index types. The tracked group lists them in creation order.
println!("attributes by name: {:?}", tracked.attr_names()?);
let mut attrs_by_creation = vec![];
tracked.iter_attrs(IndexType::CreationOrder, IterationOrder::Increasing, |name, info| {
let order = info.creation_order.expect("the group tracks attribute creation order");
println!("created {order}: @{name}");
attrs_by_creation.push(name.to_owned());
Ok(())
})?;
assert_eq!(attrs_by_creation, ATTRIBUTES);

// An attribute is opened by its position in either index, and its info looked up by name.
let first = tracked.attr_by_index(IndexType::CreationOrder, IterationOrder::Increasing, 0)?;
assert_eq!(first.name(), "title");
let history = tracked.attr_info("history")?;
println!("history: {history:?}");
assert_eq!(history.creation_order, Some(1));

// Creation order is not available in a group that never tracked it.
let err = file
.group("untracked")?
Expand Down
6 changes: 4 additions & 2 deletions hdf5/src/hl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod extents;
pub mod file;
pub mod filters;
pub mod group;
pub mod iteration;
pub mod location;
pub mod object;
pub mod plist;
Expand All @@ -17,7 +18,7 @@ pub mod selection;

pub use self::{
attribute::{
Attribute, AttributeBuilder, AttributeBuilderData, AttributeBuilderEmpty,
AttrInfo, Attribute, AttributeBuilder, AttributeBuilderData, AttributeBuilderEmpty,
AttributeBuilderEmptyShape,
},
committed_datatype::CommittedDatatype,
Expand All @@ -29,7 +30,8 @@ pub use self::{
dataspace::Dataspace,
datatype::{Conversion, Datatype},
file::{File, FileBuilder, OpenMode},
group::{Group, GroupBuilder, IndexType, IterationOrder, LinkCursor, LinkInfo, LinkType},
group::{Group, GroupBuilder, GroupInfo, GroupStorageType, LinkInfo, LinkType},
iteration::{IndexType, IterationCursor, IterationOrder},
location::{Location, LocationInfo, LocationToken, LocationType},
object::Object,
plist::PropertyList,
Expand Down
58 changes: 20 additions & 38 deletions hdf5/src/hl/attribute.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
use std::fmt::{self, Debug};
use std::ops::Deref;
use std::ptr::addr_of_mut;

use hdf5_sys::h5a::{H5Aget_create_plist, H5Aget_name};
use hdf5_sys::{
h5::{H5_index_t, H5_iter_order_t},
h5a::{H5A_info_t, H5A_operator2_t, H5Acreate2, H5Adelete, H5Aiterate2},
};
use hdf5_sys::h5a::{H5A_info_t, H5Acreate2, H5Adelete, H5Aget_create_plist, H5Aget_name};
use hdf5_types::TypeDescriptor;
use ndarray::ArrayView;

use crate::hl::plist::attribute_create::{AttributeCreate, AttributeCreateBuilder, CharEncoding};
use crate::internal_prelude::*;

/// Information about an attribute of an object.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AttrInfo {
/// Position in the creation order of the object's attributes, if known.
pub creation_order: Option<u32>,
/// Encoding of the attribute name. HDF5 values other than UTF-8 are reported as ASCII.
pub char_encoding: CharEncoding,
/// Size of the attribute data in bytes.
pub data_size: u64,
}

impl From<&H5A_info_t> for AttrInfo {
fn from(info: &H5A_info_t) -> Self {
let creation_order = if info.corder_valid == 1 { Some(info.corder) } else { None };
let char_encoding = CharEncoding::try_from(info.cset).unwrap_or(CharEncoding::Ascii);
Self { creation_order, char_encoding, data_size: info.data_size }
}
}

/// Represents the HDF5 attribute object.
#[repr(transparent)]
#[derive(Clone)]
Expand Down Expand Up @@ -65,38 +79,6 @@ impl Attribute {
// attribute is attached to, not the attribute's own name.
h5lock!(get_h5_str(|m, s| H5Aget_name(self.id(), s, m)).unwrap_or_else(|_| String::new()))
}

/// Returns names of all the members in the group, non-recursively.
pub fn attr_names(obj: &Location) -> Result<Vec<String>> {
unsafe extern "C" fn attributes_callback(
_id: hid_t, attr_name: *const c_char, _info: *const H5A_info_t, op_data: *mut c_void,
) -> herr_t {
std::panic::catch_unwind(|| {
let other_data: &mut Vec<String> =
unsafe { &mut *(op_data.cast::<std::vec::Vec<std::string::String>>()) };
// SAFETY: caller guarantees attr_name points to valid UTF-8 C string
other_data.push(unsafe { string_from_cstr(attr_name) });
0 // Continue iteration
})
.unwrap_or(-1)
}

let callback_fn: H5A_operator2_t = Some(attributes_callback);
let iteration_position: *mut hsize_t = &mut { 0_u64 };
let mut result: Vec<String> = Vec::new();
let other_data: *mut c_void = addr_of_mut!(result).cast();

h5call!(H5Aiterate2(
obj.handle().id(),
H5_index_t::H5_INDEX_NAME,
H5_iter_order_t::H5_ITER_INC,
iteration_position,
callback_fn,
other_data
))?;

Ok(result)
}
}

#[derive(Clone)]
Expand Down
Loading
Loading