Skip to main content

reddb_types/
table.rs

1//! Logical SQL table definitions.
2//!
3//! The file artifact crate owns the durable byte layout for these types. This
4//! module owns only the logical vocabulary and validation rules.
5
6use crate::DataType;
7use std::collections::HashMap;
8use std::fmt;
9
10/// Table definition containing all metadata.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct TableDef {
13    /// Table name (unique within database).
14    pub name: String,
15    /// Column definitions in order.
16    pub columns: Vec<ColumnDef>,
17    /// Primary key column names (can be composite).
18    pub primary_key: Vec<String>,
19    /// Index definitions.
20    pub indexes: Vec<IndexDef>,
21    /// Table-level constraints.
22    pub constraints: Vec<Constraint>,
23    /// Schema version (for migrations).
24    pub version: u32,
25    /// Creation timestamp.
26    pub created_at: u64,
27    /// Last modification timestamp.
28    pub updated_at: u64,
29}
30
31impl TableDef {
32    /// Create a new table definition.
33    pub fn new(name: impl Into<String>) -> Self {
34        let now = std::time::SystemTime::now()
35            .duration_since(std::time::UNIX_EPOCH)
36            .unwrap_or_default()
37            .as_secs();
38
39        Self {
40            name: name.into(),
41            columns: Vec::new(),
42            primary_key: Vec::new(),
43            indexes: Vec::new(),
44            constraints: Vec::new(),
45            version: 1,
46            created_at: now,
47            updated_at: now,
48        }
49    }
50
51    /// Add a column to the table.
52    pub fn add_column(mut self, column: ColumnDef) -> Self {
53        self.columns.push(column);
54        self
55    }
56
57    /// Set primary key columns.
58    pub fn primary_key(mut self, columns: Vec<String>) -> Self {
59        self.primary_key = columns;
60        self
61    }
62
63    /// Add an index.
64    pub fn add_index(mut self, index: IndexDef) -> Self {
65        self.indexes.push(index);
66        self
67    }
68
69    /// Add a constraint.
70    pub fn add_constraint(mut self, constraint: Constraint) -> Self {
71        self.constraints.push(constraint);
72        self
73    }
74
75    /// Get a column by name.
76    pub fn get_column(&self, name: &str) -> Option<&ColumnDef> {
77        self.columns.iter().find(|column| column.name == name)
78    }
79
80    /// Get a column's ordinal by name.
81    pub fn column_index(&self, name: &str) -> Option<usize> {
82        self.columns.iter().position(|column| column.name == name)
83    }
84
85    /// Check whether a column is part of the primary key.
86    pub fn is_primary_key_column(&self, name: &str) -> bool {
87        self.primary_key.iter().any(|column| column == name)
88    }
89
90    /// Validate references within the table definition.
91    pub fn validate(&self) -> Result<(), TableDefError> {
92        if self.name.is_empty() {
93            return Err(TableDefError::EmptyTableName);
94        }
95
96        let mut seen = HashMap::new();
97        for column in &self.columns {
98            if seen.insert(&column.name, true).is_some() {
99                return Err(TableDefError::DuplicateColumn(column.name.clone()));
100            }
101        }
102
103        for primary_key_column in &self.primary_key {
104            if self.get_column(primary_key_column).is_none() {
105                return Err(TableDefError::InvalidPrimaryKey(primary_key_column.clone()));
106            }
107        }
108
109        for index in &self.indexes {
110            for column in &index.columns {
111                if self.get_column(column).is_none() {
112                    return Err(TableDefError::InvalidIndexColumn(column.clone()));
113                }
114            }
115        }
116
117        for constraint in &self.constraints {
118            for column in &constraint.columns {
119                if self.get_column(column).is_none() {
120                    return Err(TableDefError::InvalidConstraintColumn(column.clone()));
121                }
122            }
123        }
124
125        Ok(())
126    }
127}
128
129impl fmt::Display for TableDef {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        writeln!(f, "TABLE {} (version {})", self.name, self.version)?;
132        writeln!(f, "  Columns:")?;
133        for column in &self.columns {
134            writeln!(f, "    {column}")?;
135        }
136        if !self.primary_key.is_empty() {
137            writeln!(f, "  Primary Key: ({})", self.primary_key.join(", "))?;
138        }
139        if !self.indexes.is_empty() {
140            writeln!(f, "  Indexes:")?;
141            for index in &self.indexes {
142                writeln!(f, "    {index}")?;
143            }
144        }
145        Ok(())
146    }
147}
148
149/// Column definition.
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct ColumnDef {
152    /// Column name.
153    pub name: String,
154    /// Data type.
155    pub data_type: DataType,
156    /// Whether NULL values are allowed.
157    pub nullable: bool,
158    /// Default value (serialized).
159    pub default: Option<Vec<u8>>,
160    /// Vector dimension (for vector columns).
161    pub vector_dim: Option<u32>,
162    /// Whether to compress this column's data.
163    pub compress: bool,
164    /// Valid enum variants.
165    pub enum_variants: Vec<String>,
166    /// Number of decimal places.
167    pub decimal_precision: u8,
168    /// Array element data type.
169    pub element_type: Option<DataType>,
170    /// Additional column metadata.
171    pub metadata: HashMap<String, String>,
172}
173
174impl ColumnDef {
175    /// Create a new column definition.
176    pub fn new(name: impl Into<String>, data_type: DataType) -> Self {
177        Self {
178            name: name.into(),
179            data_type,
180            nullable: true,
181            default: None,
182            vector_dim: None,
183            compress: false,
184            enum_variants: Vec::new(),
185            decimal_precision: 4,
186            element_type: None,
187            metadata: HashMap::new(),
188        }
189    }
190
191    /// Make the column non-nullable.
192    pub fn not_null(mut self) -> Self {
193        self.nullable = false;
194        self
195    }
196
197    /// Set the serialized default value.
198    pub fn with_default(mut self, default: Vec<u8>) -> Self {
199        self.default = Some(default);
200        self
201    }
202
203    /// Set the vector dimension.
204    pub fn with_vector_dim(mut self, dimension: u32) -> Self {
205        self.vector_dim = Some(dimension);
206        self
207    }
208
209    /// Add a metadata entry.
210    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
211        self.metadata.insert(key.into(), value.into());
212        self
213    }
214
215    /// Enable per-column compression.
216    pub fn compressed(mut self) -> Self {
217        self.compress = true;
218        self
219    }
220
221    /// Set enum variants.
222    pub fn with_variants(mut self, variants: Vec<String>) -> Self {
223        self.enum_variants = variants;
224        self
225    }
226
227    /// Set decimal precision.
228    pub fn with_precision(mut self, precision: u8) -> Self {
229        self.decimal_precision = precision;
230        self
231    }
232
233    /// Set the array element type.
234    pub fn with_element_type(mut self, data_type: DataType) -> Self {
235        self.element_type = Some(data_type);
236        self
237    }
238}
239
240impl fmt::Display for ColumnDef {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        write!(f, "{} {}", self.name, self.data_type)?;
243        if let Some(dimension) = self.vector_dim {
244            write!(f, "({dimension})")?;
245        }
246        if !self.nullable {
247            write!(f, " NOT NULL")?;
248        }
249        if self.default.is_some() {
250            write!(f, " DEFAULT <value>")?;
251        }
252        Ok(())
253    }
254}
255
256/// Index definition.
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct IndexDef {
259    pub name: String,
260    pub columns: Vec<String>,
261    pub index_type: IndexType,
262    pub unique: bool,
263}
264
265impl IndexDef {
266    pub fn new(name: impl Into<String>, columns: Vec<String>) -> Self {
267        Self {
268            name: name.into(),
269            columns,
270            index_type: IndexType::BTree,
271            unique: false,
272        }
273    }
274
275    pub fn unique(mut self) -> Self {
276        self.unique = true;
277        self
278    }
279
280    pub fn with_type(mut self, index_type: IndexType) -> Self {
281        self.index_type = index_type;
282        self
283    }
284}
285
286impl fmt::Display for IndexDef {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        if self.unique {
289            write!(f, "UNIQUE ")?;
290        }
291        write!(
292            f,
293            "INDEX {} ({}) USING {:?}",
294            self.name,
295            self.columns.join(", "),
296            self.index_type
297        )
298    }
299}
300
301/// Index type.
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303#[repr(u8)]
304pub enum IndexType {
305    BTree = 1,
306    Hash = 2,
307    IvfFlat = 3,
308    Hnsw = 4,
309}
310
311impl IndexType {
312    pub fn from_byte(byte: u8) -> Option<Self> {
313        match byte {
314            1 => Some(Self::BTree),
315            2 => Some(Self::Hash),
316            3 => Some(Self::IvfFlat),
317            4 => Some(Self::Hnsw),
318            _ => None,
319        }
320    }
321}
322
323/// Constraint definition.
324#[derive(Debug, Clone, PartialEq, Eq)]
325pub struct Constraint {
326    pub name: String,
327    pub constraint_type: ConstraintType,
328    pub columns: Vec<String>,
329    pub ref_table: Option<String>,
330    pub ref_columns: Option<Vec<String>>,
331}
332
333impl Constraint {
334    pub fn new(name: impl Into<String>, constraint_type: ConstraintType) -> Self {
335        Self {
336            name: name.into(),
337            constraint_type,
338            columns: Vec::new(),
339            ref_table: None,
340            ref_columns: None,
341        }
342    }
343
344    pub fn on_columns(mut self, columns: Vec<String>) -> Self {
345        self.columns = columns;
346        self
347    }
348
349    pub fn references(mut self, table: String, columns: Vec<String>) -> Self {
350        self.ref_table = Some(table);
351        self.ref_columns = Some(columns);
352        self
353    }
354}
355
356/// Constraint type.
357#[derive(Debug, Clone, Copy, PartialEq, Eq)]
358#[repr(u8)]
359pub enum ConstraintType {
360    PrimaryKey = 1,
361    Unique = 2,
362    ForeignKey = 3,
363    Check = 4,
364    NotNull = 5,
365}
366
367impl ConstraintType {
368    pub fn from_byte(byte: u8) -> Option<Self> {
369        match byte {
370            1 => Some(Self::PrimaryKey),
371            2 => Some(Self::Unique),
372            3 => Some(Self::ForeignKey),
373            4 => Some(Self::Check),
374            5 => Some(Self::NotNull),
375            _ => None,
376        }
377    }
378}
379
380/// Errors produced by table validation or table-definition decoding.
381#[derive(Debug, Clone, PartialEq, Eq)]
382pub enum TableDefError {
383    EmptyTableName,
384    DuplicateColumn(String),
385    InvalidPrimaryKey(String),
386    InvalidIndexColumn(String),
387    InvalidConstraintColumn(String),
388    TruncatedData,
389    InvalidMagic,
390    InvalidDataType,
391    InvalidIndexType,
392    InvalidConstraintType,
393    VarintOverflow,
394}
395
396impl fmt::Display for TableDefError {
397    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398        match self {
399            Self::EmptyTableName => write!(f, "empty table name"),
400            Self::DuplicateColumn(name) => write!(f, "duplicate column: {name}"),
401            Self::InvalidPrimaryKey(name) => write!(f, "invalid primary key column: {name}"),
402            Self::InvalidIndexColumn(name) => write!(f, "invalid index column: {name}"),
403            Self::InvalidConstraintColumn(name) => {
404                write!(f, "invalid constraint column: {name}")
405            }
406            Self::TruncatedData => write!(f, "truncated data"),
407            Self::InvalidMagic => write!(f, "invalid magic bytes"),
408            Self::InvalidDataType => write!(f, "invalid data type"),
409            Self::InvalidIndexType => write!(f, "invalid index type"),
410            Self::InvalidConstraintType => write!(f, "invalid constraint type"),
411            Self::VarintOverflow => write!(f, "varint overflow"),
412        }
413    }
414}
415
416impl std::error::Error for TableDefError {}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421
422    #[test]
423    fn table_validation_accepts_valid_references() {
424        let table = TableDef::new("hosts")
425            .add_column(ColumnDef::new("id", DataType::UnsignedInteger).not_null())
426            .add_column(ColumnDef::new("ip", DataType::IpAddr))
427            .primary_key(vec!["id".into()])
428            .add_index(IndexDef::new("idx_ip", vec!["ip".into()]).unique());
429
430        assert!(table.validate().is_ok());
431        assert!(table.is_primary_key_column("id"));
432        assert_eq!(table.column_index("ip"), Some(1));
433    }
434
435    #[test]
436    fn table_validation_rejects_invalid_references() {
437        let duplicate = TableDef::new("hosts")
438            .add_column(ColumnDef::new("id", DataType::Integer))
439            .add_column(ColumnDef::new("id", DataType::Text));
440        assert_eq!(
441            duplicate.validate(),
442            Err(TableDefError::DuplicateColumn("id".into()))
443        );
444
445        let missing_key = TableDef::new("hosts").primary_key(vec!["id".into()]);
446        assert_eq!(
447            missing_key.validate(),
448            Err(TableDefError::InvalidPrimaryKey("id".into()))
449        );
450    }
451
452    #[test]
453    fn builders_preserve_column_and_constraint_details() {
454        let column = ColumnDef::new("embedding", DataType::Vector)
455            .not_null()
456            .with_default(vec![1, 2, 3])
457            .with_vector_dim(384)
458            .compressed()
459            .with_variants(vec!["a".into(), "b".into()])
460            .with_precision(6)
461            .with_element_type(DataType::Float)
462            .with_metadata("unit", "f32");
463        assert_eq!(column.vector_dim, Some(384));
464        assert_eq!(column.metadata.get("unit").map(String::as_str), Some("f32"));
465
466        let constraint = Constraint::new("fk_host", ConstraintType::ForeignKey)
467            .on_columns(vec!["host_id".into()])
468            .references("hosts".into(), vec!["id".into()]);
469        assert_eq!(constraint.ref_table.as_deref(), Some("hosts"));
470    }
471}