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}