Skip to main content

zeppelin_embed/meta/
mod.rs

1//! Typed, segment-aligned metadata columns and bitmap predicate evaluation.
2//!
3//! Metadata predicates are a closed data model. They identify schema columns
4//! by [`crate::meta::ColumnId`]; this module contains no parser and accepts no
5//! filter text.
6
7mod alive;
8mod bitmap;
9#[cfg(any(test, feature = "test-support"))]
10#[doc(hidden)]
11pub mod bitmap_observer;
12mod columns;
13mod dict;
14mod predicate;
15
16pub use alive::{AliveError, AliveSet};
17pub use bitmap::DocBitmap;
18#[cfg(any(test, feature = "test-support"))]
19#[doc(hidden)]
20pub use columns::MetadataBuildTestLimits;
21pub use columns::{
22    BoolColumn, BuildError, Column, ColumnInput, ColumnStore, ColumnStoreBuilder, ColumnValue,
23    DictionaryColumn, NumericColumn, RawStringColumn,
24};
25pub use dict::{CodeWidth, DictionaryCodes, DictionaryError, StringDictionary};
26pub use predicate::{EvalError, Predicate, PredicateValue, RangeBound, RangePredicate, evaluate};
27
28/// The reserved identifier of the required per-row timestamp column.
29pub const TIMESTAMP_COLUMN: ColumnId = ColumnId(0);
30
31/// A stable, schema-local identifier used by structured predicates.
32#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
33#[repr(transparent)]
34pub struct ColumnId(u32);
35
36impl ColumnId {
37    /// Creates an identifier from its schema-local integer representation.
38    #[must_use]
39    pub const fn new(value: u32) -> Self {
40        Self(value)
41    }
42
43    /// Returns the schema-local integer representation.
44    #[must_use]
45    pub const fn get(self) -> u32 {
46        self.0
47    }
48}
49
50/// The physical type of a segment-aligned metadata column.
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub enum ColumnType {
53    /// Unsigned 64-bit integers.
54    U64,
55    /// Signed 64-bit integers.
56    I64,
57    /// IEEE-754 64-bit floating-point values.
58    F64,
59    /// Boolean values stored as contiguous bytes.
60    Bool,
61    /// Low-cardinality UTF-8 strings encoded through a segment dictionary.
62    DictionaryString,
63    /// Raw UTF-8 strings retained as stored-field values.
64    RawString,
65}
66
67/// One typed user-column declaration in a collection schema.
68#[derive(Clone, Debug, Eq, PartialEq)]
69pub struct ColumnDefinition {
70    id: ColumnId,
71    name: String,
72    column_type: ColumnType,
73    nullable: bool,
74}
75
76impl ColumnDefinition {
77    /// Creates a typed column declaration.
78    #[must_use]
79    pub fn new(
80        id: ColumnId,
81        name: impl Into<String>,
82        column_type: ColumnType,
83        nullable: bool,
84    ) -> Self {
85        Self {
86            id,
87            name: name.into(),
88            column_type,
89            nullable,
90        }
91    }
92
93    /// Returns the column identifier.
94    #[must_use]
95    pub const fn id(&self) -> ColumnId {
96        self.id
97    }
98
99    /// Returns the binding-facing column name.
100    #[must_use]
101    pub fn name(&self) -> &str {
102        &self.name
103    }
104
105    /// Returns the physical column type.
106    #[must_use]
107    pub const fn column_type(&self) -> ColumnType {
108        self.column_type
109    }
110
111    /// Returns whether a row may omit this column.
112    #[must_use]
113    pub const fn is_nullable(&self) -> bool {
114        self.nullable
115    }
116}
117
118/// A schema-construction failure detected before any rows are accepted.
119#[derive(Clone, Debug, Eq, PartialEq)]
120pub enum SchemaError {
121    /// Column zero is reserved for the required timestamp array.
122    ReservedTimestampId,
123    /// The name `ts` is reserved for the required timestamp array.
124    ReservedTimestampName,
125    /// Two declarations used the same identifier.
126    DuplicateColumnId(ColumnId),
127    /// Two declarations used the same binding-facing name.
128    DuplicateColumnName(String),
129}
130
131impl std::fmt::Display for SchemaError {
132    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        match self {
134            Self::ReservedTimestampId => formatter.write_str("column id 0 is reserved for ts"),
135            Self::ReservedTimestampName => formatter.write_str("column name ts is reserved"),
136            Self::DuplicateColumnId(id) => write!(formatter, "duplicate column id {}", id.get()),
137            Self::DuplicateColumnName(name) => write!(formatter, "duplicate column name {name}"),
138        }
139    }
140}
141
142impl std::error::Error for SchemaError {}
143
144/// An immutable collection schema including the required `ts: i64` column.
145#[derive(Clone, Debug, Eq, PartialEq)]
146pub struct Schema {
147    columns: Vec<ColumnDefinition>,
148}
149
150impl Schema {
151    pub(crate) fn resident_bytes(&self) -> Option<usize> {
152        let definitions = self
153            .columns
154            .capacity()
155            .checked_mul(std::mem::size_of::<ColumnDefinition>())?;
156        self.columns
157            .iter()
158            .try_fold(definitions, |total, definition| {
159                total.checked_add(definition.name.capacity())
160            })
161    }
162
163    /// Creates the mandatory timestamp-only schema used by public ingest today.
164    #[must_use]
165    pub fn timestamp_only() -> Self {
166        Self {
167            columns: vec![ColumnDefinition::new(
168                TIMESTAMP_COLUMN,
169                "ts",
170                ColumnType::I64,
171                false,
172            )],
173        }
174    }
175
176    /// Validates user columns and prepends the reserved timestamp definition.
177    pub fn new(user_columns: Vec<ColumnDefinition>) -> Result<Self, SchemaError> {
178        for (position, definition) in user_columns.iter().enumerate() {
179            if definition.id == TIMESTAMP_COLUMN {
180                return Err(SchemaError::ReservedTimestampId);
181            }
182            if definition.name == "ts" {
183                return Err(SchemaError::ReservedTimestampName);
184            }
185            if let Some(duplicate) = user_columns.iter().take(position).find(|candidate| {
186                candidate.id == definition.id || candidate.name == definition.name
187            }) {
188                if duplicate.id == definition.id {
189                    return Err(SchemaError::DuplicateColumnId(definition.id));
190                }
191                return Err(SchemaError::DuplicateColumnName(definition.name.clone()));
192            }
193        }
194
195        let mut columns = Self::timestamp_only().columns;
196        columns.reserve(user_columns.len());
197        columns.extend(user_columns);
198        Ok(Self { columns })
199    }
200
201    /// Returns all declarations, beginning with the required timestamp column.
202    #[must_use]
203    pub fn columns(&self) -> &[ColumnDefinition] {
204        &self.columns
205    }
206
207    /// Finds a declaration by its typed identifier.
208    #[must_use]
209    pub fn column(&self, id: ColumnId) -> Option<&ColumnDefinition> {
210        self.columns.iter().find(|definition| definition.id == id)
211    }
212
213    /// Returns the number of user-declared columns, excluding `ts`.
214    #[must_use]
215    pub fn user_column_count(&self) -> usize {
216        self.columns.len().saturating_sub(1)
217    }
218}