Skip to main content

nodedb_types/columnar/
column_def.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! [`ColumnDef`] and [`ColumnModifier`] — typed column definitions for strict
4//! document and columnar collections.
5
6use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10use super::column_type::ColumnType;
11
12/// Column-level modifiers that designate special engine roles.
13///
14/// These tell the engine which column serves a specialized purpose.
15/// Extensible for future column roles (e.g., `PartitionKey`, `SortKey`).
16#[derive(
17    Debug,
18    Clone,
19    PartialEq,
20    Eq,
21    Hash,
22    Serialize,
23    Deserialize,
24    zerompk::ToMessagePack,
25    zerompk::FromMessagePack,
26)]
27#[msgpack(c_enum)]
28#[repr(u8)]
29pub enum ColumnModifier {
30    /// This column is the time-partitioning key (timeseries profile).
31    /// Exactly one required for timeseries collections.
32    TimeKey = 0,
33    /// This column has an automatic R-tree spatial index (spatial profile).
34    /// Exactly one required for spatial collections.
35    SpatialIndex = 1,
36}
37
38/// A single column definition in a strict document or columnar schema.
39///
40/// `#[non_exhaustive]` — new fields may be added (e.g. column-level
41/// compression hints, foreign-key metadata).
42#[non_exhaustive]
43#[derive(
44    Debug,
45    Clone,
46    PartialEq,
47    Eq,
48    Serialize,
49    Deserialize,
50    zerompk::ToMessagePack,
51    zerompk::FromMessagePack,
52)]
53pub struct ColumnDef {
54    pub name: String,
55    pub column_type: ColumnType,
56    pub nullable: bool,
57    pub default: Option<String>,
58    pub primary_key: bool,
59    /// Column-level modifiers (TIME_KEY, SPATIAL_INDEX, etc.).
60    #[serde(default, skip_serializing_if = "Vec::is_empty")]
61    pub modifiers: Vec<ColumnModifier>,
62    /// GENERATED ALWAYS AS expression (serialized SqlExpr JSON).
63    /// When set, this column is computed at write time, not supplied by the user.
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub generated_expr: Option<String>,
66    /// Column names this generated column depends on.
67    #[serde(default, skip_serializing_if = "Vec::is_empty")]
68    pub generated_deps: Vec<String>,
69    /// Schema version at which this column was added. Original columns have
70    /// version 1 (the default). Columns added via `ALTER ADD COLUMN` record
71    /// the schema version after the bump so the reader can build a physical
72    /// sub-schema for tuples written under older versions.
73    #[serde(default = "default_added_at_version")]
74    pub added_at_version: u32,
75}
76
77fn default_added_at_version() -> u32 {
78    1
79}
80
81impl ColumnDef {
82    pub fn required(name: impl Into<String>, column_type: ColumnType) -> Self {
83        Self {
84            name: name.into(),
85            column_type,
86            nullable: false,
87            default: None,
88            primary_key: false,
89            modifiers: Vec::new(),
90            generated_expr: None,
91            generated_deps: Vec::new(),
92            added_at_version: 1,
93        }
94    }
95
96    pub fn nullable(name: impl Into<String>, column_type: ColumnType) -> Self {
97        Self {
98            name: name.into(),
99            column_type,
100            nullable: true,
101            default: None,
102            primary_key: false,
103            modifiers: Vec::new(),
104            generated_expr: None,
105            generated_deps: Vec::new(),
106            added_at_version: 1,
107        }
108    }
109
110    pub fn with_primary_key(mut self) -> Self {
111        self.primary_key = true;
112        self.nullable = false;
113        self
114    }
115
116    /// Check if this column has the TIME_KEY modifier.
117    pub fn is_time_key(&self) -> bool {
118        self.modifiers.contains(&ColumnModifier::TimeKey)
119    }
120
121    /// Check if this column has the SPATIAL_INDEX modifier.
122    pub fn is_spatial_index(&self) -> bool {
123        self.modifiers.contains(&ColumnModifier::SpatialIndex)
124    }
125
126    pub fn with_default(mut self, expr: impl Into<String>) -> Self {
127        self.default = Some(expr.into());
128        self
129    }
130}
131
132impl fmt::Display for ColumnDef {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        write!(f, "{} {}", self.name, self.column_type)?;
135        if !self.nullable {
136            write!(f, " NOT NULL")?;
137        }
138        if self.primary_key {
139            write!(f, " PRIMARY KEY")?;
140        }
141        if let Some(ref d) = self.default {
142            write!(f, " DEFAULT {d}")?;
143        }
144        Ok(())
145    }
146}