vibesql_ast/ddl/
schema.rs

1//! Schema, catalog, and database object DDL operations
2//!
3//! This module contains AST nodes for:
4//! - CREATE/DROP SCHEMA
5//! - CREATE/DROP VIEW
6//! - CREATE/DROP INDEX
7//! - CREATE/DROP ROLE
8//! - SET SCHEMA/CATALOG/NAMES/TIME ZONE
9//! - CREATE/DROP TRIGGER
10
11use crate::Expression;
12
13/// CREATE SCHEMA statement
14#[derive(Debug, Clone, PartialEq)]
15pub struct CreateSchemaStmt {
16    pub schema_name: String,
17    pub if_not_exists: bool,
18    pub schema_elements: Vec<SchemaElement>,
19}
20
21/// Schema element that can be included in CREATE SCHEMA
22#[derive(Debug, Clone, PartialEq)]
23pub enum SchemaElement {
24    CreateTable(super::table::CreateTableStmt),
25    // Future: CreateView, Grant, etc.
26}
27
28/// DROP SCHEMA statement
29#[derive(Debug, Clone, PartialEq)]
30pub struct DropSchemaStmt {
31    pub schema_name: String,
32    pub if_exists: bool,
33    pub cascade: bool,
34}
35
36/// SET SCHEMA statement
37#[derive(Debug, Clone, PartialEq)]
38pub struct SetSchemaStmt {
39    pub schema_name: String,
40}
41
42/// SET CATALOG statement
43#[derive(Debug, Clone, PartialEq)]
44pub struct SetCatalogStmt {
45    pub catalog_name: String,
46}
47
48/// SET NAMES statement
49#[derive(Debug, Clone, PartialEq)]
50pub struct SetNamesStmt {
51    pub charset_name: String,
52    pub collation: Option<String>,
53}
54
55/// SET TIME ZONE statement
56#[derive(Debug, Clone, PartialEq)]
57pub struct SetTimeZoneStmt {
58    pub zone: TimeZoneSpec,
59}
60
61/// Time zone specification for SET TIME ZONE
62#[derive(Debug, Clone, PartialEq)]
63pub enum TimeZoneSpec {
64    Local,
65    Interval(String), // e.g., "+05:00"
66}
67
68/// SET variable statement (MySQL/PostgreSQL extension)
69/// Handles: SET [GLOBAL | SESSION] variable_name = value
70#[derive(Debug, Clone, PartialEq)]
71pub struct SetVariableStmt {
72    pub scope: VariableScope,
73    pub variable: String,
74    pub value: Expression,
75}
76
77/// Variable scope for SET statements
78#[derive(Debug, Clone, PartialEq)]
79pub enum VariableScope {
80    Session, // Default or explicit SESSION
81    Global,  // GLOBAL keyword
82}
83
84/// CREATE ROLE statement
85#[derive(Debug, Clone, PartialEq)]
86pub struct CreateRoleStmt {
87    pub role_name: String,
88}
89
90/// DROP ROLE statement
91#[derive(Debug, Clone, PartialEq)]
92pub struct DropRoleStmt {
93    pub role_name: String,
94}
95
96/// CREATE VIEW statement
97#[derive(Debug, Clone, PartialEq)]
98pub struct CreateViewStmt {
99    pub view_name: String,
100    pub columns: Option<Vec<String>>,
101    pub query: Box<crate::SelectStmt>,
102    pub with_check_option: bool,
103    pub or_replace: bool,
104    /// Whether to skip creation if view already exists (CREATE VIEW IF NOT EXISTS)
105    pub if_not_exists: bool,
106    /// Whether this is a temporary view (CREATE TEMP VIEW or CREATE TEMPORARY VIEW)
107    pub temporary: bool,
108    /// Original SQL definition for sqlite_master compatibility
109    /// This is populated during parsing to preserve the exact SQL text
110    pub sql_definition: Option<String>,
111}
112
113/// DROP VIEW statement
114#[derive(Debug, Clone, PartialEq)]
115pub struct DropViewStmt {
116    pub view_name: String,
117    pub if_exists: bool,
118    pub cascade: bool,
119    /// Whether RESTRICT was explicitly specified.
120    /// When neither CASCADE nor RESTRICT is specified, we use SQLite-compatible
121    /// behavior (allow dropping views even if dependents exist).
122    /// When RESTRICT is explicit, we enforce dependency checks.
123    pub restrict: bool,
124}
125
126/// CREATE TRIGGER statement
127#[derive(Debug, Clone, PartialEq)]
128pub struct CreateTriggerStmt {
129    pub trigger_name: String,
130    pub timing: TriggerTiming,
131    pub event: TriggerEvent,
132    pub table_name: String,
133    pub granularity: TriggerGranularity,
134    pub when_condition: Option<Box<Expression>>,
135    pub triggered_action: TriggerAction,
136}
137
138/// Trigger timing: BEFORE | AFTER | INSTEAD OF
139#[derive(Debug, Clone, PartialEq)]
140pub enum TriggerTiming {
141    Before,
142    After,
143    InsteadOf,
144}
145
146/// Trigger event: INSERT | UPDATE | DELETE
147#[derive(Debug, Clone, PartialEq)]
148pub enum TriggerEvent {
149    Insert,
150    Update(Option<Vec<String>>), // Optional column list for UPDATE OF
151    Delete,
152}
153
154/// Trigger granularity: FOR EACH ROW | FOR EACH STATEMENT
155#[derive(Debug, Clone, PartialEq)]
156pub enum TriggerGranularity {
157    Row,       // FOR EACH ROW
158    Statement, // FOR EACH STATEMENT (default in SQL:1999)
159}
160
161/// Triggered action (procedural SQL)
162#[derive(Debug, Clone, PartialEq)]
163pub enum TriggerAction {
164    /// For initial implementation, store raw SQL
165    RawSql(String),
166    // Future: Add procedural SQL statement support
167    // Statements(Vec<Statement>),
168}
169
170/// DROP TRIGGER statement
171#[derive(Debug, Clone, PartialEq)]
172pub struct DropTriggerStmt {
173    pub trigger_name: String,
174    pub cascade: bool,
175}
176
177/// ALTER TRIGGER statement
178#[derive(Debug, Clone, PartialEq)]
179pub struct AlterTriggerStmt {
180    pub trigger_name: String,
181    pub action: AlterTriggerAction,
182}
183
184/// ALTER TRIGGER action
185#[derive(Debug, Clone, PartialEq)]
186pub enum AlterTriggerAction {
187    Enable,
188    Disable,
189}
190
191/// CREATE INDEX statement
192#[derive(Debug, Clone, PartialEq)]
193pub struct CreateIndexStmt {
194    pub if_not_exists: bool,
195    pub index_name: String,
196    pub table_name: String,
197    pub index_type: IndexType,
198    pub columns: Vec<IndexColumn>,
199}
200
201/// Index type specification
202#[derive(Debug, Clone, PartialEq)]
203pub enum IndexType {
204    /// Standard B-tree index (default)
205    BTree { unique: bool },
206    /// FULLTEXT index for full-text search
207    Fulltext,
208    /// SPATIAL index for spatial/geometric data (R-tree)
209    Spatial,
210    /// IVFFlat index for approximate nearest neighbor search on vectors
211    IVFFlat {
212        /// Distance metric to use for similarity calculations
213        metric: VectorDistanceMetric,
214        /// Number of clusters/lists for partitioning (default: 100)
215        lists: u32,
216    },
217    /// HNSW index for high-performance approximate nearest neighbor search
218    Hnsw {
219        /// Distance metric to use for similarity calculations
220        metric: VectorDistanceMetric,
221        /// Maximum number of connections per node (default: 16)
222        m: u32,
223        /// Size of dynamic candidate list during construction (default: 64)
224        ef_construction: u32,
225    },
226}
227
228/// Distance metric for vector index operations
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum VectorDistanceMetric {
231    /// Euclidean distance (L2 norm)
232    L2,
233    /// Cosine similarity (1 - cosine distance)
234    Cosine,
235    /// Inner product (dot product) - negative for similarity
236    InnerProduct,
237}
238
239/// Index column specification - can be either a simple column reference or an expression
240#[derive(Debug, Clone, PartialEq)]
241pub enum IndexColumn {
242    /// Simple column reference with optional prefix length
243    Column {
244        column_name: String,
245        direction: crate::select::OrderDirection,
246        /// Optional prefix length for indexed columns (MySQL/SQLite feature)
247        /// Example: UNIQUE (name(10)) creates index on first 10 characters of 'name'
248        prefix_length: Option<u64>,
249    },
250    /// Expression index (functional index)
251    /// Example: CREATE INDEX idx ON t(lower(name)) or CREATE INDEX idx ON t(a + b)
252    Expression { expr: Box<Expression>, direction: crate::select::OrderDirection },
253}
254
255impl IndexColumn {
256    /// Create a new simple column index
257    pub fn new_column(column_name: String, direction: crate::select::OrderDirection) -> Self {
258        IndexColumn::Column { column_name, direction, prefix_length: None }
259    }
260
261    /// Create a new column index with prefix length
262    pub fn new_column_with_prefix(
263        column_name: String,
264        direction: crate::select::OrderDirection,
265        prefix_length: u64,
266    ) -> Self {
267        IndexColumn::Column { column_name, direction, prefix_length: Some(prefix_length) }
268    }
269
270    /// Create a new expression index
271    pub fn new_expression(expr: Expression, direction: crate::select::OrderDirection) -> Self {
272        IndexColumn::Expression { expr: Box::new(expr), direction }
273    }
274
275    /// Get the column name if this is a simple column reference
276    pub fn column_name(&self) -> Option<&str> {
277        match self {
278            IndexColumn::Column { column_name, .. } => Some(column_name),
279            IndexColumn::Expression { .. } => None,
280        }
281    }
282
283    /// Get the column name, panicking if this is an expression index.
284    /// Use this in code paths that don't support expression indexes yet.
285    pub fn expect_column_name(&self) -> &str {
286        match self {
287            IndexColumn::Column { column_name, .. } => column_name,
288            IndexColumn::Expression { .. } => {
289                panic!("Expression indexes are not supported in this context")
290            }
291        }
292    }
293
294    /// Get the direction of this index column
295    pub fn direction(&self) -> crate::select::OrderDirection {
296        match self {
297            IndexColumn::Column { direction, .. } => direction.clone(),
298            IndexColumn::Expression { direction, .. } => direction.clone(),
299        }
300    }
301
302    /// Get the prefix length if this is a column with prefix
303    pub fn prefix_length(&self) -> Option<u64> {
304        match self {
305            IndexColumn::Column { prefix_length, .. } => *prefix_length,
306            IndexColumn::Expression { .. } => None,
307        }
308    }
309
310    /// Check if this is an expression index
311    pub fn is_expression(&self) -> bool {
312        matches!(self, IndexColumn::Expression { .. })
313    }
314
315    /// Get the expression if this is an expression index
316    pub fn get_expression(&self) -> Option<&Expression> {
317        match self {
318            IndexColumn::Expression { expr, .. } => Some(expr),
319            IndexColumn::Column { .. } => None,
320        }
321    }
322}
323
324/// DROP INDEX statement
325#[derive(Debug, Clone, PartialEq)]
326pub struct DropIndexStmt {
327    pub if_exists: bool,
328    pub index_name: String,
329}
330
331/// REINDEX statement
332///
333/// Rebuilds indexes to reclaim space or improve query performance.
334/// Syntax: REINDEX [database_name | table_name | index_name]
335#[derive(Debug, Clone, PartialEq)]
336pub struct ReindexStmt {
337    /// Optional target: database name, table name, or index name
338    pub target: Option<String>,
339}
340
341/// ANALYZE statement
342///
343/// Computes table and column statistics to improve query plan optimization.
344/// Syntax:
345/// - ANALYZE;                          -- All tables
346/// - ANALYZE table_name;               -- Specific table
347/// - ANALYZE table_name (col1, col2);  -- Specific columns
348#[derive(Debug, Clone, PartialEq)]
349pub struct AnalyzeStmt {
350    /// Optional table name
351    pub table_name: Option<String>,
352    /// Optional column names (only valid when table_name is specified)
353    pub columns: Option<Vec<String>>,
354}
355
356/// PRAGMA statement
357///
358/// SQLite-specific statement for database configuration and introspection.
359/// Currently parsed but treated as a no-op for SQLite compatibility.
360///
361/// Syntax variations:
362/// - PRAGMA pragma_name;                 -- Query pragma value
363/// - PRAGMA pragma_name = value;         -- Set pragma value
364/// - PRAGMA pragma_name(value);          -- Set pragma value (function syntax)
365/// - PRAGMA database.pragma_name;        -- Database-qualified pragma
366/// - PRAGMA database.pragma_name = value;
367#[derive(Debug, Clone, PartialEq)]
368pub struct PragmaStmt {
369    /// Optional database/schema name (before the dot)
370    pub database: Option<String>,
371    /// The pragma name
372    pub name: String,
373    /// Optional pragma value (from = value or (value) syntax)
374    pub value: Option<PragmaValue>,
375}
376
377/// Value for a PRAGMA statement
378#[derive(Debug, Clone, PartialEq)]
379pub enum PragmaValue {
380    /// Identifier value (ON, OFF, FULL, etc.)
381    Identifier(String),
382    /// String literal value
383    String(String),
384    /// Numeric value (integer or float)
385    Number(String),
386    /// Signed number (negative numbers)
387    SignedNumber(String),
388}