alopex_sql/catalog/
table.rs

1//! Table and column metadata definitions for the Alopex SQL catalog.
2//!
3//! This module defines [`TableMetadata`] and [`ColumnMetadata`] which store
4//! schema information for tables and their columns.
5
6use crate::ast::expr::Expr;
7use crate::planner::types::ResolvedType;
8
9/// Storage layout for a table.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum StorageType {
12    /// Row-oriented storage (existing engine).
13    Row,
14    /// Columnar storage (v0.1.3+).
15    Columnar,
16}
17
18/// RowID materialization mode for columnar tables.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum RowIdMode {
21    /// RowID is not materialized or tracked.
22    None,
23    /// RowID is stored directly as a column.
24    Direct,
25}
26
27/// Compression codec used for stored data.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum Compression {
30    None,
31    Lz4,
32    Zstd,
33}
34
35/// Configurable storage options for a table.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct StorageOptions {
38    pub storage_type: StorageType,
39    pub compression: Compression,
40    pub row_group_size: u32,
41    pub row_id_mode: RowIdMode,
42}
43
44impl Default for StorageOptions {
45    fn default() -> Self {
46        Self {
47            storage_type: StorageType::Row,
48            compression: Compression::Lz4,
49            row_group_size: 100_000,
50            row_id_mode: RowIdMode::Direct,
51        }
52    }
53}
54
55/// Metadata for a table in the catalog.
56///
57/// Contains the table ID, name, column definitions, and optional primary key constraint.
58///
59/// # Examples
60///
61/// ```
62/// use alopex_sql::catalog::{TableMetadata, ColumnMetadata};
63/// use alopex_sql::planner::types::ResolvedType;
64///
65/// let columns = vec![
66///     ColumnMetadata::new("id", ResolvedType::Integer)
67///         .with_primary_key(true)
68///         .with_not_null(true),
69///     ColumnMetadata::new("name", ResolvedType::Text)
70///         .with_not_null(true),
71/// ];
72///
73/// let table = TableMetadata::new("users", columns)
74///     .with_primary_key(vec!["id".to_string()]);
75///
76/// assert_eq!(table.name, "users");
77/// assert!(table.get_column("id").is_some());
78/// assert_eq!(table.column_names(), vec!["id", "name"]);
79/// ```
80#[derive(Debug, Clone)]
81pub struct TableMetadata {
82    /// Unique table ID assigned by the catalog.
83    pub table_id: u32,
84    /// Table name.
85    pub name: String,
86    /// Column definitions (order is preserved).
87    pub columns: Vec<ColumnMetadata>,
88    /// Primary key columns (supports composite keys).
89    pub primary_key: Option<Vec<String>>,
90    /// Storage configuration (row/columnar, compression, row group sizing).
91    pub storage_options: StorageOptions,
92}
93
94impl TableMetadata {
95    /// Create a new table metadata with the given name and columns.
96    ///
97    /// The table_id defaults to 0; use `with_table_id()` to set it,
98    /// or it will be assigned by the Catalog when the table is created.
99    pub fn new(name: impl Into<String>, columns: Vec<ColumnMetadata>) -> Self {
100        Self {
101            table_id: 0,
102            name: name.into(),
103            columns,
104            primary_key: None,
105            storage_options: StorageOptions::default(),
106        }
107    }
108
109    /// Set the table ID.
110    pub fn with_table_id(mut self, table_id: u32) -> Self {
111        self.table_id = table_id;
112        self
113    }
114
115    /// Set the primary key columns.
116    pub fn with_primary_key(mut self, columns: Vec<String>) -> Self {
117        self.primary_key = Some(columns);
118        self
119    }
120
121    /// Get a column by name.
122    ///
123    /// Returns `None` if the column doesn't exist.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use alopex_sql::catalog::{TableMetadata, ColumnMetadata};
129    /// use alopex_sql::planner::types::ResolvedType;
130    ///
131    /// let table = TableMetadata::new("users", vec![
132    ///     ColumnMetadata::new("id", ResolvedType::Integer),
133    ///     ColumnMetadata::new("name", ResolvedType::Text),
134    /// ]);
135    ///
136    /// assert!(table.get_column("id").is_some());
137    /// assert!(table.get_column("unknown").is_none());
138    /// ```
139    pub fn get_column(&self, name: &str) -> Option<&ColumnMetadata> {
140        self.columns.iter().find(|c| c.name == name)
141    }
142
143    /// Get the index of a column by name.
144    ///
145    /// Returns `None` if the column doesn't exist.
146    pub fn get_column_index(&self, name: &str) -> Option<usize> {
147        self.columns.iter().position(|c| c.name == name)
148    }
149
150    /// Get a list of all column names in definition order.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use alopex_sql::catalog::{TableMetadata, ColumnMetadata};
156    /// use alopex_sql::planner::types::ResolvedType;
157    ///
158    /// let table = TableMetadata::new("users", vec![
159    ///     ColumnMetadata::new("id", ResolvedType::Integer),
160    ///     ColumnMetadata::new("name", ResolvedType::Text),
161    ///     ColumnMetadata::new("age", ResolvedType::Integer),
162    /// ]);
163    ///
164    /// assert_eq!(table.column_names(), vec!["id", "name", "age"]);
165    /// ```
166    pub fn column_names(&self) -> Vec<&str> {
167        self.columns.iter().map(|c| c.name.as_str()).collect()
168    }
169
170    /// Get the number of columns in the table.
171    pub fn column_count(&self) -> usize {
172        self.columns.len()
173    }
174}
175
176/// Metadata for a column in a table.
177///
178/// Contains the column name, data type, and constraint information.
179///
180/// # Examples
181///
182/// ```
183/// use alopex_sql::catalog::ColumnMetadata;
184/// use alopex_sql::planner::types::ResolvedType;
185///
186/// let column = ColumnMetadata::new("id", ResolvedType::Integer)
187///     .with_not_null(true)
188///     .with_primary_key(true);
189///
190/// assert_eq!(column.name, "id");
191/// assert_eq!(column.data_type, ResolvedType::Integer);
192/// assert!(column.not_null);
193/// assert!(column.primary_key);
194/// ```
195#[derive(Debug, Clone)]
196pub struct ColumnMetadata {
197    /// Column name.
198    pub name: String,
199    /// Column data type (normalized).
200    pub data_type: ResolvedType,
201    /// NOT NULL constraint.
202    pub not_null: bool,
203    /// PRIMARY KEY constraint.
204    pub primary_key: bool,
205    /// UNIQUE constraint.
206    pub unique: bool,
207    /// DEFAULT value expression.
208    pub default: Option<Expr>,
209}
210
211impl ColumnMetadata {
212    /// Create a new column metadata with the given name and data type.
213    ///
214    /// All constraints default to `false`, and `default` is `None`.
215    pub fn new(name: impl Into<String>, data_type: ResolvedType) -> Self {
216        Self {
217            name: name.into(),
218            data_type,
219            not_null: false,
220            primary_key: false,
221            unique: false,
222            default: None,
223        }
224    }
225
226    /// Set the NOT NULL constraint.
227    pub fn with_not_null(mut self, not_null: bool) -> Self {
228        self.not_null = not_null;
229        self
230    }
231
232    /// Set the PRIMARY KEY constraint.
233    pub fn with_primary_key(mut self, primary_key: bool) -> Self {
234        self.primary_key = primary_key;
235        self
236    }
237
238    /// Set the UNIQUE constraint.
239    pub fn with_unique(mut self, unique: bool) -> Self {
240        self.unique = unique;
241        self
242    }
243
244    /// Set the DEFAULT value.
245    pub fn with_default(mut self, default: Expr) -> Self {
246        self.default = Some(default);
247        self
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn test_table_metadata_new() {
257        let table = TableMetadata::new("users", vec![]);
258        assert_eq!(table.table_id, 0);
259        assert_eq!(table.name, "users");
260        assert!(table.columns.is_empty());
261        assert!(table.primary_key.is_none());
262    }
263
264    #[test]
265    fn test_table_metadata_with_table_id() {
266        let table = TableMetadata::new("users", vec![]).with_table_id(42);
267        assert_eq!(table.table_id, 42);
268        assert_eq!(table.name, "users");
269    }
270
271    #[test]
272    fn test_table_metadata_with_columns() {
273        let columns = vec![
274            ColumnMetadata::new("id", ResolvedType::Integer),
275            ColumnMetadata::new("name", ResolvedType::Text),
276        ];
277        let table = TableMetadata::new("users", columns);
278
279        assert_eq!(table.columns.len(), 2);
280        assert_eq!(table.columns[0].name, "id");
281        assert_eq!(table.columns[1].name, "name");
282    }
283
284    #[test]
285    fn test_table_metadata_with_primary_key() {
286        let table = TableMetadata::new("users", vec![])
287            .with_primary_key(vec!["id".to_string(), "tenant_id".to_string()]);
288
289        assert_eq!(
290            table.primary_key,
291            Some(vec!["id".to_string(), "tenant_id".to_string()])
292        );
293    }
294
295    #[test]
296    fn test_get_column() {
297        let columns = vec![
298            ColumnMetadata::new("id", ResolvedType::Integer),
299            ColumnMetadata::new("name", ResolvedType::Text),
300        ];
301        let table = TableMetadata::new("users", columns);
302
303        let id_col = table.get_column("id");
304        assert!(id_col.is_some());
305        assert_eq!(id_col.unwrap().name, "id");
306        assert_eq!(id_col.unwrap().data_type, ResolvedType::Integer);
307
308        let name_col = table.get_column("name");
309        assert!(name_col.is_some());
310        assert_eq!(name_col.unwrap().data_type, ResolvedType::Text);
311
312        assert!(table.get_column("unknown").is_none());
313    }
314
315    #[test]
316    fn test_get_column_index() {
317        let columns = vec![
318            ColumnMetadata::new("id", ResolvedType::Integer),
319            ColumnMetadata::new("name", ResolvedType::Text),
320            ColumnMetadata::new("age", ResolvedType::Integer),
321        ];
322        let table = TableMetadata::new("users", columns);
323
324        assert_eq!(table.get_column_index("id"), Some(0));
325        assert_eq!(table.get_column_index("name"), Some(1));
326        assert_eq!(table.get_column_index("age"), Some(2));
327        assert_eq!(table.get_column_index("unknown"), None);
328    }
329
330    #[test]
331    fn test_column_names() {
332        let columns = vec![
333            ColumnMetadata::new("id", ResolvedType::Integer),
334            ColumnMetadata::new("name", ResolvedType::Text),
335            ColumnMetadata::new("age", ResolvedType::Integer),
336        ];
337        let table = TableMetadata::new("users", columns);
338
339        assert_eq!(table.column_names(), vec!["id", "name", "age"]);
340    }
341
342    #[test]
343    fn test_column_count() {
344        let table = TableMetadata::new(
345            "users",
346            vec![
347                ColumnMetadata::new("id", ResolvedType::Integer),
348                ColumnMetadata::new("name", ResolvedType::Text),
349            ],
350        );
351        assert_eq!(table.column_count(), 2);
352    }
353
354    #[test]
355    fn test_column_metadata_new() {
356        let column = ColumnMetadata::new("id", ResolvedType::Integer);
357
358        assert_eq!(column.name, "id");
359        assert_eq!(column.data_type, ResolvedType::Integer);
360        assert!(!column.not_null);
361        assert!(!column.primary_key);
362        assert!(!column.unique);
363        assert!(column.default.is_none());
364    }
365
366    #[test]
367    fn test_column_metadata_constraints() {
368        let column = ColumnMetadata::new("id", ResolvedType::Integer)
369            .with_not_null(true)
370            .with_primary_key(true)
371            .with_unique(true);
372
373        assert!(column.not_null);
374        assert!(column.primary_key);
375        assert!(column.unique);
376    }
377
378    #[test]
379    fn test_storage_options_default() {
380        let options = StorageOptions::default();
381        assert_eq!(options.storage_type, StorageType::Row);
382        assert_eq!(options.compression, Compression::Lz4);
383        assert_eq!(options.row_group_size, 100_000);
384        assert_eq!(options.row_id_mode, RowIdMode::Direct);
385    }
386
387    #[test]
388    fn test_table_metadata_sets_default_storage_options() {
389        let table = TableMetadata::new("users", vec![]);
390        assert_eq!(table.storage_options, StorageOptions::default());
391    }
392}