1use crate::{Column, Constraint, Schema, Table, View};
2use sim_kernel::Datum;
3use sim_relation_core::{
4 ColumnName, ConstraintName, DomainCatalog, DomainId, IndexName, SchemaName, TableName, ViewName,
5};
6use std::{
7 collections::{BTreeMap, BTreeSet},
8 fmt,
9};
10
11pub trait ValueShapeValidator {
13 fn accepts(&self, domain: &DomainId, value: &Datum) -> bool;
15}
16pub struct AcceptAllValues;
18impl ValueShapeValidator for AcceptAllValues {
19 fn accepts(&self, _: &DomainId, _: &Datum) -> bool {
20 true
21 }
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum SchemaError {
27 DuplicateTable(TableName),
29 DuplicateView(ViewName),
31 DuplicateColumn {
33 table: TableName,
35 column: ColumnName,
37 },
38 DuplicateConstraint {
40 table: TableName,
42 constraint: ConstraintName,
44 },
45 DuplicateIndex {
47 table: TableName,
49 index: IndexName,
51 },
52 DanglingDomain(DomainId),
54 DanglingColumn {
56 table: TableName,
58 column: ColumnName,
60 },
61 DanglingTable(TableName),
63 DanglingView(ViewName),
65 EmptyKey(ConstraintName),
67 NullablePrimaryKey(ColumnName),
69 DefaultAndGenerated(ColumnName),
71 GeneratedCycle(ColumnName),
73 InvalidDefault(ColumnName),
75 InvalidGenerated(ColumnName),
77 ForeignKeyArity(ConstraintName),
79 ForeignKeyDomain {
81 constraint: ConstraintName,
83 index: usize,
85 },
86 ViewCycle(ViewName),
88}
89impl fmt::Display for SchemaError {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(f, "{self:?}")
92 }
93}
94impl std::error::Error for SchemaError {}
95
96pub(crate) fn validate(
97 name: SchemaName,
98 mut tables: Vec<Table>,
99 mut views: Vec<View>,
100 domains: &DomainCatalog,
101 validator: &impl ValueShapeValidator,
102) -> Result<Schema, SchemaError> {
103 unique(&tables, |v| v.name.clone(), SchemaError::DuplicateTable)?;
104 unique(&views, |v| v.name.clone(), SchemaError::DuplicateView)?;
105 let table_map: BTreeMap<_, _> = tables.iter().map(|v| (v.name.clone(), v)).collect();
106 for table in &tables {
107 validate_table(table, &table_map, domains, validator)?;
108 }
109 let view_names: BTreeSet<_> = views.iter().map(|v| v.name.clone()).collect();
110 for view in &views {
111 for table in &view.table_dependencies {
112 if !table_map.contains_key(table) {
113 return Err(SchemaError::DanglingTable(table.clone()));
114 }
115 }
116 for dep in &view.view_dependencies {
117 if !view_names.contains(dep) {
118 return Err(SchemaError::DanglingView(dep.clone()));
119 }
120 }
121 }
122 for view in &views {
123 visit_view(
124 &view.name,
125 &views,
126 &mut BTreeSet::new(),
127 &mut BTreeSet::new(),
128 )?;
129 }
130 tables.sort_by(|a, b| a.name.cmp(&b.name));
131 views.sort_by(|a, b| a.name.cmp(&b.name));
132 for table in &mut tables {
133 table.constraints.sort_by(|a, b| a.name().cmp(b.name()));
134 table.indexes.sort_by(|a, b| a.name.cmp(&b.name));
135 }
136 Ok(Schema {
137 name,
138 tables,
139 views,
140 })
141}
142fn unique<T, K: Ord + Clone>(
143 values: &[T],
144 key: impl Fn(&T) -> K,
145 error: impl Fn(K) -> SchemaError,
146) -> Result<(), SchemaError> {
147 let mut seen = BTreeSet::new();
148 for v in values {
149 let k = key(v);
150 if !seen.insert(k.clone()) {
151 return Err(error(k));
152 }
153 }
154 Ok(())
155}
156fn columns(table: &Table) -> BTreeMap<ColumnName, &Column> {
157 table.columns.iter().map(|v| (v.name.clone(), v)).collect()
158}
159fn validate_table(
160 table: &Table,
161 tables: &BTreeMap<TableName, &Table>,
162 domains: &DomainCatalog,
163 validator: &impl ValueShapeValidator,
164) -> Result<(), SchemaError> {
165 unique(
166 &table.columns,
167 |v| v.name.clone(),
168 |column| SchemaError::DuplicateColumn {
169 table: table.name.clone(),
170 column,
171 },
172 )?;
173 unique(
174 &table.constraints,
175 |v| v.name().clone(),
176 |constraint| SchemaError::DuplicateConstraint {
177 table: table.name.clone(),
178 constraint,
179 },
180 )?;
181 unique(
182 &table.indexes,
183 |v| v.name.clone(),
184 |index| SchemaError::DuplicateIndex {
185 table: table.name.clone(),
186 index,
187 },
188 )?;
189 let cols = columns(table);
190 for c in &table.columns {
191 if domains.get(&c.domain).is_none() {
192 return Err(SchemaError::DanglingDomain(c.domain.clone()));
193 }
194 if c.default.is_some() && c.generated.is_some() {
195 return Err(SchemaError::DefaultAndGenerated(c.name.clone()));
196 }
197 if let Some(v) = &c.default
198 && !validator.accepts(&c.domain, &v.0)
199 {
200 return Err(SchemaError::InvalidDefault(c.name.clone()));
201 }
202 if let Some(v) = &c.generated {
203 for dep in &v.depends_on {
204 require_column(table, &cols, dep)?;
205 if dep == &c.name {
206 return Err(SchemaError::GeneratedCycle(c.name.clone()));
207 }
208 }
209 if !validator.accepts(&c.domain, &v.expression) {
210 return Err(SchemaError::InvalidGenerated(c.name.clone()));
211 }
212 }
213 }
214 for constraint in &table.constraints {
215 match constraint {
216 Constraint::Primary(v) => {
217 key(table, &cols, &v.name, &v.columns)?;
218 for n in &v.columns {
219 if cols[n].nullable {
220 return Err(SchemaError::NullablePrimaryKey(n.clone()));
221 }
222 }
223 }
224 Constraint::Unique(v) => key(table, &cols, &v.name, &v.columns)?,
225 Constraint::Check(v) => {
226 for n in &v.columns {
227 require_column(table, &cols, n)?
228 }
229 }
230 Constraint::Foreign(v) => {
231 key(table, &cols, &v.name, &v.columns)?;
232 if v.columns.len() != v.target_columns.len() {
233 return Err(SchemaError::ForeignKeyArity(v.name.clone()));
234 }
235 let target = tables
236 .get(&v.target_table)
237 .ok_or_else(|| SchemaError::DanglingTable(v.target_table.clone()))?;
238 let target_cols = columns(target);
239 for (i, (left, right)) in v.columns.iter().zip(&v.target_columns).enumerate() {
240 require_column(target, &target_cols, right)?;
241 if cols[left].domain != target_cols[right].domain {
242 return Err(SchemaError::ForeignKeyDomain {
243 constraint: v.name.clone(),
244 index: i,
245 });
246 }
247 }
248 }
249 }
250 }
251 for index in &table.indexes {
252 if index.columns.is_empty() {
253 return Err(SchemaError::DanglingColumn {
254 table: table.name.clone(),
255 column: ColumnName::new(sim_kernel::Symbol::new("<empty-index>")).expect("valid"),
256 });
257 }
258 for n in &index.columns {
259 require_column(table, &cols, n)?
260 }
261 }
262 Ok(())
263}
264fn require_column(
265 table: &Table,
266 cols: &BTreeMap<ColumnName, &Column>,
267 name: &ColumnName,
268) -> Result<(), SchemaError> {
269 if cols.contains_key(name) {
270 Ok(())
271 } else {
272 Err(SchemaError::DanglingColumn {
273 table: table.name.clone(),
274 column: name.clone(),
275 })
276 }
277}
278fn key(
279 table: &Table,
280 cols: &BTreeMap<ColumnName, &Column>,
281 name: &ConstraintName,
282 names: &[ColumnName],
283) -> Result<(), SchemaError> {
284 if names.is_empty() {
285 return Err(SchemaError::EmptyKey(name.clone()));
286 }
287 for n in names {
288 require_column(table, cols, n)?
289 }
290 Ok(())
291}
292fn visit_view(
293 name: &ViewName,
294 views: &[View],
295 visiting: &mut BTreeSet<ViewName>,
296 done: &mut BTreeSet<ViewName>,
297) -> Result<(), SchemaError> {
298 if done.contains(name) {
299 return Ok(());
300 }
301 if !visiting.insert(name.clone()) {
302 return Err(SchemaError::ViewCycle(name.clone()));
303 }
304 let view = views
305 .iter()
306 .find(|v| &v.name == name)
307 .expect("existence checked");
308 for dep in &view.view_dependencies {
309 visit_view(dep, views, visiting, done)?;
310 }
311 visiting.remove(name);
312 done.insert(name.clone());
313 Ok(())
314}