vibesql_ast/ddl/
table.rs

1//! Table DDL operations
2//!
3//! This module contains AST nodes for table-related DDL operations:
4//! - CREATE TABLE
5//! - DROP TABLE
6//! - ALTER TABLE (add/drop column, add/drop constraint, etc.)
7
8use vibesql_types::DataType;
9
10use crate::Expression;
11
12/// Referential action for foreign key constraints
13#[derive(Debug, Clone, PartialEq)]
14pub enum ReferentialAction {
15    NoAction,
16    Restrict,
17    Cascade,
18    SetNull,
19    SetDefault,
20}
21
22/// Constraint deferral mode for foreign key constraints (SQL:1999)
23///
24/// Syntax: `[NOT] DEFERRABLE [INITIALLY {DEFERRED | IMMEDIATE}]`
25///
26/// When a constraint is DEFERRABLE, its enforcement can be deferred until
27/// the end of a transaction (with SET CONSTRAINTS DEFERRED). Non-deferrable
28/// constraints are always checked immediately.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
30pub struct ConstraintDeferral {
31    /// Whether the constraint can be deferred.
32    /// Default is NOT DEFERRABLE (false).
33    pub is_deferrable: bool,
34    /// When deferrable, whether it starts in deferred mode.
35    /// Only meaningful when `is_deferrable` is true.
36    /// INITIALLY DEFERRED (true) or INITIALLY IMMEDIATE (false, default).
37    pub initially_deferred: bool,
38}
39
40/// Storage format for tables
41///
42/// Tables can be stored in row-oriented (default) or columnar format.
43/// Columnar storage is optimized for analytical queries (OLAP) with
44/// SIMD-accelerated scans and aggregations.
45///
46/// # Usage
47///
48/// ```sql
49/// -- Create a columnar table for analytics
50/// CREATE TABLE lineitem (...) STORAGE COLUMNAR;
51///
52/// -- Explicitly create a row-oriented table
53/// CREATE TABLE orders (...) STORAGE ROW;
54/// ```
55///
56/// # Performance Trade-offs
57///
58/// | Format   | INSERT | Point Query | Scan       | Aggregation |
59/// |----------|--------|-------------|------------|-------------|
60/// | Row      | O(1)   | O(1) index  | O(n)       | O(n)        |
61/// | Columnar | O(n)*  | O(n)        | O(n) SIMD  | O(n) SIMD   |
62///
63/// *Columnar INSERT triggers full rebuild - use for bulk-load scenarios only.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
65pub enum StorageFormat {
66    /// Traditional row-oriented storage (default)
67    ///
68    /// Optimized for OLTP workloads: fast inserts, point lookups.
69    /// Use for tables with frequent writes or transactional access patterns.
70    #[default]
71    Row,
72    /// Native columnar storage for analytical tables
73    ///
74    /// Optimized for OLAP workloads: fast scans, aggregations.
75    /// Eliminates row-to-columnar conversion overhead for analytical queries.
76    ///
77    /// **Warning**: Each write operation (INSERT/UPDATE/DELETE) triggers a
78    /// full rebuild of the columnar representation. Only use for tables that
79    /// are bulk-loaded and rarely modified.
80    Columnar,
81}
82
83impl std::fmt::Display for StorageFormat {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        match self {
86            StorageFormat::Row => write!(f, "row"),
87            StorageFormat::Columnar => write!(f, "columnar"),
88        }
89    }
90}
91
92/// MySQL table options for CREATE TABLE
93#[derive(Debug, Clone, PartialEq)]
94pub enum TableOption {
95    /// KEY_BLOCK_SIZE [=] value
96    KeyBlockSize(Option<i64>),
97    /// CONNECTION [=] 'string'
98    Connection(Option<String>),
99    /// INSERT_METHOD = {FIRST | LAST | NO}
100    InsertMethod(InsertMethod),
101    /// UNION [=] (col1, col2, ...)
102    Union(Option<Vec<String>>),
103    /// ROW_FORMAT [=] {DEFAULT | DYNAMIC | FIXED | COMPRESSED | REDUNDANT | COMPACT}
104    RowFormat(Option<RowFormat>),
105    /// DELAY_KEY_WRITE [=] value
106    DelayKeyWrite(Option<i64>),
107    /// TABLE_CHECKSUM [=] value | CHECKSUM [=] value
108    TableChecksum(Option<i64>),
109    /// STATS_SAMPLE_PAGES [=] value
110    StatsSamplePages(Option<i64>),
111    /// PASSWORD [=] 'string'
112    Password(Option<String>),
113    /// AVG_ROW_LENGTH [=] value
114    AvgRowLength(Option<i64>),
115    /// MIN_ROWS [=] value
116    MinRows(Option<i64>),
117    /// MAX_ROWS [=] value
118    MaxRows(Option<i64>),
119    /// SECONDARY_ENGINE [=] identifier | NULL
120    SecondaryEngine(Option<String>),
121    /// COLLATE [=] collation_name
122    Collate(Option<String>),
123    /// COMMENT [=] 'string'
124    Comment(Option<String>),
125    /// STORAGE [=] {ROW | COLUMNAR}
126    /// VibeSQL extension for native columnar storage
127    Storage(StorageFormat),
128}
129
130/// MySQL INSERT_METHOD values
131#[derive(Debug, Clone, PartialEq)]
132pub enum InsertMethod {
133    First,
134    Last,
135    No,
136}
137
138/// MySQL ROW_FORMAT values
139#[derive(Debug, Clone, PartialEq)]
140pub enum RowFormat {
141    Default,
142    Dynamic,
143    Fixed,
144    Compressed,
145    Redundant,
146    Compact,
147}
148
149/// CREATE TABLE statement
150#[derive(Debug, Clone, PartialEq)]
151pub struct CreateTableStmt {
152    /// If true, this is a temporary table (CREATE TEMP TABLE)
153    /// Temporary tables are stored in a separate "temp" schema and are not persisted.
154    pub temporary: bool,
155    /// If true, don't error if the table already exists
156    pub if_not_exists: bool,
157    /// Table name (possibly qualified as schema.table)
158    pub table_name: String,
159    pub columns: Vec<ColumnDef>,
160    pub table_constraints: Vec<TableConstraint>,
161    pub table_options: Vec<TableOption>,
162    /// Whether the table name was quoted (delimited) in the original SQL.
163    pub quoted: bool,
164    /// Optional AS SELECT query for CREATE TABLE ... AS SELECT syntax
165    /// When present, columns are derived from the query result
166    pub as_query: Option<Box<crate::SelectStmt>>,
167    /// If true, table was created with WITHOUT ROWID clause (SQLite compatibility).
168    /// WITHOUT ROWID tables have no implicit rowid column and last_insert_rowid()
169    /// is not updated when inserting into them.
170    pub without_rowid: bool,
171}
172
173/// Column definition
174#[derive(Debug, Clone, PartialEq)]
175pub struct ColumnDef {
176    pub name: String,
177    pub data_type: DataType,
178    pub nullable: bool,
179    pub constraints: Vec<ColumnConstraint>,
180    pub default_value: Option<Box<Expression>>,
181    pub comment: Option<String>,
182    /// Generated/computed column expression (AS(expression) syntax)
183    /// When present, this column's value is computed from the expression
184    /// rather than being stored explicitly
185    pub generated_expr: Option<Box<Expression>>,
186    /// SQLite rowid alias eligibility flag.
187    /// True only when the original type declaration was exactly "INTEGER" (case-insensitive).
188    /// In SQLite, only `INTEGER PRIMARY KEY` is a rowid alias, not `INT PRIMARY KEY`.
189    /// This distinguishes between INT and INTEGER which both parse to DataType::Integer.
190    pub is_exact_integer_type: bool,
191}
192
193/// Column-level constraint
194#[derive(Debug, Clone, PartialEq)]
195pub struct ColumnConstraint {
196    pub name: Option<String>,
197    pub kind: ColumnConstraintKind,
198}
199
200/// Column constraint types
201#[derive(Debug, Clone, PartialEq)]
202pub enum ColumnConstraintKind {
203    NotNull,
204    /// PRIMARY KEY constraint with optional conflict resolution
205    PrimaryKey {
206        /// Optional conflict resolution clause (SQLite extension)
207        /// Syntax: PRIMARY KEY ON CONFLICT ROLLBACK|ABORT|FAIL|IGNORE|REPLACE
208        on_conflict: Option<crate::ConflictClause>,
209    },
210    /// UNIQUE constraint with optional conflict resolution
211    Unique {
212        /// Optional conflict resolution clause (SQLite extension)
213        /// Syntax: UNIQUE ON CONFLICT ROLLBACK|ABORT|FAIL|IGNORE|REPLACE
214        on_conflict: Option<crate::ConflictClause>,
215    },
216    /// NOT NULL constraint with optional conflict resolution
217    NotNullWithConflict {
218        /// Optional conflict resolution clause (SQLite extension)
219        /// Syntax: NOT NULL ON CONFLICT ROLLBACK|ABORT|FAIL|IGNORE|REPLACE
220        on_conflict: Option<crate::ConflictClause>,
221    },
222    Check(Box<Expression>),
223    References {
224        table: String,
225        /// Column in the referenced table. If None, defaults to the primary key.
226        column: Option<String>,
227        on_delete: Option<ReferentialAction>,
228        on_update: Option<ReferentialAction>,
229        /// Constraint deferral mode (DEFERRABLE INITIALLY DEFERRED, etc.)
230        deferral: Option<ConstraintDeferral>,
231    },
232    /// AUTO_INCREMENT (MySQL) or AUTOINCREMENT (SQLite)
233    /// Automatically generates sequential integer values for new rows
234    AutoIncrement,
235    /// KEY (MySQL-specific)
236    /// Creates an index on the column
237    Key,
238    /// COLLATE clause (SQLite/MySQL)
239    /// Specifies the collation for the column
240    Collate(String),
241}
242
243/// Table-level constraint
244#[derive(Debug, Clone, PartialEq)]
245pub struct TableConstraint {
246    pub name: Option<String>,
247    pub kind: TableConstraintKind,
248}
249
250/// Table constraint types
251#[derive(Debug, Clone, PartialEq)]
252pub enum TableConstraintKind {
253    PrimaryKey {
254        columns: Vec<crate::IndexColumn>,
255        /// Optional conflict resolution clause (SQLite extension)
256        /// Syntax: PRIMARY KEY (columns) ON CONFLICT ROLLBACK|ABORT|FAIL|IGNORE|REPLACE
257        on_conflict: Option<crate::ConflictClause>,
258    },
259    ForeignKey {
260        columns: Vec<String>,
261        references_table: String,
262        references_columns: Vec<String>,
263        on_delete: Option<ReferentialAction>,
264        on_update: Option<ReferentialAction>,
265        /// Constraint deferral mode (DEFERRABLE INITIALLY DEFERRED, etc.)
266        deferral: Option<ConstraintDeferral>,
267    },
268    Unique {
269        columns: Vec<crate::IndexColumn>,
270        /// Optional conflict resolution clause (SQLite extension)
271        /// Syntax: UNIQUE (columns) ON CONFLICT ROLLBACK|ABORT|FAIL|IGNORE|REPLACE
272        on_conflict: Option<crate::ConflictClause>,
273    },
274    Check {
275        expr: Box<Expression>,
276    },
277    /// FULLTEXT index constraint
278    /// Example: FULLTEXT INDEX ft_search (title, body)
279    Fulltext {
280        index_name: Option<String>,
281        columns: Vec<crate::IndexColumn>,
282    },
283}
284
285/// DROP TABLE statement
286#[derive(Debug, Clone, PartialEq)]
287pub struct DropTableStmt {
288    /// Table name (possibly qualified as schema.table)
289    pub table_name: String,
290    pub if_exists: bool,
291    /// Whether the table name was quoted (delimited) in the original SQL.
292    pub quoted: bool,
293}
294
295/// CASCADE option for TRUNCATE TABLE
296#[derive(Debug, Clone, PartialEq)]
297pub enum TruncateCascadeOption {
298    /// CASCADE - recursively truncate dependent tables
299    Cascade,
300    /// RESTRICT - fail if foreign key references exist (default)
301    Restrict,
302}
303
304/// TRUNCATE TABLE statement
305#[derive(Debug, Clone, PartialEq)]
306pub struct TruncateTableStmt {
307    pub table_names: Vec<String>,
308    pub if_exists: bool,
309    /// CASCADE/RESTRICT option (None = default to RESTRICT)
310    pub cascade: Option<TruncateCascadeOption>,
311}
312
313/// ALTER TABLE statement
314#[derive(Debug, Clone, PartialEq)]
315pub enum AlterTableStmt {
316    AddColumn(AddColumnStmt),
317    DropColumn(DropColumnStmt),
318    AlterColumn(AlterColumnStmt),
319    AddConstraint(AddConstraintStmt),
320    DropConstraint(DropConstraintStmt),
321    RenameTable(RenameTableStmt),
322    ModifyColumn(ModifyColumnStmt),
323    ChangeColumn(ChangeColumnStmt),
324}
325
326/// ADD COLUMN operation
327#[derive(Debug, Clone, PartialEq)]
328pub struct AddColumnStmt {
329    pub table_name: String,
330    pub column_def: ColumnDef,
331}
332
333/// DROP COLUMN operation
334#[derive(Debug, Clone, PartialEq)]
335pub struct DropColumnStmt {
336    pub table_name: String,
337    pub column_name: String,
338    pub if_exists: bool,
339}
340
341/// ALTER COLUMN operation
342#[derive(Debug, Clone, PartialEq)]
343pub enum AlterColumnStmt {
344    SetDefault { table_name: String, column_name: String, default: Expression },
345    DropDefault { table_name: String, column_name: String },
346    SetNotNull { table_name: String, column_name: String },
347    DropNotNull { table_name: String, column_name: String },
348}
349
350/// ADD CONSTRAINT operation
351#[derive(Debug, Clone, PartialEq)]
352pub struct AddConstraintStmt {
353    pub table_name: String,
354    pub constraint: TableConstraint,
355}
356
357/// DROP CONSTRAINT operation
358#[derive(Debug, Clone, PartialEq)]
359pub struct DropConstraintStmt {
360    pub table_name: String,
361    pub constraint_name: String,
362}
363
364/// RENAME TABLE operation
365#[derive(Debug, Clone, PartialEq)]
366pub struct RenameTableStmt {
367    pub table_name: String,
368    pub new_table_name: String,
369}
370
371/// MODIFY COLUMN operation (MySQL-style)
372#[derive(Debug, Clone, PartialEq)]
373pub struct ModifyColumnStmt {
374    pub table_name: String,
375    pub column_name: String,
376    pub new_column_def: ColumnDef,
377}
378
379/// CHANGE COLUMN operation (MySQL-style - rename and modify)
380#[derive(Debug, Clone, PartialEq)]
381pub struct ChangeColumnStmt {
382    pub table_name: String,
383    pub old_column_name: String,
384    pub new_column_def: ColumnDef,
385}