toasty_core/schema/
builder.rs

1mod table;
2
3use super::{app, db, mapping, Result};
4use crate::schema::mapping::TableToModel;
5use crate::schema::{Mapping, Schema, Table, TableId};
6use crate::{driver, stmt};
7use indexmap::IndexMap;
8
9#[derive(Debug)]
10pub struct Builder {
11    /// If set, prefix all table names with this string
12    table_name_prefix: Option<String>,
13}
14
15/// Used to track state during the build process
16struct BuildSchema<'a> {
17    /// Build options
18    builder: &'a Builder,
19
20    db: &'a driver::Capability,
21
22    /// Maps table names to identifiers. The identifiers are reserved before the
23    /// table objects are actually created.
24    table_lookup: IndexMap<String, TableId>,
25
26    /// Tables as they are built
27    tables: Vec<Table>,
28
29    /// App-level to db-level schema mapping
30    mapping: Mapping,
31}
32
33impl Builder {
34    pub fn new() -> Self {
35        Self {
36            table_name_prefix: None,
37        }
38    }
39
40    pub fn table_name_prefix(&mut self, prefix: &str) -> &mut Self {
41        self.table_name_prefix = Some(prefix.to_string());
42        self
43    }
44
45    pub fn build(&self, mut app: app::Schema, db: &driver::Capability) -> Result<Schema> {
46        let mut builder = BuildSchema {
47            builder: self,
48            db,
49            table_lookup: IndexMap::new(),
50            tables: vec![],
51            mapping: Mapping {
52                models: IndexMap::new(),
53            },
54        };
55
56        for model in app.models.values_mut() {
57            // Initial verification pass to ensure all models are valid based on the
58            // specified driver capability.
59            model.verify(db)?;
60
61            // Generate any additional field-level constraints to satisfy the
62            // target database.
63            builder.build_model_constraints(model)?;
64        }
65
66        // Find all models that specified a table name, ensure a table is
67        // created for that model, and link the model with the table.
68        // Skip embedded models as they don't have their own tables.
69        for model in app.models() {
70            // Skip embedded models - they are flattened into parent tables
71            let app::Model::Root(model) = model else {
72                continue;
73            };
74
75            let table = builder.build_table_stub_for_model(model);
76
77            // Create a mapping shell for the model (fields will be built during mapping phase)
78            builder.mapping.models.insert(
79                model.id,
80                mapping::Model {
81                    id: model.id,
82                    table,
83                    columns: vec![],
84                    fields: vec![], // Will be populated during mapping phase
85                    model_to_table: stmt::ExprRecord::default(),
86                    table_to_model: TableToModel::default(),
87                },
88            );
89        }
90
91        builder.build_tables_from_models(&app, db)?;
92
93        let schema = Schema {
94            app,
95            db: db::Schema {
96                tables: builder.tables,
97            },
98            mapping: builder.mapping,
99        };
100
101        // Verify the schema structure
102        schema.verify()?;
103
104        Ok(schema)
105    }
106}
107
108impl Default for Builder {
109    fn default() -> Self {
110        Self::new()
111    }
112}
113
114impl BuildSchema<'_> {
115    fn build_model_constraints(&self, model: &mut app::Model) -> Result<()> {
116        let fields = match model {
117            app::Model::Root(root) => &mut root.fields[..],
118            app::Model::EmbeddedStruct(embedded) => &mut embedded.fields[..],
119            app::Model::EmbeddedEnum(_) => return Ok(()),
120        };
121        for field in fields.iter_mut() {
122            if let app::FieldTy::Primitive(primitive) = &mut field.ty {
123                let storage_ty = db::Type::from_app(
124                    &primitive.ty,
125                    primitive.storage_ty.as_ref(),
126                    &self.db.storage_types,
127                )?;
128
129                if let db::Type::VarChar(size) = storage_ty {
130                    field
131                        .constraints
132                        .push(app::Constraint::length_less_than(size));
133                }
134            }
135        }
136
137        Ok(())
138    }
139}