Merge pull request 'docs(clawhdf5): document DType variants, fix unresolved doc links' (#17) from sdlc-docs/clawhdf5-types-20260514-165210 into main
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
//! Property list types for configuring dataset, file access, and file creation.
|
||||
//!
|
||||
//! Property lists group related configuration into reusable bundles.
|
||||
//! They provide an alternative to individual setter methods on builders.
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{vec, vec::Vec};
|
||||
|
||||
use crate::type_builders::FillTime;
|
||||
|
||||
/// Dataset creation properties.
|
||||
///
|
||||
/// Controls storage layout, compression, fill values, and alignment
|
||||
/// for a new dataset.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DatasetCreateProps {
|
||||
/// Chunk dimensions (enables chunked storage).
|
||||
pub chunk_dims: Option<Vec<u64>>,
|
||||
/// Deflate compression level (0-9).
|
||||
pub deflate_level: Option<u32>,
|
||||
/// Shuffle filter before compression.
|
||||
pub shuffle: bool,
|
||||
/// Fletcher32 checksum.
|
||||
pub fletcher32: bool,
|
||||
/// LZ4 compression.
|
||||
pub lz4: bool,
|
||||
/// Zstandard compression level (1-22).
|
||||
pub zstd_level: Option<u32>,
|
||||
/// Fill value write time.
|
||||
pub fill_time: FillTime,
|
||||
/// Use compact (inline) storage.
|
||||
pub compact: bool,
|
||||
/// Per-dataset alignment in bytes.
|
||||
pub alignment: usize,
|
||||
}
|
||||
|
||||
impl Default for DatasetCreateProps {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
chunk_dims: None,
|
||||
deflate_level: None,
|
||||
shuffle: false,
|
||||
fletcher32: false,
|
||||
lz4: false,
|
||||
zstd_level: None,
|
||||
fill_time: FillTime::Alloc,
|
||||
compact: false,
|
||||
alignment: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DatasetCreateProps {
|
||||
/// Create default dataset creation properties.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Set chunk dimensions.
|
||||
pub fn chunk(mut self, dims: &[u64]) -> Self {
|
||||
self.chunk_dims = Some(dims.to_vec());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set deflate compression level (0-9).
|
||||
pub fn deflate(mut self, level: u32) -> Self {
|
||||
self.deflate_level = Some(level);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable shuffle filter.
|
||||
pub fn shuffle(mut self) -> Self {
|
||||
self.shuffle = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable fletcher32 checksum.
|
||||
pub fn fletcher32(mut self) -> Self {
|
||||
self.fletcher32 = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable LZ4 compression.
|
||||
pub fn lz4(mut self) -> Self {
|
||||
self.lz4 = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set Zstandard compression level (1-22).
|
||||
pub fn zstd(mut self, level: u32) -> Self {
|
||||
self.zstd_level = Some(level);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set fill time policy.
|
||||
pub fn fill_time(mut self, ft: FillTime) -> Self {
|
||||
self.fill_time = ft;
|
||||
self
|
||||
}
|
||||
|
||||
/// Use compact (inline) storage.
|
||||
pub fn compact(mut self) -> Self {
|
||||
self.compact = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set per-dataset alignment in bytes.
|
||||
pub fn align(mut self, bytes: usize) -> Self {
|
||||
self.alignment = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Validate that the property list is internally consistent.
|
||||
///
|
||||
/// Returns `Ok(())` if the configuration is valid, or an error string
|
||||
/// describing the first contradiction found.
|
||||
///
|
||||
/// Checked invariants:
|
||||
/// - Compact storage and chunk_dims are mutually exclusive.
|
||||
/// - Deflate, shuffle, LZ4, and Zstd filters require chunked storage (chunk_dims).
|
||||
/// - Deflate level must be 0..=9.
|
||||
/// - Zstd level must be 1..=22.
|
||||
pub fn validate(&self) -> Result<(), &'static str> {
|
||||
if self.compact && self.chunk_dims.is_some() {
|
||||
return Err("compact storage is incompatible with chunk_dims");
|
||||
}
|
||||
let has_filters = self.deflate_level.is_some()
|
||||
|| self.shuffle
|
||||
|| self.lz4
|
||||
|| self.zstd_level.is_some()
|
||||
|| self.fletcher32;
|
||||
if has_filters && self.chunk_dims.is_none() && !self.compact {
|
||||
return Err("compression/filter requires chunked storage (set chunk_dims)");
|
||||
}
|
||||
if let Some(level) = self.deflate_level
|
||||
&& level > 9
|
||||
{
|
||||
return Err("deflate level must be 0..=9");
|
||||
}
|
||||
if let Some(level) = self.zstd_level
|
||||
&& !(1..=22).contains(&level)
|
||||
{
|
||||
return Err("zstd level must be 1..=22");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// File access properties.
|
||||
///
|
||||
/// Controls caching, alignment, and I/O behavior when reading files.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileAccessProps {
|
||||
/// Maximum bytes for the chunk cache.
|
||||
pub chunk_cache_bytes: usize,
|
||||
/// Maximum slots in the chunk cache.
|
||||
pub chunk_cache_slots: usize,
|
||||
/// Maximum bytes for the metadata cache.
|
||||
pub metadata_cache_bytes: usize,
|
||||
/// Global alignment threshold.
|
||||
pub alignment_threshold: usize,
|
||||
/// Global alignment bytes.
|
||||
pub alignment_bytes: usize,
|
||||
/// Sieve buffer size for raw data I/O (default: 64 KiB).
|
||||
///
|
||||
/// The sieve buffer aggregates small contiguous reads/writes into
|
||||
/// fewer, larger I/O operations.
|
||||
pub sieve_buffer_size: usize,
|
||||
/// Metadata block allocation size (default: 2 KiB).
|
||||
///
|
||||
/// Metadata is allocated in blocks of this size to reduce fragmentation.
|
||||
pub metadata_block_size: usize,
|
||||
/// Library version bounds: (low, high).
|
||||
///
|
||||
/// Controls which HDF5 format features are used.
|
||||
/// - 0 = earliest (most compatible)
|
||||
/// - 1 = v18 (HDF5 1.8+)
|
||||
/// - 2 = v110 (HDF5 1.10+)
|
||||
/// - 3 = v112 (HDF5 1.12+)
|
||||
/// - 4 = latest (newest features)
|
||||
pub lib_version_bounds: (u8, u8),
|
||||
}
|
||||
|
||||
/// Library version bound constants.
|
||||
pub mod lib_version {
|
||||
/// Earliest version — maximum compatibility.
|
||||
pub const EARLIEST: u8 = 0;
|
||||
/// HDF5 1.8 features.
|
||||
pub const V18: u8 = 1;
|
||||
/// HDF5 1.10 features (SWMR, paged aggregation).
|
||||
pub const V110: u8 = 2;
|
||||
/// HDF5 1.12 features.
|
||||
pub const V112: u8 = 3;
|
||||
/// Latest version — all features.
|
||||
pub const LATEST: u8 = 4;
|
||||
}
|
||||
|
||||
impl Default for FileAccessProps {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
chunk_cache_bytes: crate::chunk_cache::DEFAULT_CACHE_BYTES,
|
||||
chunk_cache_slots: crate::chunk_cache::DEFAULT_MAX_SLOTS,
|
||||
metadata_cache_bytes: 2 * 1024 * 1024, // 2 MiB default
|
||||
alignment_threshold: 0,
|
||||
alignment_bytes: 0,
|
||||
sieve_buffer_size: 64 * 1024, // 64 KiB
|
||||
metadata_block_size: 2048, // 2 KiB
|
||||
lib_version_bounds: (lib_version::EARLIEST, lib_version::LATEST),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileAccessProps {
|
||||
/// Create default file access properties.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Set chunk cache size and slot count.
|
||||
pub fn chunk_cache(mut self, bytes: usize, slots: usize) -> Self {
|
||||
self.chunk_cache_bytes = bytes;
|
||||
self.chunk_cache_slots = slots;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set metadata cache size.
|
||||
pub fn metadata_cache(mut self, bytes: usize) -> Self {
|
||||
self.metadata_cache_bytes = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set global alignment.
|
||||
pub fn alignment(mut self, threshold: usize, bytes: usize) -> Self {
|
||||
self.alignment_threshold = threshold;
|
||||
self.alignment_bytes = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set sieve buffer size for raw data I/O aggregation.
|
||||
pub fn sieve_buffer(mut self, bytes: usize) -> Self {
|
||||
self.sieve_buffer_size = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set metadata block allocation size.
|
||||
pub fn metadata_block(mut self, bytes: usize) -> Self {
|
||||
self.metadata_block_size = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set library version bounds (low, high).
|
||||
///
|
||||
/// Use constants from [`lib_version`] module.
|
||||
pub fn version_bounds(mut self, low: u8, high: u8) -> Self {
|
||||
self.lib_version_bounds = (low, high);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// File creation properties.
|
||||
///
|
||||
/// Controls file-level format parameters.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileCreateProps {
|
||||
/// Superblock version (default: 3).
|
||||
pub superblock_version: u8,
|
||||
/// Size of offsets in bytes (default: 8).
|
||||
pub offset_size: u8,
|
||||
/// Size of lengths in bytes (default: 8).
|
||||
pub length_size: u8,
|
||||
/// B-tree v1 group leaf node K.
|
||||
pub group_leaf_node_k: u16,
|
||||
/// B-tree v1 group internal node K.
|
||||
pub group_internal_node_k: u16,
|
||||
/// B-tree v1 indexed storage internal node K.
|
||||
pub indexed_storage_internal_node_k: u16,
|
||||
}
|
||||
|
||||
impl Default for FileCreateProps {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
superblock_version: 3,
|
||||
offset_size: 8,
|
||||
length_size: 8,
|
||||
group_leaf_node_k: 4,
|
||||
group_internal_node_k: 16,
|
||||
indexed_storage_internal_node_k: 32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileCreateProps {
|
||||
/// Create default file creation properties.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Set superblock version.
|
||||
pub fn superblock_version(mut self, v: u8) -> Self {
|
||||
self.superblock_version = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set offset and length sizes.
|
||||
pub fn sizes(mut self, offset_size: u8, length_size: u8) -> Self {
|
||||
self.offset_size = offset_size;
|
||||
self.length_size = length_size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set B-tree v1 group leaf node K.
|
||||
pub fn group_leaf_k(mut self, k: u16) -> Self {
|
||||
self.group_leaf_node_k = k;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set B-tree v1 group internal node K.
|
||||
pub fn group_internal_k(mut self, k: u16) -> Self {
|
||||
self.group_internal_node_k = k;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set B-tree v1 indexed storage internal node K.
|
||||
pub fn indexed_storage_k(mut self, k: u16) -> Self {
|
||||
self.indexed_storage_internal_node_k = k;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dcpl_defaults() {
|
||||
let dcpl = DatasetCreateProps::new();
|
||||
assert!(dcpl.chunk_dims.is_none());
|
||||
assert_eq!(dcpl.fill_time, FillTime::Alloc);
|
||||
assert!(!dcpl.compact);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcpl_builder_chain() {
|
||||
let dcpl = DatasetCreateProps::new()
|
||||
.chunk(&[10, 10])
|
||||
.deflate(6)
|
||||
.shuffle()
|
||||
.fill_time(FillTime::Never)
|
||||
.align(4096);
|
||||
assert_eq!(dcpl.chunk_dims, Some(vec![10, 10]));
|
||||
assert_eq!(dcpl.deflate_level, Some(6));
|
||||
assert!(dcpl.shuffle);
|
||||
assert_eq!(dcpl.fill_time, FillTime::Never);
|
||||
assert_eq!(dcpl.alignment, 4096);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fapl_defaults() {
|
||||
let fapl = FileAccessProps::new();
|
||||
assert_eq!(fapl.chunk_cache_bytes, 16 * 1024 * 1024);
|
||||
assert_eq!(fapl.chunk_cache_slots, 521);
|
||||
assert_eq!(fapl.sieve_buffer_size, 64 * 1024);
|
||||
assert_eq!(fapl.metadata_block_size, 2048);
|
||||
assert_eq!(
|
||||
fapl.lib_version_bounds,
|
||||
(lib_version::EARLIEST, lib_version::LATEST)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fapl_builder_chain() {
|
||||
let fapl = FileAccessProps::new()
|
||||
.chunk_cache(4 * 1024 * 1024, 127)
|
||||
.sieve_buffer(128 * 1024)
|
||||
.metadata_block(4096)
|
||||
.version_bounds(lib_version::V110, lib_version::LATEST);
|
||||
assert_eq!(fapl.chunk_cache_bytes, 4 * 1024 * 1024);
|
||||
assert_eq!(fapl.chunk_cache_slots, 127);
|
||||
assert_eq!(fapl.sieve_buffer_size, 128 * 1024);
|
||||
assert_eq!(fapl.metadata_block_size, 4096);
|
||||
assert_eq!(
|
||||
fapl.lib_version_bounds,
|
||||
(lib_version::V110, lib_version::LATEST)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fcpl_defaults() {
|
||||
let fcpl = FileCreateProps::new();
|
||||
assert_eq!(fcpl.superblock_version, 3);
|
||||
assert_eq!(fcpl.offset_size, 8);
|
||||
assert_eq!(fcpl.group_leaf_node_k, 4);
|
||||
assert_eq!(fcpl.group_internal_node_k, 16);
|
||||
assert_eq!(fcpl.indexed_storage_internal_node_k, 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fcpl_builder_chain() {
|
||||
let fcpl = FileCreateProps::new()
|
||||
.superblock_version(2)
|
||||
.sizes(4, 4)
|
||||
.group_leaf_k(8)
|
||||
.group_internal_k(32)
|
||||
.indexed_storage_k(64);
|
||||
assert_eq!(fcpl.superblock_version, 2);
|
||||
assert_eq!(fcpl.offset_size, 4);
|
||||
assert_eq!(fcpl.length_size, 4);
|
||||
assert_eq!(fcpl.group_leaf_node_k, 8);
|
||||
assert_eq!(fcpl.group_internal_node_k, 32);
|
||||
assert_eq!(fcpl.indexed_storage_internal_node_k, 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcpl_validate_ok() {
|
||||
let dcpl = DatasetCreateProps::new().chunk(&[10]).deflate(6);
|
||||
assert!(dcpl.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcpl_validate_compact_with_chunks() {
|
||||
let dcpl = DatasetCreateProps::new().compact().chunk(&[10]);
|
||||
assert_eq!(
|
||||
dcpl.validate().unwrap_err(),
|
||||
"compact storage is incompatible with chunk_dims"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcpl_validate_deflate_without_chunks() {
|
||||
let dcpl = DatasetCreateProps::new().deflate(6);
|
||||
assert_eq!(
|
||||
dcpl.validate().unwrap_err(),
|
||||
"compression/filter requires chunked storage (set chunk_dims)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcpl_validate_deflate_level_too_high() {
|
||||
let dcpl = DatasetCreateProps::new().chunk(&[10]).deflate(10);
|
||||
assert_eq!(dcpl.validate().unwrap_err(), "deflate level must be 0..=9");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user