mod alive;
mod bitmap;
#[cfg(any(test, feature = "test-seams"))]
#[doc(hidden)]
pub mod bitmap_observer;
mod columns;
mod dict;
mod predicate;
pub use alive::{AliveError, AliveSet};
pub use bitmap::DocBitmap;
#[cfg(any(test, feature = "test-seams"))]
#[doc(hidden)]
pub use columns::MetadataBuildTestLimits;
pub use columns::{
BoolColumn, BuildError, Column, ColumnInput, ColumnStore, ColumnStoreBuilder, ColumnValue,
DictionaryColumn, NumericColumn, RawStringColumn,
};
pub use dict::{CodeWidth, DictionaryCodes, DictionaryError, StringDictionary};
pub use predicate::{EvalError, Predicate, PredicateValue, RangeBound, RangePredicate, evaluate};
pub const TIMESTAMP_COLUMN: ColumnId = ColumnId(0);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct ColumnId(u32);
impl ColumnId {
#[must_use]
pub const fn new(value: u32) -> Self {
Self(value)
}
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ColumnType {
U64,
Id128,
I64,
F64,
Bool,
DictionaryString,
RawString,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ColumnDefinition {
id: ColumnId,
name: String,
column_type: ColumnType,
nullable: bool,
}
impl ColumnDefinition {
#[must_use]
pub fn new(
id: ColumnId,
name: impl Into<String>,
column_type: ColumnType,
nullable: bool,
) -> Self {
Self {
id,
name: name.into(),
column_type,
nullable,
}
}
#[must_use]
pub const fn id(&self) -> ColumnId {
self.id
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn column_type(&self) -> ColumnType {
self.column_type
}
#[must_use]
pub const fn is_nullable(&self) -> bool {
self.nullable
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SchemaError {
ReservedTimestampId,
ReservedTimestampName,
DuplicateColumnId(ColumnId),
DuplicateColumnName(String),
}
impl std::fmt::Display for SchemaError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ReservedTimestampId => formatter.write_str("column id 0 is reserved for ts"),
Self::ReservedTimestampName => formatter.write_str("column name ts is reserved"),
Self::DuplicateColumnId(id) => write!(formatter, "duplicate column id {}", id.get()),
Self::DuplicateColumnName(name) => write!(formatter, "duplicate column name {name}"),
}
}
}
impl std::error::Error for SchemaError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SchemaConflict {
Removed(ColumnDefinition),
Changed {
persisted: ColumnDefinition,
declared: ColumnDefinition,
},
AddedNotNullable(ColumnDefinition),
}
struct DescribedColumn<'a>(&'a ColumnDefinition);
impl std::fmt::Display for DescribedColumn<'_> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let definition = self.0;
write!(
formatter,
"'{}' (id {}, {:?}, {})",
definition.name,
definition.id.get(),
definition.column_type,
if definition.nullable {
"nullable"
} else {
"not nullable"
}
)
}
}
impl std::fmt::Display for SchemaConflict {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Removed(persisted) => write!(
formatter,
"attribute {} is persisted but not declared; attributes cannot be removed",
DescribedColumn(persisted)
),
Self::Changed {
persisted,
declared,
} => write!(
formatter,
"attribute {} is declared as {}; persisted attributes cannot change",
DescribedColumn(persisted),
DescribedColumn(declared)
),
Self::AddedNotNullable(declared) => write!(
formatter,
"added attribute {} must be nullable so existing documents read it as null",
DescribedColumn(declared)
),
}
}
}
impl std::error::Error for SchemaConflict {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Schema {
columns: Vec<ColumnDefinition>,
}
impl Schema {
pub(crate) fn resident_bytes(&self) -> Option<usize> {
let definitions = self
.columns
.capacity()
.checked_mul(std::mem::size_of::<ColumnDefinition>())?;
self.columns
.iter()
.try_fold(definitions, |total, definition| {
total.checked_add(definition.name.capacity())
})
}
#[must_use]
pub fn timestamp_only() -> Self {
Self {
columns: vec![ColumnDefinition::new(
TIMESTAMP_COLUMN,
"ts",
ColumnType::I64,
false,
)],
}
}
pub fn new(user_columns: Vec<ColumnDefinition>) -> Result<Self, SchemaError> {
for (position, definition) in user_columns.iter().enumerate() {
if definition.id == TIMESTAMP_COLUMN {
return Err(SchemaError::ReservedTimestampId);
}
if definition.name == "ts" {
return Err(SchemaError::ReservedTimestampName);
}
if let Some(duplicate) = user_columns.iter().take(position).find(|candidate| {
candidate.id == definition.id || candidate.name == definition.name
}) {
if duplicate.id == definition.id {
return Err(SchemaError::DuplicateColumnId(definition.id));
}
return Err(SchemaError::DuplicateColumnName(definition.name.clone()));
}
}
let mut columns = Self::timestamp_only().columns;
columns.reserve(user_columns.len());
columns.extend(user_columns);
Ok(Self { columns })
}
pub fn additive_evolution(&self, declared: &Self) -> Result<Option<Self>, SchemaConflict> {
for persisted in &self.columns {
match declared.column(persisted.id) {
None => return Err(SchemaConflict::Removed(persisted.clone())),
Some(candidate) if candidate != persisted => {
return Err(SchemaConflict::Changed {
persisted: persisted.clone(),
declared: candidate.clone(),
});
}
Some(_) => {}
}
}
let mut added = Vec::new();
for candidate in &declared.columns {
if self.column(candidate.id).is_none() {
if !candidate.nullable {
return Err(SchemaConflict::AddedNotNullable(candidate.clone()));
}
added.push(candidate.clone());
}
}
if added.is_empty() {
return Ok(None);
}
let mut columns = self.columns.clone();
columns.extend(added);
Ok(Some(Self { columns }))
}
#[must_use]
pub fn columns(&self) -> &[ColumnDefinition] {
&self.columns
}
#[must_use]
pub fn column(&self, id: ColumnId) -> Option<&ColumnDefinition> {
self.columns.iter().find(|definition| definition.id == id)
}
#[must_use]
pub fn user_column_count(&self) -> usize {
self.columns.len().saturating_sub(1)
}
}