use super::{ChunkDescriptor, ChunkId, DatasetId, LogicalRow, ProviderError};
use std::sync::Arc;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PayloadKind {
Structure,
Property,
Frame,
BondTopology,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChunkLayout {
Regular {
rows_per_chunk: u32,
},
SourceDefined {
target_rows: u32,
},
}
impl ChunkLayout {
#[must_use]
pub const fn target_rows(self) -> u32 {
match self {
Self::Regular { rows_per_chunk } => rows_per_chunk,
Self::SourceDefined { target_rows } => target_rows,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DatasetDescriptor {
id: DatasetId,
payload: PayloadKind,
logical_rows: u64,
chunk_count: u64,
first_chunk: ChunkId,
layout: ChunkLayout,
}
impl DatasetDescriptor {
pub fn regular(
id: DatasetId,
payload: PayloadKind,
logical_rows: u64,
rows_per_chunk: u32,
first_chunk: ChunkId,
) -> Result<Self, ProviderError> {
if rows_per_chunk == 0 {
return Err(ProviderError::ZeroChunkRows { dataset: id });
}
let divisor = u64::from(rows_per_chunk);
let chunk_count = match logical_rows.checked_add(divisor - 1) {
Some(rounded) => rounded / divisor,
None => {
return Err(ProviderError::IdentityOverflow {
dataset: id,
field: "chunk count",
});
}
};
Self::new(
id,
payload,
logical_rows,
chunk_count,
first_chunk,
ChunkLayout::Regular { rows_per_chunk },
)
}
pub fn source_defined(
id: DatasetId,
payload: PayloadKind,
logical_rows: u64,
chunk_count: u64,
first_chunk: ChunkId,
target_rows: u32,
) -> Result<Self, ProviderError> {
Self::new(
id,
payload,
logical_rows,
chunk_count,
first_chunk,
ChunkLayout::SourceDefined { target_rows },
)
}
fn new(
id: DatasetId,
payload: PayloadKind,
logical_rows: u64,
chunk_count: u64,
first_chunk: ChunkId,
layout: ChunkLayout,
) -> Result<Self, ProviderError> {
if layout.target_rows() == 0 {
return Err(ProviderError::ZeroChunkRows { dataset: id });
}
if logical_rows > 0 && chunk_count == 0 {
return Err(ProviderError::MissingChunks {
dataset: id,
rows: logical_rows,
});
}
validate_chunk_range(id, first_chunk, chunk_count)?;
Ok(Self {
id,
payload,
logical_rows,
chunk_count,
first_chunk,
layout,
})
}
#[must_use]
pub const fn id(self) -> DatasetId {
self.id
}
#[must_use]
pub const fn payload(self) -> PayloadKind {
self.payload
}
#[must_use]
pub const fn logical_rows(self) -> u64 {
self.logical_rows
}
#[must_use]
pub const fn chunk_count(self) -> u64 {
self.chunk_count
}
#[must_use]
pub const fn first_chunk(self) -> ChunkId {
self.first_chunk
}
#[must_use]
pub const fn layout(self) -> ChunkLayout {
self.layout
}
pub fn regular_chunk(self, chunk: ChunkId) -> Result<ChunkDescriptor, ProviderError> {
let ChunkLayout::Regular { rows_per_chunk } = self.layout else {
return Err(ProviderError::UnknownChunk {
dataset: self.id,
chunk,
});
};
let ordinal = self.chunk_ordinal(chunk)?;
let start = ordinal.checked_mul(u64::from(rows_per_chunk)).ok_or(
ProviderError::IdentityOverflow {
dataset: self.id,
field: "logical row",
},
)?;
let remaining =
self.logical_rows
.checked_sub(start)
.ok_or(ProviderError::LogicalRowOutOfRange {
dataset: self.id,
row: LogicalRow::new(start),
})?;
let rows = remaining.min(u64::from(rows_per_chunk));
let rows = u32::try_from(rows).map_err(|_| ProviderError::IdentityOverflow {
dataset: self.id,
field: "local row count",
})?;
ChunkDescriptor::new(self.id, chunk, LogicalRow::new(start), rows)
}
pub(crate) fn chunk_ordinal(self, chunk: ChunkId) -> Result<u64, ProviderError> {
let Some(ordinal) = chunk.get().checked_sub(self.first_chunk.get()) else {
return Err(ProviderError::UnknownChunk {
dataset: self.id,
chunk,
});
};
if ordinal >= self.chunk_count {
return Err(ProviderError::UnknownChunk {
dataset: self.id,
chunk,
});
}
Ok(ordinal)
}
}
#[derive(Clone, Debug, Default)]
pub struct DatasetCatalog {
datasets: Arc<[DatasetDescriptor]>,
}
impl DatasetCatalog {
pub fn new(mut datasets: Vec<DatasetDescriptor>) -> Result<Self, ProviderError> {
datasets.sort_unstable_by_key(|dataset| dataset.id());
validate_dataset_ids(&datasets)?;
validate_chunk_ranges(&datasets)?;
Ok(Self {
datasets: datasets.into(),
})
}
#[must_use]
pub fn len(&self) -> usize {
self.datasets.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.datasets.is_empty()
}
#[must_use]
pub fn datasets(&self) -> &[DatasetDescriptor] {
&self.datasets
}
#[must_use]
pub fn get(&self, id: DatasetId) -> Option<DatasetDescriptor> {
self.datasets
.binary_search_by_key(&id, |dataset| dataset.id())
.ok()
.and_then(|index| self.datasets.get(index).copied())
}
}
fn validate_chunk_range(
dataset: DatasetId,
first: ChunkId,
count: u64,
) -> Result<(), ProviderError> {
if count == 0 {
return Ok(());
}
first
.get()
.checked_add(count - 1)
.map(|_| ())
.ok_or(ProviderError::IdentityOverflow {
dataset,
field: "chunk identity",
})
}
fn validate_dataset_ids(datasets: &[DatasetDescriptor]) -> Result<(), ProviderError> {
for pair in datasets.windows(2) {
if pair[0].id() == pair[1].id() {
return Err(ProviderError::DuplicateDataset {
dataset: pair[0].id(),
});
}
}
Ok(())
}
fn validate_chunk_ranges(datasets: &[DatasetDescriptor]) -> Result<(), ProviderError> {
let mut ranges: Vec<_> = datasets
.iter()
.filter(|dataset| dataset.chunk_count() > 0)
.map(|dataset| {
let end = dataset.first_chunk().get() + dataset.chunk_count() - 1;
(dataset.first_chunk().get(), end)
})
.collect();
ranges.sort_unstable_by_key(|range| range.0);
for pair in ranges.windows(2) {
if pair[1].0 <= pair[0].1 {
return Err(ProviderError::OverlappingChunkRange {
chunk: ChunkId::new(pair[1].0),
});
}
}
Ok(())
}
#[cfg(test)]
#[path = "catalog_tests.rs"]
mod tests;