Skip to main content

sim_relation_schema/
model.rs

1use crate::{SchemaError, ValueShapeValidator};
2use sim_kernel::{Datum, Symbol};
3use sim_relation_core::{
4    ColumnName, ConstraintName, DomainCatalog, DomainId, IndexName, RelationId, SchemaName,
5    TableName, ToRelationDatum, ViewName,
6};
7
8/// A literal default checked through its logical domain Shape.
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct DefaultValue(pub Datum);
11/// A generated expression and the columns it reads.
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct GeneratedValue {
14    pub(crate) expression: Datum,
15    pub(crate) depends_on: Vec<ColumnName>,
16}
17impl GeneratedValue {
18    /// Creates an expression with ordered dependency names.
19    pub fn new(expression: Datum, depends_on: impl IntoIterator<Item = ColumnName>) -> Self {
20        Self {
21            expression,
22            depends_on: depends_on.into_iter().collect(),
23        }
24    }
25}
26/// A logical column declaration.
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct Column {
29    pub(crate) name: ColumnName,
30    pub(crate) domain: DomainId,
31    pub(crate) nullable: bool,
32    pub(crate) default: Option<DefaultValue>,
33    pub(crate) generated: Option<GeneratedValue>,
34}
35impl Column {
36    /// Returns the name.
37    pub fn name(&self) -> &ColumnName {
38        &self.name
39    }
40    /// Returns the domain.
41    pub fn domain(&self) -> &DomainId {
42        &self.domain
43    }
44    /// Returns whether NULL is permitted.
45    pub const fn nullable(&self) -> bool {
46        self.nullable
47    }
48    /// Returns whether omission is legal because a default is installed.
49    pub const fn has_default(&self) -> bool {
50        self.default.is_some()
51    }
52    /// Returns whether the provider computes this column.
53    pub const fn is_generated(&self) -> bool {
54        self.generated.is_some()
55    }
56}
57/// An ordered primary key.
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct PrimaryKey {
60    /// Stable constraint name.
61    pub name: ConstraintName,
62    /// Key columns in comparison order.
63    pub columns: Vec<ColumnName>,
64}
65/// An ordered unique key.
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct UniqueConstraint {
68    /// Stable constraint name.
69    pub name: ConstraintName,
70    /// Key columns in comparison order.
71    pub columns: Vec<ColumnName>,
72}
73/// A table-local check and the columns in its scope.
74#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct CheckConstraint {
76    /// Stable constraint name.
77    pub name: ConstraintName,
78    /// Portable check expression.
79    pub expression: Datum,
80    /// Columns visible to the expression.
81    pub columns: Vec<ColumnName>,
82}
83/// An ordered foreign-key mapping.
84#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct ForeignKey {
86    /// Stable constraint name.
87    pub name: ConstraintName,
88    /// Local columns in mapping order.
89    pub columns: Vec<ColumnName>,
90    /// Referenced table.
91    pub target_table: TableName,
92    /// Referenced columns in mapping order.
93    pub target_columns: Vec<ColumnName>,
94}
95/// A table constraint.
96#[derive(Clone, Debug, PartialEq, Eq)]
97pub enum Constraint {
98    /// One primary key.
99    Primary(PrimaryKey),
100    /// A uniqueness constraint.
101    Unique(UniqueConstraint),
102    /// A table-local predicate.
103    Check(CheckConstraint),
104    /// A referential constraint.
105    Foreign(ForeignKey),
106}
107impl Constraint {
108    pub(crate) fn name(&self) -> &ConstraintName {
109        match self {
110            Self::Primary(v) => &v.name,
111            Self::Unique(v) => &v.name,
112            Self::Check(v) => &v.name,
113            Self::Foreign(v) => &v.name,
114        }
115    }
116}
117/// An index whose column order is semantic.
118#[derive(Clone, Debug, PartialEq, Eq)]
119pub struct Index {
120    /// Stable index name.
121    pub name: IndexName,
122    /// Indexed columns in key order.
123    pub columns: Vec<ColumnName>,
124    /// Whether the provider must enforce uniqueness.
125    pub unique: bool,
126}
127/// A validated table declaration.
128#[derive(Clone, Debug, PartialEq, Eq)]
129pub struct Table {
130    pub(crate) name: TableName,
131    pub(crate) columns: Vec<Column>,
132    pub(crate) constraints: Vec<Constraint>,
133    pub(crate) indexes: Vec<Index>,
134}
135impl Table {
136    /// Returns the name.
137    pub fn name(&self) -> &TableName {
138        &self.name
139    }
140    /// Returns columns in semantic order.
141    pub fn columns(&self) -> &[Column] {
142        &self.columns
143    }
144    /// Returns constraints in canonical name order.
145    pub fn constraints(&self) -> &[Constraint] {
146        &self.constraints
147    }
148    /// Returns indexes in canonical name order.
149    pub fn indexes(&self) -> &[Index] {
150        &self.indexes
151    }
152}
153/// A logical view and its table/view dependencies.
154#[derive(Clone, Debug, PartialEq, Eq)]
155pub struct View {
156    /// Stable view name.
157    pub name: ViewName,
158    /// Portable logical query.
159    pub query: Datum,
160    /// Tables read by the query.
161    pub table_dependencies: Vec<TableName>,
162    /// Views read by the query.
163    pub view_dependencies: Vec<ViewName>,
164}
165/// Complete provider-neutral schema intent.
166#[derive(Clone, Debug, PartialEq, Eq)]
167pub struct Schema {
168    pub(crate) name: SchemaName,
169    pub(crate) tables: Vec<Table>,
170    pub(crate) views: Vec<View>,
171}
172impl Schema {
173    /// Validates a complete schema graph.
174    pub fn new(
175        name: SchemaName,
176        tables: impl IntoIterator<Item = Table>,
177        views: impl IntoIterator<Item = View>,
178        domains: &DomainCatalog,
179        validator: &impl ValueShapeValidator,
180    ) -> Result<Self, SchemaError> {
181        crate::validation::validate(
182            name,
183            tables.into_iter().collect(),
184            views.into_iter().collect(),
185            domains,
186            validator,
187        )
188    }
189    /// Returns its name.
190    pub fn name(&self) -> &SchemaName {
191        &self.name
192    }
193    /// Returns tables in canonical name order.
194    pub fn tables(&self) -> &[Table] {
195        &self.tables
196    }
197    /// Returns views in canonical name order.
198    pub fn views(&self) -> &[View] {
199        &self.views
200    }
201    /// Returns its canonical content identity.
202    pub fn id(&self) -> Result<RelationId, sim_kernel::Error> {
203        RelationId::of(self)
204    }
205}
206
207fn sym(name: &str, value: Symbol) -> (Symbol, Datum) {
208    (Symbol::new(name), Datum::Symbol(value))
209}
210fn node(tag: &str, fields: Vec<(Symbol, Datum)>) -> Datum {
211    Datum::Node {
212        tag: Symbol::qualified("relation-schema", tag),
213        fields,
214    }
215}
216fn names<T>(values: &[T], f: impl Fn(&T) -> Symbol) -> Datum {
217    Datum::Vector(values.iter().map(|v| Datum::Symbol(f(v))).collect())
218}
219impl ToRelationDatum for Column {
220    fn to_datum(&self) -> Datum {
221        node(
222            "column",
223            vec![
224                sym("name", self.name.symbol().clone()),
225                sym("domain", self.domain.symbol().clone()),
226                (Symbol::new("nullable"), Datum::Bool(self.nullable)),
227                (
228                    Symbol::new("default"),
229                    self.default.as_ref().map_or(Datum::Nil, |v| v.0.clone()),
230                ),
231                (
232                    Symbol::new("generated"),
233                    self.generated.as_ref().map_or(Datum::Nil, |v| {
234                        node(
235                            "generated",
236                            vec![
237                                (Symbol::new("expression"), v.expression.clone()),
238                                (
239                                    Symbol::new("depends-on"),
240                                    names(&v.depends_on, |n| n.symbol().clone()),
241                                ),
242                            ],
243                        )
244                    }),
245                ),
246            ],
247        )
248    }
249}
250impl ToRelationDatum for Constraint {
251    fn to_datum(&self) -> Datum {
252        match self {
253            Self::Primary(v) => node(
254                "primary",
255                vec![
256                    sym("name", v.name.symbol().clone()),
257                    (
258                        Symbol::new("columns"),
259                        names(&v.columns, |n| n.symbol().clone()),
260                    ),
261                ],
262            ),
263            Self::Unique(v) => node(
264                "unique",
265                vec![
266                    sym("name", v.name.symbol().clone()),
267                    (
268                        Symbol::new("columns"),
269                        names(&v.columns, |n| n.symbol().clone()),
270                    ),
271                ],
272            ),
273            Self::Check(v) => node(
274                "check",
275                vec![
276                    sym("name", v.name.symbol().clone()),
277                    (Symbol::new("expression"), v.expression.clone()),
278                    (
279                        Symbol::new("columns"),
280                        names(&v.columns, |n| n.symbol().clone()),
281                    ),
282                ],
283            ),
284            Self::Foreign(v) => node(
285                "foreign",
286                vec![
287                    sym("name", v.name.symbol().clone()),
288                    (
289                        Symbol::new("columns"),
290                        names(&v.columns, |n| n.symbol().clone()),
291                    ),
292                    sym("target-table", v.target_table.symbol().clone()),
293                    (
294                        Symbol::new("target-columns"),
295                        names(&v.target_columns, |n| n.symbol().clone()),
296                    ),
297                ],
298            ),
299        }
300    }
301}
302impl ToRelationDatum for Index {
303    fn to_datum(&self) -> Datum {
304        node(
305            "index",
306            vec![
307                sym("name", self.name.symbol().clone()),
308                (
309                    Symbol::new("columns"),
310                    names(&self.columns, |n| n.symbol().clone()),
311                ),
312                (Symbol::new("unique"), Datum::Bool(self.unique)),
313            ],
314        )
315    }
316}
317impl ToRelationDatum for Table {
318    fn to_datum(&self) -> Datum {
319        node(
320            "table",
321            vec![
322                sym("name", self.name.symbol().clone()),
323                (
324                    Symbol::new("columns"),
325                    Datum::Vector(self.columns.iter().map(ToRelationDatum::to_datum).collect()),
326                ),
327                (
328                    Symbol::new("constraints"),
329                    Datum::Vector(
330                        self.constraints
331                            .iter()
332                            .map(ToRelationDatum::to_datum)
333                            .collect(),
334                    ),
335                ),
336                (
337                    Symbol::new("indexes"),
338                    Datum::Vector(self.indexes.iter().map(ToRelationDatum::to_datum).collect()),
339                ),
340            ],
341        )
342    }
343}
344impl ToRelationDatum for View {
345    fn to_datum(&self) -> Datum {
346        node(
347            "view",
348            vec![
349                sym("name", self.name.symbol().clone()),
350                (Symbol::new("query"), self.query.clone()),
351                (
352                    Symbol::new("tables"),
353                    names(&self.table_dependencies, |n| n.symbol().clone()),
354                ),
355                (
356                    Symbol::new("views"),
357                    names(&self.view_dependencies, |n| n.symbol().clone()),
358                ),
359            ],
360        )
361    }
362}
363impl ToRelationDatum for Schema {
364    fn to_datum(&self) -> Datum {
365        node(
366            "logical-schema",
367            vec![
368                sym("name", self.name.symbol().clone()),
369                (
370                    Symbol::new("tables"),
371                    Datum::Vector(self.tables.iter().map(ToRelationDatum::to_datum).collect()),
372                ),
373                (
374                    Symbol::new("views"),
375                    Datum::Vector(self.views.iter().map(ToRelationDatum::to_datum).collect()),
376                ),
377            ],
378        )
379    }
380}