Skip to main content

gaman_core/states/
builder.rs

1use super::{
2    Column, ColumnRef, Constraint, EnumDef, ExtensionDef, ForeignKey, FunctionDef, Index,
3    OpaqueMeta, PrimaryKey, Schema, SchemaLoadError, Table, TableOptionsMeta, TriggerDef, ViewDef,
4    names, schema_qualified_key,
5};
6use crate::column_type::ColumnType;
7use crate::dialects::Dialect;
8
9/// Map a Rust type to a table definition.
10pub trait IntoTable {
11    fn into_table(dialect: &Dialect) -> Table;
12}
13
14pub struct ColumnBuilder {
15    col: Column,
16}
17
18impl ColumnBuilder {
19    fn with_reference(
20        mut self,
21        name: Option<String>,
22        table: impl Into<String>,
23        column: impl Into<String>,
24    ) -> Self {
25        self.col.references = Some(ColumnRef {
26            table: table.into(),
27            column: column.into(),
28            name,
29            on_delete: None,
30            on_update: None,
31        });
32        self
33    }
34
35    pub fn nullable(mut self) -> Self {
36        self.col.nullable = true;
37        self
38    }
39
40    pub fn not_null(mut self) -> Self {
41        self.col.nullable = false;
42        self
43    }
44
45    pub fn primary_key(mut self) -> Self {
46        self.col.primary_key = true;
47        self.col.nullable = false;
48        self
49    }
50
51    pub fn default(mut self, expr: impl Into<String>) -> Self {
52        self.col.default = Some(expr.into());
53        self
54    }
55
56    /// Set a generated column expression.
57    pub fn generated(mut self, expr: impl Into<String>) -> Self {
58        let expr = expr.into();
59        if !expr.trim().is_empty() {
60            self.col.generated = Some(expr);
61        }
62        self
63    }
64
65    pub fn references(self, table: impl Into<String>, column: impl Into<String>) -> Self {
66        self.with_reference(None, table, column)
67    }
68
69    pub fn references_named(
70        self,
71        name: impl Into<String>,
72        table: impl Into<String>,
73        column: impl Into<String>,
74    ) -> Self {
75        self.with_reference(Some(name.into()), table, column)
76    }
77
78    /// Set the inline foreign-key `ON DELETE` action.
79    pub fn on_delete(mut self, action: impl Into<String>) -> Self {
80        if let Some(reference) = &mut self.col.references {
81            let action = action.into();
82            if !action.trim().is_empty() {
83                reference.on_delete = Some(action);
84            }
85        }
86        self
87    }
88
89    /// Set the inline foreign-key `ON UPDATE` action.
90    pub fn on_update(mut self, action: impl Into<String>) -> Self {
91        if let Some(reference) = &mut self.col.references {
92            let action = action.into();
93            if !action.trim().is_empty() {
94                reference.on_update = Some(action);
95            }
96        }
97        self
98    }
99
100    pub fn check(mut self, expr: impl Into<String>) -> Self {
101        self.col.check = Some(expr.into());
102        self
103    }
104
105    fn finish(self) -> Column {
106        self.col
107    }
108}
109
110pub struct TableBuilder {
111    table: Table,
112}
113
114impl TableBuilder {
115    fn push_foreign_key(
116        mut self,
117        name: String,
118        from_columns: impl IntoIterator<Item = impl Into<String>>,
119        to_table: impl Into<String>,
120        to_columns: impl IntoIterator<Item = impl Into<String>>,
121    ) -> Self {
122        self.table
123            .foreign_keys
124            .push(ForeignKey::new(name, from_columns, to_table, to_columns));
125        self
126    }
127
128    fn push_index(mut self, name: impl Into<String>, columns: &[&str], unique: bool) -> Self {
129        self.table.indexes.push(Index {
130            name: name.into(),
131            columns: columns.iter().map(|s| s.to_string()).collect(),
132            unique,
133            predicate: None,
134            opaque: OpaqueMeta::default(),
135        });
136        self
137    }
138
139    fn push_index_with(
140        mut self,
141        name: impl Into<String>,
142        columns: &[&str],
143        unique: bool,
144        f: impl FnOnce(Index) -> Index,
145    ) -> Self {
146        let index = Index {
147            name: name.into(),
148            columns: columns.iter().map(|s| s.to_string()).collect(),
149            unique,
150            predicate: None,
151            opaque: OpaqueMeta::default(),
152        };
153        self.table.indexes.push(f(index));
154        self
155    }
156
157    pub fn new(name: impl Into<String>) -> Self {
158        let name = name.into();
159        Self {
160            table: Table {
161                name,
162                schema: None,
163                primary_key: None,
164                columns: vec![],
165                foreign_keys: vec![],
166                indexes: vec![],
167                constraints: vec![],
168                triggers: vec![],
169                options: TableOptionsMeta::default(),
170            },
171        }
172    }
173
174    pub fn schema(mut self, schema: impl Into<String>) -> Self {
175        self.table.schema = Some(schema.into());
176        self
177    }
178
179    pub fn column(
180        mut self,
181        name: impl Into<String>,
182        col_type: impl Into<String>,
183        f: impl FnOnce(ColumnBuilder) -> ColumnBuilder,
184    ) -> Self {
185        let b = ColumnBuilder {
186            col: Column {
187                name: name.into(),
188                col_type: col_type.into(),
189                ..Default::default()
190            },
191        };
192        self.table.columns.push(f(b).finish());
193        self
194    }
195
196    /// Add a column whose SQL type and default nullability are inferred from a Rust type.
197    ///
198    /// The caller supplies the dialect because type mapping is dialect-specific.
199    /// The closure runs after inference and can override nullability or add
200    /// primary-key, default, reference, and check metadata.
201    pub fn column_from_type<T: ColumnType>(
202        mut self,
203        dialect: &Dialect,
204        name: impl Into<String>,
205        f: impl FnOnce(ColumnBuilder) -> ColumnBuilder,
206    ) -> Self {
207        let desc = T::column_desc(dialect);
208        let b = ColumnBuilder {
209            col: Column {
210                name: name.into(),
211                col_type: desc.sql_type.to_string(),
212                nullable: desc.nullable,
213                ..Default::default()
214            },
215        };
216        self.table.columns.push(f(b).finish());
217        self
218    }
219
220    /// Shorthand: adds a `bigserial` primary key column named `id`.
221    pub fn id(self) -> Self {
222        self.column("id", "bigserial", |c| c.primary_key())
223    }
224
225    pub fn foreign_key(
226        self,
227        from: impl Into<String>,
228        to_table: impl Into<String>,
229        to_column: impl Into<String>,
230    ) -> Self {
231        let from = from.into();
232        let name = names::foreign_key(&self.table.name, &[from.as_str()]);
233        self.push_foreign_key(name, [from], to_table, [to_column.into()])
234    }
235
236    /// Add a foreign key and let the caller set advanced metadata.
237    pub fn foreign_key_with(
238        self,
239        from: impl Into<String>,
240        to_table: impl Into<String>,
241        to_column: impl Into<String>,
242        f: impl FnOnce(ForeignKey) -> ForeignKey,
243    ) -> Self {
244        let from = from.into();
245        let name = names::foreign_key(&self.table.name, &[from.as_str()]);
246        let foreign_key = ForeignKey::single(name, from, to_table, to_column);
247        self.foreign_key_obj(f(foreign_key))
248    }
249
250    pub fn foreign_key_named(
251        self,
252        fk_name: impl Into<String>,
253        from: impl Into<String>,
254        to_table: impl Into<String>,
255        to_column: impl Into<String>,
256    ) -> Self {
257        self.push_foreign_key(fk_name.into(), [from.into()], to_table, [to_column.into()])
258    }
259
260    /// Add a named foreign key and let the caller set advanced metadata.
261    pub fn foreign_key_named_with(
262        self,
263        fk_name: impl Into<String>,
264        from: impl Into<String>,
265        to_table: impl Into<String>,
266        to_column: impl Into<String>,
267        f: impl FnOnce(ForeignKey) -> ForeignKey,
268    ) -> Self {
269        let foreign_key = ForeignKey::single(fk_name, from, to_table, to_column);
270        self.foreign_key_obj(f(foreign_key))
271    }
272
273    pub fn foreign_key_columns(
274        self,
275        from_columns: &[&str],
276        to_table: impl Into<String>,
277        to_columns: &[&str],
278    ) -> Self {
279        let name = names::foreign_key(&self.table.name, from_columns);
280        self.push_foreign_key(
281            name,
282            from_columns.iter().copied(),
283            to_table,
284            to_columns.iter().copied(),
285        )
286    }
287
288    /// Add a composite foreign key and let the caller set advanced metadata.
289    pub fn foreign_key_columns_with(
290        self,
291        from_columns: &[&str],
292        to_table: impl Into<String>,
293        to_columns: &[&str],
294        f: impl FnOnce(ForeignKey) -> ForeignKey,
295    ) -> Self {
296        let name = names::foreign_key(&self.table.name, from_columns);
297        let foreign_key = ForeignKey::new(
298            name,
299            from_columns.iter().copied(),
300            to_table,
301            to_columns.iter().copied(),
302        );
303        self.foreign_key_obj(f(foreign_key))
304    }
305
306    pub fn foreign_key_named_columns(
307        self,
308        fk_name: impl Into<String>,
309        from_columns: &[&str],
310        to_table: impl Into<String>,
311        to_columns: &[&str],
312    ) -> Self {
313        self.push_foreign_key(
314            fk_name.into(),
315            from_columns.iter().copied(),
316            to_table,
317            to_columns.iter().copied(),
318        )
319    }
320
321    /// Add a named composite foreign key and let the caller set advanced metadata.
322    pub fn foreign_key_named_columns_with(
323        self,
324        fk_name: impl Into<String>,
325        from_columns: &[&str],
326        to_table: impl Into<String>,
327        to_columns: &[&str],
328        f: impl FnOnce(ForeignKey) -> ForeignKey,
329    ) -> Self {
330        let foreign_key = ForeignKey::new(
331            fk_name,
332            from_columns.iter().copied(),
333            to_table,
334            to_columns.iter().copied(),
335        );
336        self.foreign_key_obj(f(foreign_key))
337    }
338
339    /// Add a fully constructed foreign key.
340    pub fn foreign_key_obj(mut self, foreign_key: ForeignKey) -> Self {
341        self.table.foreign_keys.push(foreign_key);
342        self
343    }
344
345    pub fn index_columns(self, columns: &[&str]) -> Self {
346        let name = names::index(&self.table.name, columns);
347        self.push_index(name, columns, false)
348    }
349
350    /// Add an index with generated name and advanced metadata.
351    pub fn index_columns_with(self, columns: &[&str], f: impl FnOnce(Index) -> Index) -> Self {
352        let name = names::index(&self.table.name, columns);
353        self.push_index_with(name, columns, false, f)
354    }
355
356    pub fn unique_index_columns(self, columns: &[&str]) -> Self {
357        let name = names::index(&self.table.name, columns);
358        self.push_index(name, columns, true)
359    }
360
361    /// Add a unique index with generated name and advanced metadata.
362    pub fn unique_index_columns_with(
363        self,
364        columns: &[&str],
365        f: impl FnOnce(Index) -> Index,
366    ) -> Self {
367        let name = names::index(&self.table.name, columns);
368        self.push_index_with(name, columns, true, f)
369    }
370
371    pub fn index(self, name: impl Into<String>, columns: &[&str]) -> Self {
372        self.push_index(name, columns, false)
373    }
374
375    /// Add an index and let the caller set advanced metadata.
376    pub fn index_with(
377        self,
378        name: impl Into<String>,
379        columns: &[&str],
380        f: impl FnOnce(Index) -> Index,
381    ) -> Self {
382        self.push_index_with(name, columns, false, f)
383    }
384
385    pub fn unique_index(self, name: impl Into<String>, columns: &[&str]) -> Self {
386        self.push_index(name, columns, true)
387    }
388
389    /// Add a unique index and let the caller set advanced metadata.
390    pub fn unique_index_with(
391        self,
392        name: impl Into<String>,
393        columns: &[&str],
394        f: impl FnOnce(Index) -> Index,
395    ) -> Self {
396        self.push_index_with(name, columns, true, f)
397    }
398
399    pub fn check(mut self, name: impl Into<String>, expression: impl Into<String>) -> Self {
400        self.table.constraints.push(Constraint::Check {
401            name: name.into(),
402            expression: expression.into(),
403        });
404        self
405    }
406
407    pub fn check_expr(self, expression: impl Into<String>) -> Self {
408        let name = names::table_check(&self.table.name);
409        self.check(name, expression)
410    }
411
412    pub fn unique(mut self, name: impl Into<String>, columns: &[&str]) -> Self {
413        self.table.constraints.push(Constraint::Unique {
414            name: name.into(),
415            columns: columns.iter().map(|s| s.to_string()).collect(),
416        });
417        self
418    }
419
420    pub fn unique_columns(self, columns: &[&str]) -> Self {
421        let name = names::unique(&self.table.name, columns);
422        self.unique(name, columns)
423    }
424
425    pub fn primary_key(mut self, name: impl Into<String>, columns: &[&str]) -> Self {
426        self.table.primary_key = Some(PrimaryKey {
427            name: name.into(),
428            columns: columns.iter().map(|s| s.to_string()).collect(),
429        });
430        self
431    }
432
433    pub fn primary_key_columns(self, columns: &[&str]) -> Self {
434        let name = names::primary_key(&self.table.name);
435        self.primary_key(name, columns)
436    }
437
438    pub fn trigger(self, trigger: TriggerDef) -> Self {
439        let mut this = self;
440        this.table.triggers.push(trigger);
441        this
442    }
443
444    pub fn build(mut self) -> Table {
445        if self.table.primary_key.is_none() {
446            let columns: Vec<String> = self
447                .table
448                .columns
449                .iter()
450                .filter(|column| column.primary_key)
451                .map(|column| column.name.clone())
452                .collect();
453            if !columns.is_empty() {
454                self.table.primary_key = Some(PrimaryKey {
455                    name: names::primary_key(&self.table.name),
456                    columns,
457                });
458            }
459        }
460        if let Some(pk) = &self.table.primary_key {
461            for column in &mut self.table.columns {
462                column.primary_key = pk.columns.iter().any(|name| name == &column.name);
463                if column.primary_key {
464                    column.nullable = false;
465                }
466            }
467        }
468        self.table
469    }
470}
471
472pub struct SchemaBuilder {
473    dialect: Dialect,
474    state: Schema,
475}
476
477impl SchemaBuilder {
478    fn insert_extension(mut self, name: impl Into<String>, version: Option<String>) -> Self {
479        let name = name.into();
480        self.state.extensions.insert(
481            name.clone(),
482            ExtensionDef {
483                name,
484                schema: None,
485                version,
486                opaque: OpaqueMeta::default(),
487            },
488        );
489        self
490    }
491
492    pub fn new(dialect: Dialect) -> Self {
493        Self {
494            dialect,
495            state: Schema::default(),
496        }
497    }
498
499    /// Add a table from any type that implements [`IntoTable`].
500    pub fn table<T: IntoTable>(mut self) -> Self {
501        let t = T::into_table(&self.dialect);
502        let key = schema_qualified_key(&t.name, t.schema.as_deref());
503        self.state.tables.insert(key, t);
504        self
505    }
506
507    /// Add a fully constructed modeled table definition.
508    pub fn table_def(mut self, table: Table) -> Self {
509        let key = schema_qualified_key(&table.name, table.schema.as_deref());
510        self.state.tables.insert(key, table);
511        self
512    }
513
514    pub fn extension(self, name: impl Into<String>) -> Self {
515        self.insert_extension(name, None)
516    }
517
518    pub fn extension_versioned(self, name: impl Into<String>, version: impl Into<String>) -> Self {
519        self.insert_extension(name, Some(version.into()))
520    }
521
522    /// Add a fully constructed extension definition.
523    pub fn extension_def(mut self, extension: ExtensionDef) -> Self {
524        let key = schema_qualified_key(&extension.name, extension.schema.as_deref());
525        self.state.extensions.insert(key, extension);
526        self
527    }
528
529    pub fn view(mut self, name: impl Into<String>, definition: impl Into<String>) -> Self {
530        let name = name.into();
531        self.state.views.insert(
532            name.clone(),
533            ViewDef {
534                name,
535                schema: None,
536                definition: definition.into(),
537                opaque: OpaqueMeta::default(),
538            },
539        );
540        self
541    }
542
543    /// Add a fully constructed view definition.
544    pub fn view_def(mut self, view: ViewDef) -> Self {
545        let key = schema_qualified_key(&view.name, view.schema.as_deref());
546        self.state.views.insert(key, view);
547        self
548    }
549
550    pub fn function(mut self, f: FunctionDef) -> Self {
551        let key = schema_qualified_key(&f.name, f.schema.as_deref());
552        self.state.functions.insert(key, f);
553        self
554    }
555
556    pub fn enum_type(mut self, name: impl Into<String>, values: &[&str]) -> Self {
557        let name = name.into();
558        self.state.enums.insert(
559            name.clone(),
560            EnumDef {
561                name,
562                schema: None,
563                values: values.iter().map(|s| s.to_string()).collect(),
564                opaque: OpaqueMeta::default(),
565            },
566        );
567        self
568    }
569
570    /// Add a fully constructed enum definition.
571    pub fn enum_def(mut self, enum_def: EnumDef) -> Self {
572        let key = schema_qualified_key(&enum_def.name, enum_def.schema.as_deref());
573        self.state.enums.insert(key, enum_def);
574        self
575    }
576
577    /// Build a schema through the same normalize and prepare lifecycle as file
578    /// and SQL ingestion.
579    pub fn build(self) -> Result<Schema, SchemaLoadError> {
580        let dialect = self.dialect;
581        self.build_raw().prepare_loaded(dialect)
582    }
583
584    /// Build a raw normalized schema without dialect preparation.
585    ///
586    /// This is intended for internal tests and low-level tooling that need to
587    /// inspect pre-validation model state. User-facing builder paths should use
588    /// [`SchemaBuilder::build`].
589    pub(crate) fn build_raw(self) -> Schema {
590        self.state
591    }
592}