Skip to main content

kimberlite_query/
schema.rs

1//! Schema definitions for query planning.
2//!
3//! Provides explicit mapping of SQL table/column names to store types.
4
5use std::collections::BTreeMap;
6use std::fmt::{self, Debug, Display};
7
8use kimberlite_store::TableId;
9
10// ============================================================================
11// Names
12// ============================================================================
13
14/// SQL table name.
15#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub struct TableName(String);
17
18impl TableName {
19    /// Creates a new table name.
20    pub fn new(name: impl Into<String>) -> Self {
21        Self(name.into())
22    }
23
24    /// Returns the table name as a string slice.
25    pub fn as_str(&self) -> &str {
26        &self.0
27    }
28}
29
30impl Debug for TableName {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "TableName({:?})", self.0)
33    }
34}
35
36impl Display for TableName {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(f, "{}", self.0)
39    }
40}
41
42impl From<&str> for TableName {
43    fn from(s: &str) -> Self {
44        Self::new(s)
45    }
46}
47
48impl From<String> for TableName {
49    fn from(s: String) -> Self {
50        Self::new(s)
51    }
52}
53
54/// SQL column name.
55#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub struct ColumnName(String);
57
58impl ColumnName {
59    /// Creates a new column name.
60    pub fn new(name: impl Into<String>) -> Self {
61        Self(name.into())
62    }
63
64    /// Returns the column name as a string slice.
65    pub fn as_str(&self) -> &str {
66        &self.0
67    }
68}
69
70impl Debug for ColumnName {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "ColumnName({:?})", self.0)
73    }
74}
75
76impl Display for ColumnName {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "{}", self.0)
79    }
80}
81
82impl From<&str> for ColumnName {
83    fn from(s: &str) -> Self {
84        Self::new(s)
85    }
86}
87
88impl From<String> for ColumnName {
89    fn from(s: String) -> Self {
90        Self::new(s)
91    }
92}
93
94// ============================================================================
95// Data Types
96// ============================================================================
97
98/// SQL data types supported by the query engine.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub enum DataType {
101    // ===== Integer Types =====
102    /// 8-bit signed integer (-128 to 127).
103    TinyInt,
104    /// 16-bit signed integer (-32,768 to 32,767).
105    SmallInt,
106    /// 32-bit signed integer (-2^31 to 2^31-1).
107    Integer,
108    /// 64-bit signed integer (-2^63 to 2^63-1).
109    BigInt,
110
111    // ===== Numeric Types =====
112    /// 64-bit floating point number (IEEE 754 double precision).
113    Real,
114    /// Fixed-precision decimal number.
115    ///
116    /// Stored internally as i128 in smallest units.
117    /// Example: DECIMAL(10,2) stores 123.45 as 12345.
118    Decimal {
119        /// Total number of digits (1-38).
120        precision: u8,
121        /// Number of digits after decimal point (0-precision).
122        scale: u8,
123    },
124
125    // ===== String Types =====
126    /// Variable-length UTF-8 text.
127    Text,
128
129    // ===== Binary Types =====
130    /// Variable-length binary data.
131    Bytes,
132
133    // ===== Boolean Type =====
134    /// Boolean value (true/false).
135    Boolean,
136
137    // ===== Date/Time Types =====
138    /// Date (days since Unix epoch, i32).
139    Date,
140    /// Time of day (nanoseconds within day, i64).
141    Time,
142    /// Timestamp (nanoseconds since Unix epoch, u64).
143    Timestamp,
144
145    // ===== Structured Types =====
146    /// UUID (RFC 4122, 128-bit).
147    Uuid,
148    /// JSON document (validated, stored as text).
149    Json,
150}
151
152impl Display for DataType {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match self {
155            DataType::TinyInt => write!(f, "TINYINT"),
156            DataType::SmallInt => write!(f, "SMALLINT"),
157            DataType::Integer => write!(f, "INTEGER"),
158            DataType::BigInt => write!(f, "BIGINT"),
159            DataType::Real => write!(f, "REAL"),
160            DataType::Decimal { precision, scale } => write!(f, "DECIMAL({precision},{scale})"),
161            DataType::Text => write!(f, "TEXT"),
162            DataType::Bytes => write!(f, "BYTES"),
163            DataType::Boolean => write!(f, "BOOLEAN"),
164            DataType::Date => write!(f, "DATE"),
165            DataType::Time => write!(f, "TIME"),
166            DataType::Timestamp => write!(f, "TIMESTAMP"),
167            DataType::Uuid => write!(f, "UUID"),
168            DataType::Json => write!(f, "JSON"),
169        }
170    }
171}
172
173// ============================================================================
174// Column Definition
175// ============================================================================
176
177/// Definition of a table column.
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct ColumnDef {
180    /// Column name.
181    pub name: ColumnName,
182    /// Column data type.
183    pub data_type: DataType,
184    /// Whether the column can contain NULL values.
185    pub nullable: bool,
186}
187
188impl ColumnDef {
189    /// Creates a new column definition.
190    pub fn new(name: impl Into<ColumnName>, data_type: DataType) -> Self {
191        Self {
192            name: name.into(),
193            data_type,
194            nullable: true,
195        }
196    }
197
198    /// Makes this column non-nullable.
199    pub fn not_null(mut self) -> Self {
200        self.nullable = false;
201        self
202    }
203}
204
205// ============================================================================
206// Index Definition
207// ============================================================================
208
209/// Definition of a secondary index on a table.
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct IndexDef {
212    /// Index ID in the store.
213    pub index_id: u64,
214    /// Index name.
215    pub name: String,
216    /// Indexed column names (in order).
217    pub columns: Vec<ColumnName>,
218}
219
220impl IndexDef {
221    /// Creates a new index definition.
222    pub fn new(index_id: u64, name: impl Into<String>, columns: Vec<ColumnName>) -> Self {
223        Self {
224            index_id,
225            name: name.into(),
226            columns,
227        }
228    }
229}
230
231// ============================================================================
232// Table Definition
233// ============================================================================
234
235/// Definition of a table in the schema.
236#[derive(Debug, Clone, PartialEq, Eq)]
237pub struct TableDef {
238    /// Underlying store table ID.
239    pub table_id: TableId,
240    /// Column definitions in order.
241    pub columns: Vec<ColumnDef>,
242    /// Primary key column names (in order).
243    pub primary_key: Vec<ColumnName>,
244    /// Secondary indexes on this table.
245    pub indexes: Vec<IndexDef>,
246}
247
248impl TableDef {
249    /// Creates a new table definition.
250    pub fn new(table_id: TableId, columns: Vec<ColumnDef>, primary_key: Vec<ColumnName>) -> Self {
251        // Validate primary key columns exist
252        for pk_col in &primary_key {
253            debug_assert!(
254                columns.iter().any(|c| &c.name == pk_col),
255                "primary key column '{pk_col}' not found in columns"
256            );
257        }
258
259        Self {
260            table_id,
261            columns,
262            primary_key,
263            indexes: Vec::new(),
264        }
265    }
266
267    /// Adds an index to this table definition.
268    pub fn with_index(mut self, index: IndexDef) -> Self {
269        self.indexes.push(index);
270        self
271    }
272
273    /// Returns all indexes for this table.
274    pub fn indexes(&self) -> &[IndexDef] {
275        &self.indexes
276    }
277
278    /// Finds an index that can be used for the given column.
279    pub fn find_index_for_column(&self, column: &ColumnName) -> Option<&IndexDef> {
280        self.indexes
281            .iter()
282            .find(|idx| !idx.columns.is_empty() && &idx.columns[0] == column)
283    }
284
285    /// Finds a column by name.
286    pub fn find_column(&self, name: &ColumnName) -> Option<(usize, &ColumnDef)> {
287        self.columns
288            .iter()
289            .enumerate()
290            .find(|(_, c)| &c.name == name)
291    }
292
293    /// Returns true if the given column is part of the primary key.
294    pub fn is_primary_key(&self, name: &ColumnName) -> bool {
295        self.primary_key.contains(name)
296    }
297
298    /// Returns the index of a column in the primary key.
299    pub fn primary_key_position(&self, name: &ColumnName) -> Option<usize> {
300        self.primary_key.iter().position(|pk| pk == name)
301    }
302
303    /// Returns the column indices that form the primary key.
304    pub fn primary_key_indices(&self) -> Vec<usize> {
305        self.primary_key
306            .iter()
307            .filter_map(|pk| self.find_column(pk).map(|(idx, _)| idx))
308            .collect()
309    }
310}
311
312// ============================================================================
313// Schema
314// ============================================================================
315
316/// Schema registry mapping SQL names to store types.
317#[derive(Debug, Clone, Default)]
318pub struct Schema {
319    tables: BTreeMap<TableName, TableDef>,
320}
321
322impl Schema {
323    /// Creates an empty schema.
324    pub fn new() -> Self {
325        Self::default()
326    }
327
328    /// Adds a table to the schema.
329    pub fn add_table(&mut self, name: impl Into<TableName>, def: TableDef) {
330        self.tables.insert(name.into(), def);
331    }
332
333    /// Looks up a table by name.
334    pub fn get_table(&self, name: &TableName) -> Option<&TableDef> {
335        self.tables.get(name)
336    }
337
338    /// Returns all table names.
339    pub fn table_names(&self) -> impl Iterator<Item = &TableName> {
340        self.tables.keys()
341    }
342
343    /// Returns the number of tables.
344    pub fn len(&self) -> usize {
345        self.tables.len()
346    }
347
348    /// Returns true if the schema has no tables.
349    pub fn is_empty(&self) -> bool {
350        self.tables.is_empty()
351    }
352}
353
354// ============================================================================
355// Builder
356// ============================================================================
357
358/// Builder for constructing schemas fluently.
359#[derive(Debug, Default)]
360pub struct SchemaBuilder {
361    schema: Schema,
362}
363
364impl SchemaBuilder {
365    /// Creates a new schema builder.
366    pub fn new() -> Self {
367        Self::default()
368    }
369
370    /// Adds a table to the schema.
371    pub fn table(
372        mut self,
373        name: impl Into<TableName>,
374        table_id: TableId,
375        columns: Vec<ColumnDef>,
376        primary_key: Vec<ColumnName>,
377    ) -> Self {
378        let def = TableDef::new(table_id, columns, primary_key);
379        self.schema.add_table(name, def);
380        self
381    }
382
383    /// Builds the schema.
384    pub fn build(self) -> Schema {
385        self.schema
386    }
387}