Skip to main content

qcraft_core/ast/
ddl.rs

1use super::common::{NullsOrder, OrderDir, SchemaRef};
2use super::conditions::Conditions;
3use super::custom::{CustomConstraint, CustomFieldType, CustomSchemaMutation};
4use super::expr::Expr;
5
6// ---------------------------------------------------------------------------
7// Schema mutation statements (DDL)
8// ---------------------------------------------------------------------------
9
10/// All DDL operations.
11#[derive(Debug, Clone)]
12pub enum SchemaMutationStmt {
13    // ── Table operations ──
14    CreateTable {
15        schema: SchemaDef,
16        if_not_exists: bool,
17        temporary: bool,
18        unlogged: bool,
19        tablespace: Option<String>,
20        partition_by: Option<PartitionByDef>,
21        inherits: Option<Vec<SchemaRef>>,
22        using_method: Option<String>,
23        with_options: Option<Vec<(String, String)>>,
24        on_commit: Option<OnCommitAction>,
25        /// Generic table options (MySQL ENGINE, ROW_FORMAT, etc.).
26        table_options: Option<Vec<(String, String)>>,
27        /// SQLite WITHOUT ROWID.
28        without_rowid: bool,
29        /// SQLite STRICT mode.
30        strict: bool,
31    },
32    DropTable {
33        schema_ref: SchemaRef,
34        if_exists: bool,
35        cascade: bool,
36    },
37    RenameTable {
38        schema_ref: SchemaRef,
39        new_name: String,
40    },
41    TruncateTable {
42        schema_ref: SchemaRef,
43        restart_identity: bool,
44        cascade: bool,
45    },
46
47    // ── Column operations ──
48    AddColumn {
49        schema_ref: SchemaRef,
50        column: Box<ColumnDef>,
51        if_not_exists: bool,
52        position: Option<ColumnPosition>,
53    },
54    DropColumn {
55        schema_ref: SchemaRef,
56        name: String,
57        if_exists: bool,
58        cascade: bool,
59    },
60    RenameColumn {
61        schema_ref: SchemaRef,
62        old_name: String,
63        new_name: String,
64    },
65    AlterColumnType {
66        schema_ref: SchemaRef,
67        column_name: String,
68        new_type: FieldType,
69        using_expr: Option<Expr>,
70    },
71    AlterColumnDefault {
72        schema_ref: SchemaRef,
73        column_name: String,
74        default: Option<Expr>,
75    },
76    AlterColumnNullability {
77        schema_ref: SchemaRef,
78        column_name: String,
79        not_null: bool,
80    },
81
82    // ── Constraint operations ──
83    AddConstraint {
84        schema_ref: SchemaRef,
85        constraint: ConstraintDef,
86        not_valid: bool,
87    },
88    DropConstraint {
89        schema_ref: SchemaRef,
90        constraint_name: String,
91        if_exists: bool,
92        cascade: bool,
93    },
94    RenameConstraint {
95        schema_ref: SchemaRef,
96        old_name: String,
97        new_name: String,
98    },
99    ValidateConstraint {
100        schema_ref: SchemaRef,
101        constraint_name: String,
102    },
103
104    // ── Index operations ──
105    CreateIndex {
106        schema_ref: SchemaRef,
107        index: IndexDef,
108        if_not_exists: bool,
109        concurrently: bool,
110    },
111    DropIndex {
112        schema_ref: SchemaRef,
113        index_name: String,
114        if_exists: bool,
115        concurrently: bool,
116        cascade: bool,
117    },
118
119    // ── Extension operations (PostgreSQL) ──
120    CreateExtension {
121        name: String,
122        if_not_exists: bool,
123        schema: Option<String>,
124        version: Option<String>,
125        cascade: bool,
126    },
127    DropExtension {
128        name: String,
129        if_exists: bool,
130        cascade: bool,
131    },
132
133    // ── Collation operations (PostgreSQL) ──
134    CreateCollation {
135        name: String,
136        if_not_exists: bool,
137        locale: Option<String>,
138        lc_collate: Option<String>,
139        lc_ctype: Option<String>,
140        provider: Option<String>,
141        deterministic: Option<bool>,
142        from_collation: Option<String>,
143    },
144    DropCollation {
145        name: String,
146        if_exists: bool,
147        cascade: bool,
148    },
149
150    /// User-defined DDL operation (extension point).
151    Custom(Box<dyn CustomSchemaMutation>),
152}
153
154impl SchemaMutationStmt {
155    pub fn create_table(schema: SchemaDef) -> Self {
156        Self::CreateTable {
157            schema,
158            if_not_exists: false,
159            temporary: false,
160            unlogged: false,
161            tablespace: None,
162            partition_by: None,
163            inherits: None,
164            using_method: None,
165            with_options: None,
166            on_commit: None,
167            table_options: None,
168            without_rowid: false,
169            strict: false,
170        }
171    }
172
173    pub fn drop_table(name: &str) -> Self {
174        Self::DropTable {
175            schema_ref: SchemaRef::new(name),
176            if_exists: false,
177            cascade: false,
178        }
179    }
180
181    pub fn drop_table_if_exists(name: &str) -> Self {
182        Self::DropTable {
183            schema_ref: SchemaRef::new(name),
184            if_exists: true,
185            cascade: false,
186        }
187    }
188
189    pub fn create_index(table: &str, index: IndexDef) -> Self {
190        Self::CreateIndex {
191            schema_ref: SchemaRef::new(table),
192            index,
193            if_not_exists: false,
194            concurrently: false,
195        }
196    }
197
198    pub fn drop_index(table: &str, name: &str) -> Self {
199        Self::DropIndex {
200            schema_ref: SchemaRef::new(table),
201            index_name: name.to_string(),
202            if_exists: false,
203            concurrently: false,
204            cascade: false,
205        }
206    }
207
208    pub fn add_column(table: &str, column: ColumnDef) -> Self {
209        Self::AddColumn {
210            schema_ref: SchemaRef::new(table),
211            column: Box::new(column),
212            if_not_exists: false,
213            position: None,
214        }
215    }
216
217    pub fn drop_column(table: &str, name: &str) -> Self {
218        Self::DropColumn {
219            schema_ref: SchemaRef::new(table),
220            name: name.to_string(),
221            if_exists: false,
222            cascade: false,
223        }
224    }
225
226    pub fn rename_table(old: &str, new_name: &str) -> Self {
227        Self::RenameTable {
228            schema_ref: SchemaRef::new(old),
229            new_name: new_name.to_string(),
230        }
231    }
232
233    pub fn rename_column(table: &str, old: &str, new_name: &str) -> Self {
234        Self::RenameColumn {
235            schema_ref: SchemaRef::new(table),
236            old_name: old.to_string(),
237            new_name: new_name.to_string(),
238        }
239    }
240
241    pub fn truncate(table: &str) -> Self {
242        Self::TruncateTable {
243            schema_ref: SchemaRef::new(table),
244            restart_identity: false,
245            cascade: false,
246        }
247    }
248
249    pub fn create_collation(name: &str) -> Self {
250        Self::CreateCollation {
251            name: name.to_string(),
252            if_not_exists: false,
253            locale: None,
254            lc_collate: None,
255            lc_ctype: None,
256            provider: None,
257            deterministic: None,
258            from_collation: None,
259        }
260    }
261
262    pub fn drop_collation(name: &str) -> Self {
263        Self::DropCollation {
264            name: name.to_string(),
265            if_exists: false,
266            cascade: false,
267        }
268    }
269}
270
271// ---------------------------------------------------------------------------
272// Table / Schema definition
273// ---------------------------------------------------------------------------
274
275/// Complete table definition (for CREATE TABLE).
276#[derive(Debug, Clone)]
277pub struct SchemaDef {
278    pub name: String,
279    pub namespace: Option<String>,
280    pub columns: Vec<ColumnDef>,
281    pub constraints: Option<Vec<ConstraintDef>>,
282    pub indexes: Option<Vec<IndexDef>>,
283    pub like_tables: Option<Vec<LikeTableDef>>,
284}
285
286impl SchemaDef {
287    pub fn new(name: impl Into<String>) -> Self {
288        Self {
289            name: name.into(),
290            namespace: None,
291            columns: Vec::new(),
292            constraints: None,
293            indexes: None,
294            like_tables: None,
295        }
296    }
297}
298
299// ---------------------------------------------------------------------------
300// Column definition
301// ---------------------------------------------------------------------------
302
303/// A column in a table.
304#[derive(Debug, Clone)]
305pub struct ColumnDef {
306    pub name: String,
307    pub field_type: FieldType,
308    pub not_null: bool,
309    pub default: Option<Expr>,
310    pub generated: Option<GeneratedColumn>,
311    pub identity: Option<IdentityColumn>,
312    pub collation: Option<String>,
313    pub comment: Option<String>,
314    pub storage: Option<String>,
315    pub compression: Option<String>,
316}
317
318impl ColumnDef {
319    pub fn new(name: impl Into<String>, field_type: FieldType) -> Self {
320        Self {
321            name: name.into(),
322            field_type,
323            not_null: false,
324            default: None,
325            generated: None,
326            identity: None,
327            collation: None,
328            comment: None,
329            storage: None,
330            compression: None,
331        }
332    }
333
334    pub fn not_null(mut self) -> Self {
335        self.not_null = true;
336        self
337    }
338
339    pub fn default(mut self, expr: Expr) -> Self {
340        self.default = Some(expr);
341        self
342    }
343
344    pub fn generated(mut self, expr: Expr, stored: bool) -> Self {
345        self.generated = Some(GeneratedColumn { expr, stored });
346        self
347    }
348
349    pub fn collation(mut self, name: impl Into<String>) -> Self {
350        self.collation = Some(name.into());
351        self
352    }
353}
354
355/// Column type.
356#[derive(Debug, Clone)]
357pub enum FieldType {
358    /// Well-known scalar type: text, integer, bigint, boolean, float, double,
359    /// serial, bigserial, json, jsonb, uuid, timestamp, timestamptz,
360    /// bytea, numeric, date, time, interval, etc.
361    Scalar(String),
362
363    /// Custom type with optional parameters: `VARCHAR(255)`, `NUMERIC(10,2)`.
364    Parameterized { name: String, params: Vec<String> },
365
366    /// Array type: `INTEGER[]`, `TEXT[]`.
367    Array(Box<FieldType>),
368
369    /// Vector type (pgvector): `VECTOR(1536)`.
370    Vector(i64),
371
372    /// User-defined type (extension point).
373    Custom(Box<dyn CustomFieldType>),
374}
375
376impl FieldType {
377    pub fn scalar(name: impl Into<String>) -> Self {
378        Self::Scalar(name.into())
379    }
380
381    pub fn parameterized(name: impl Into<String>, params: Vec<impl Into<String>>) -> Self {
382        Self::Parameterized {
383            name: name.into(),
384            params: params.into_iter().map(Into::into).collect(),
385        }
386    }
387}
388
389/// Generated (computed) column.
390#[derive(Debug, Clone)]
391pub struct GeneratedColumn {
392    pub expr: Expr,
393    pub stored: bool,
394}
395
396/// Identity (auto-increment) column.
397#[derive(Debug, Clone, Default)]
398pub struct IdentityColumn {
399    pub always: bool,
400    pub start: Option<i64>,
401    pub increment: Option<i64>,
402    pub min_value: Option<i64>,
403    pub max_value: Option<i64>,
404    pub cycle: bool,
405    pub cache: Option<i64>,
406}
407
408/// Column position for ADD COLUMN (MySQL-specific: FIRST / AFTER).
409#[derive(Debug, Clone)]
410pub enum ColumnPosition {
411    First,
412    After(String),
413}
414
415// ---------------------------------------------------------------------------
416// Constraints
417// ---------------------------------------------------------------------------
418
419/// Table or column constraint.
420#[derive(Debug, Clone)]
421pub enum ConstraintDef {
422    PrimaryKey {
423        name: Option<String>,
424        columns: Vec<String>,
425        include: Option<Vec<String>>,
426    },
427
428    ForeignKey {
429        name: Option<String>,
430        columns: Vec<String>,
431        ref_table: SchemaRef,
432        ref_columns: Vec<String>,
433        on_delete: Option<ReferentialAction>,
434        on_update: Option<ReferentialAction>,
435        deferrable: Option<DeferrableConstraint>,
436        match_type: Option<MatchType>,
437    },
438
439    Unique {
440        name: Option<String>,
441        columns: Vec<String>,
442        include: Option<Vec<String>>,
443        nulls_distinct: Option<bool>,
444        condition: Option<Conditions>,
445    },
446
447    Check {
448        name: Option<String>,
449        condition: Conditions,
450        no_inherit: bool,
451        enforced: Option<bool>,
452    },
453
454    Exclusion {
455        name: Option<String>,
456        elements: Vec<ExclusionElement>,
457        index_method: String,
458        condition: Option<Conditions>,
459    },
460
461    /// User-defined constraint (extension point).
462    Custom(Box<dyn CustomConstraint>),
463}
464
465impl ConstraintDef {
466    pub fn primary_key(columns: Vec<&str>) -> Self {
467        Self::PrimaryKey {
468            name: None,
469            columns: columns.into_iter().map(String::from).collect(),
470            include: None,
471        }
472    }
473
474    pub fn foreign_key(columns: Vec<&str>, ref_table: &str, ref_columns: Vec<&str>) -> Self {
475        Self::ForeignKey {
476            name: None,
477            columns: columns.into_iter().map(String::from).collect(),
478            ref_table: SchemaRef::new(ref_table),
479            ref_columns: ref_columns.into_iter().map(String::from).collect(),
480            on_delete: None,
481            on_update: None,
482            deferrable: None,
483            match_type: None,
484        }
485    }
486
487    pub fn unique(columns: Vec<&str>) -> Self {
488        Self::Unique {
489            name: None,
490            columns: columns.into_iter().map(String::from).collect(),
491            include: None,
492            nulls_distinct: None,
493            condition: None,
494        }
495    }
496
497    pub fn check(condition: Conditions) -> Self {
498        Self::Check {
499            name: None,
500            condition,
501            no_inherit: false,
502            enforced: None,
503        }
504    }
505}
506
507/// Referential action for ON DELETE / ON UPDATE.
508#[derive(Debug, Clone, PartialEq, Eq)]
509pub enum ReferentialAction {
510    NoAction,
511    Restrict,
512    Cascade,
513    SetNull(Option<Vec<String>>),
514    SetDefault(Option<Vec<String>>),
515}
516
517/// Deferrable constraint options.
518#[derive(Debug, Clone, Copy, PartialEq, Eq)]
519pub struct DeferrableConstraint {
520    pub deferrable: bool,
521    pub initially_deferred: bool,
522}
523
524/// MATCH type for foreign keys.
525#[derive(Debug, Clone, Copy, PartialEq, Eq)]
526pub enum MatchType {
527    Full,
528    Partial,
529    Simple,
530}
531
532/// Element in an EXCLUSION constraint.
533#[derive(Debug, Clone)]
534pub struct ExclusionElement {
535    pub column: String,
536    pub operator: String,
537    pub opclass: Option<String>,
538}
539
540// ---------------------------------------------------------------------------
541// Index definition
542// ---------------------------------------------------------------------------
543
544/// An index on a table.
545#[derive(Debug, Clone)]
546pub struct IndexDef {
547    pub name: String,
548    pub columns: Vec<IndexColumnDef>,
549    pub unique: bool,
550    pub index_type: Option<String>,
551    pub include: Option<Vec<String>>,
552    pub condition: Option<Conditions>,
553    pub parameters: Option<Vec<(String, String)>>,
554    pub tablespace: Option<String>,
555    pub nulls_distinct: Option<bool>,
556}
557
558impl IndexDef {
559    pub fn new(name: impl Into<String>, columns: Vec<IndexColumnDef>) -> Self {
560        Self {
561            name: name.into(),
562            columns,
563            unique: false,
564            index_type: None,
565            include: None,
566            condition: None,
567            parameters: None,
568            tablespace: None,
569            nulls_distinct: None,
570        }
571    }
572
573    pub fn unique(mut self) -> Self {
574        self.unique = true;
575        self
576    }
577}
578
579/// A column or expression in an index.
580#[derive(Debug, Clone)]
581pub struct IndexColumnDef {
582    pub expr: IndexExpr,
583    pub direction: Option<OrderDir>,
584    pub nulls: Option<NullsOrder>,
585    pub opclass: Option<String>,
586    pub collation: Option<String>,
587}
588
589impl IndexColumnDef {
590    pub fn column(name: impl Into<String>) -> Self {
591        Self {
592            expr: IndexExpr::Column(name.into()),
593            direction: None,
594            nulls: None,
595            opclass: None,
596            collation: None,
597        }
598    }
599
600    pub fn expression(expr: Expr) -> Self {
601        Self {
602            expr: IndexExpr::Expression(expr),
603            direction: None,
604            nulls: None,
605            opclass: None,
606            collation: None,
607        }
608    }
609
610    pub fn asc(mut self) -> Self {
611        self.direction = Some(OrderDir::Asc);
612        self
613    }
614
615    pub fn desc(mut self) -> Self {
616        self.direction = Some(OrderDir::Desc);
617        self
618    }
619
620    pub fn nulls_first(mut self) -> Self {
621        self.nulls = Some(NullsOrder::First);
622        self
623    }
624
625    pub fn nulls_last(mut self) -> Self {
626        self.nulls = Some(NullsOrder::Last);
627        self
628    }
629}
630
631/// What's being indexed: a column name or an expression.
632#[derive(Debug, Clone)]
633pub enum IndexExpr {
634    Column(String),
635    Expression(Expr),
636}
637
638// ---------------------------------------------------------------------------
639// Partition definition
640// ---------------------------------------------------------------------------
641
642/// PARTITION BY clause for CREATE TABLE.
643#[derive(Debug, Clone)]
644pub struct PartitionByDef {
645    pub strategy: PartitionStrategy,
646    pub columns: Vec<PartitionColumnDef>,
647}
648
649/// Partition strategy.
650#[derive(Debug, Clone, Copy, PartialEq, Eq)]
651pub enum PartitionStrategy {
652    Range,
653    List,
654    Hash,
655}
656
657/// A column or expression in a PARTITION BY clause.
658#[derive(Debug, Clone)]
659pub struct PartitionColumnDef {
660    pub expr: IndexExpr,
661    pub collation: Option<String>,
662    pub opclass: Option<String>,
663}
664
665// ---------------------------------------------------------------------------
666// LIKE table definition
667// ---------------------------------------------------------------------------
668
669/// LIKE source_table [ like_option ... ] in CREATE TABLE.
670#[derive(Debug, Clone)]
671pub struct LikeTableDef {
672    pub source_table: SchemaRef,
673    pub options: Vec<LikeOption>,
674}
675
676/// LIKE options: INCLUDING or EXCLUDING specific properties.
677#[derive(Debug, Clone)]
678pub struct LikeOption {
679    pub kind: LikeOptionKind,
680    pub include: bool,
681}
682
683/// What to include/exclude from the LIKE source.
684#[derive(Debug, Clone, Copy, PartialEq, Eq)]
685pub enum LikeOptionKind {
686    Comments,
687    Compression,
688    Constraints,
689    Defaults,
690    Generated,
691    Identity,
692    Indexes,
693    Statistics,
694    Storage,
695    All,
696}
697
698// ---------------------------------------------------------------------------
699// ON COMMIT action for temporary tables
700// ---------------------------------------------------------------------------
701
702/// ON COMMIT action for temporary tables.
703#[derive(Debug, Clone, Copy, PartialEq, Eq)]
704pub enum OnCommitAction {
705    PreserveRows,
706    DeleteRows,
707    Drop,
708}