1use crate::{
2 datum::{MutationDatum, QueryDatum},
3 model::*,
4};
5use sim_kernel::Symbol;
6use sim_relation_core::{
7 BindingName, ColumnName, DomainCatalog, DomainId, DomainTrait, FieldName, FieldType,
8 RelationId, RowType, TableName,
9};
10use sim_relation_schema::{Constraint, Schema, Table};
11use std::collections::{BTreeMap, BTreeSet};
12
13type Scope = BTreeMap<BindingName, RowType>;
14struct Admit<'a> {
15 schema: &'a Schema,
16 domains: &'a DomainCatalog,
17 params: &'a RowType,
18 limits: AdmissionLimits,
19}
20impl<'a> Admit<'a> {
21 fn table(&self, name: &TableName) -> Result<&'a Table, AdmissionError> {
22 self.schema
23 .tables()
24 .iter()
25 .find(|t| t.name() == name)
26 .ok_or_else(|| AdmissionError::UnknownTable(name.clone()))
27 }
28 fn table_row(&self, table: &Table, nullable: bool) -> Result<RowType, AdmissionError> {
29 RowType::new(table.columns().iter().map(|c| FieldType {
30 name: FieldName::new(c.name().symbol().clone()).expect("validated"),
31 domain: c.domain().clone(),
32 nullable: nullable || c.nullable(),
33 }))
34 .map_err(|_| AdmissionError::DuplicateName)
35 }
36 fn bind(scope: &mut Scope, name: BindingName, row: RowType) -> Result<(), AdmissionError> {
37 if scope.insert(name.clone(), row).is_some() {
38 Err(AdmissionError::AmbiguousBinding(name))
39 } else {
40 Ok(())
41 }
42 }
43 fn visible(&self, rel: &Rel, row: &RowType, scope: &mut Scope) -> Result<(), AdmissionError> {
44 match rel {
45 Rel::Scan { table, bind, .. } => Self::bind(
46 scope,
47 bind.clone(),
48 self.table_row(self.table(table)?, false)?,
49 ),
50 Rel::Values { bind, row_type, .. } => Self::bind(scope, bind.clone(), row_type.clone()),
51 Rel::Project { bind, .. } | Rel::Group { bind, .. } => {
52 Self::bind(scope, bind.clone(), row.clone())
53 }
54 Rel::Join {
55 left, right, kind, ..
56 } => {
57 let l = self.rel(left, scope)?;
58 self.visible(left, &l, scope)?;
59 let left_names: BTreeSet<_> = scope.keys().cloned().collect();
60 let r = self.rel(right, scope)?;
61 self.visible(right, &r, scope)?;
62 if *kind == JoinKind::Left {
63 for (name, row) in scope.iter_mut() {
64 if !left_names.contains(name) {
65 *row = RowType::new(row.fields().iter().cloned().map(|mut field| {
66 field.nullable = true;
67 field
68 }))
69 .expect("changing nullability preserves field names");
70 }
71 }
72 }
73 Ok(())
74 }
75 Rel::Filter { input, .. }
76 | Rel::Distinct(input)
77 | Rel::Order { input, .. }
78 | Rel::Limit { input, .. } => self.visible(input, row, scope),
79 Rel::Set { .. } => Ok(()),
80 }
81 }
82 fn scalar(&self, value: &Scalar, scope: &Scope) -> Result<FieldType, AdmissionError> {
83 let bool_ty = || FieldType {
84 name: fname("value"),
85 domain: sim_relation_core::BaseDomain::Bool.id(),
86 nullable: false,
87 };
88 match value {
89 Scalar::Field(r) => scope
90 .get(&r.binding)
91 .ok_or_else(|| AdmissionError::UnresolvedBinding(r.binding.clone()))?
92 .fields()
93 .iter()
94 .find(|f| f.name == r.field)
95 .cloned()
96 .ok_or_else(|| AdmissionError::UnresolvedField(r.clone())),
97 Scalar::Literal(c) => Ok(FieldType {
98 name: fname("value"),
99 domain: c.domain().clone(),
100 nullable: c.value().is_none(),
101 }),
102 Scalar::Param(p) => self
103 .params
104 .fields()
105 .iter()
106 .find(|f| f.name.symbol() == p.symbol())
107 .cloned()
108 .ok_or_else(|| AdmissionError::UnresolvedParameter(p.clone())),
109 Scalar::Exists(q) => {
110 self.rel(q, scope)?;
111 Ok(bool_ty())
112 }
113 Scalar::InQuery { value, query } => {
114 let l = self.scalar(value, scope)?;
115 let q = self.rel(query, scope)?;
116 if q.fields().len() != 1 {
117 return Err(AdmissionError::ScalarQueryArity);
118 }
119 compatible(&l, &q.fields()[0])?;
120 Ok(bool_ty())
121 }
122 Scalar::ScalarQuery(q) => {
123 let row = self.rel(q, scope)?;
124 if row.fields().len() != 1 {
125 return Err(AdmissionError::ScalarQueryArity);
126 }
127 Ok(row.fields()[0].clone())
128 }
129 Scalar::Case {
130 branches,
131 otherwise,
132 } => {
133 let mut out = None;
134 let mut nullable = otherwise.is_none();
135 for (p, v) in branches {
136 require_bool(&self.scalar(p, scope)?)?;
137 let t = self.scalar(v, scope)?;
138 if let Some(old) = &out {
139 compatible(old, &t)?;
140 }
141 nullable |= t.nullable;
142 out = Some(t);
143 }
144 if let Some(v) = otherwise {
145 let t = self.scalar(v, scope)?;
146 if let Some(old) = &out {
147 compatible(old, &t)?;
148 }
149 nullable |= t.nullable;
150 out = Some(t);
151 }
152 let mut out = out.ok_or(AdmissionError::TypeError("empty case"))?;
153 out.nullable = nullable;
154 Ok(out)
155 }
156 Scalar::Call(op, args) => self.call(*op, args, scope),
157 }
158 }
159 fn call(
160 &self,
161 op: ScalarOp,
162 args: &[Scalar],
163 scope: &Scope,
164 ) -> Result<FieldType, AdmissionError> {
165 let ts: Vec<_> = args
166 .iter()
167 .map(|v| self.scalar(v, scope))
168 .collect::<Result<_, _>>()?;
169 let arity = |n| {
170 if ts.len() == n {
171 Ok(())
172 } else {
173 Err(AdmissionError::TypeError("operator arity"))
174 }
175 };
176 match op {
177 ScalarOp::Not | ScalarOp::IsNull => {
178 arity(1)?;
179 if op == ScalarOp::Not {
180 require_bool(&ts[0])?;
181 }
182 Ok(FieldType {
183 name: fname("value"),
184 domain: sim_relation_core::BaseDomain::Bool.id(),
185 nullable: false,
186 })
187 }
188 ScalarOp::And | ScalarOp::Or => {
189 if ts.len() < 2 {
190 return Err(AdmissionError::TypeError("operator arity"));
191 }
192 for t in &ts {
193 require_bool(t)?;
194 }
195 Ok(FieldType {
196 name: fname("value"),
197 domain: sim_relation_core::BaseDomain::Bool.id(),
198 nullable: ts.iter().any(|t| t.nullable),
199 })
200 }
201 ScalarOp::Eq
202 | ScalarOp::Ne
203 | ScalarOp::Lt
204 | ScalarOp::Le
205 | ScalarOp::Gt
206 | ScalarOp::Ge => {
207 arity(2)?;
208 compatible(&ts[0], &ts[1])?;
209 let needed = if matches!(op, ScalarOp::Eq | ScalarOp::Ne) {
210 DomainTrait::Equatable
211 } else {
212 DomainTrait::Ordered
213 };
214 require_trait(self.domains, &ts[0].domain, needed)?;
215 Ok(FieldType {
216 name: fname("value"),
217 domain: sim_relation_core::BaseDomain::Bool.id(),
218 nullable: ts.iter().any(|t| t.nullable),
219 })
220 }
221 ScalarOp::Add | ScalarOp::Sub | ScalarOp::Mul | ScalarOp::Div => {
222 arity(2)?;
223 compatible(&ts[0], &ts[1])?;
224 require_trait(self.domains, &ts[0].domain, DomainTrait::Ordered)?;
225 let mut t = ts[0].clone();
226 t.nullable = ts.iter().any(|t| t.nullable);
227 Ok(t)
228 }
229 ScalarOp::Coalesce => {
230 if ts.is_empty() {
231 return Err(AdmissionError::TypeError("empty coalesce"));
232 }
233 for t in &ts[1..] {
234 compatible(&ts[0], t)?;
235 }
236 let mut t = ts[0].clone();
237 t.nullable = ts.iter().all(|t| t.nullable);
238 Ok(t)
239 }
240 }
241 }
242 fn aggregate(&self, a: &Aggregate, scope: &Scope) -> Result<FieldType, AdmissionError> {
243 match a {
244 Aggregate::CountAll | Aggregate::Count(_) => {
245 if let Aggregate::Count(v) = a {
246 self.scalar(v, scope)?;
247 }
248 Ok(FieldType {
249 name: fname("value"),
250 domain: sim_relation_core::BaseDomain::I64.id(),
251 nullable: false,
252 })
253 }
254 Aggregate::Sum(v) | Aggregate::Min(v) | Aggregate::Max(v) => {
255 let mut t = self.scalar(v, scope)?;
256 require_trait(self.domains, &t.domain, DomainTrait::Ordered)?;
257 t.nullable = true;
258 Ok(t)
259 }
260 }
261 }
262 fn rel(&self, rel: &Rel, outer: &Scope) -> Result<RowType, AdmissionError> {
263 match rel {
264 Rel::Scan { table, bind, .. } => {
265 let row = self.table_row(self.table(table)?, false)?;
266 let mut s = outer.clone();
267 Self::bind(&mut s, bind.clone(), row.clone())?;
268 Ok(row)
269 }
270 Rel::Values {
271 bind,
272 row_type,
273 rows,
274 } => {
275 if rows.len() > self.limits.max_literal_rows {
276 return Err(AdmissionError::LiteralRowLimit {
277 limit: self.limits.max_literal_rows,
278 actual: rows.len(),
279 });
280 }
281 if rows.iter().any(|r| r.row_type() != row_type) {
282 return Err(AdmissionError::TypeError("values row type"));
283 }
284 let mut s = outer.clone();
285 Self::bind(&mut s, bind.clone(), row_type.clone())?;
286 Ok(row_type.clone())
287 }
288 Rel::Project {
289 input,
290 bind,
291 fields,
292 } => {
293 let input_ty = self.rel(input, outer)?;
294 let mut s = outer.clone();
295 self.visible(input, &input_ty, &mut s)?;
296 let row = named_row(fields.iter().map(|n| (&n.name, &n.scalar)), |v| {
297 self.scalar(v, &s)
298 })?;
299 Self::bind(&mut s, bind.clone(), row.clone())?;
300 Ok(row)
301 }
302 Rel::Filter { input, predicate } => {
303 let row = self.rel(input, outer)?;
304 let mut s = outer.clone();
305 self.visible(input, &row, &mut s)?;
306 require_bool(&self.scalar(predicate, &s)?)?;
307 Ok(row)
308 }
309 Rel::Join {
310 left,
311 right,
312 kind,
313 on,
314 } => {
315 let l = self.rel(left, outer)?;
316 let mut s = outer.clone();
317 self.visible(left, &l, &mut s)?;
318 let r = self.rel(right, &s)?;
319 self.visible(right, &r, &mut s)?;
320 if *kind != JoinKind::Cross {
321 require_bool(&self.scalar(on, &s)?)?;
322 }
323 let mut fields = Vec::new();
324 for (binding, row) in &s {
325 if outer.contains_key(binding) {
326 continue;
327 }
328 for f in row.fields() {
329 let mut f = f.clone();
330 f.name = FieldName::new(Symbol::qualified(
331 binding.symbol().name.clone(),
332 f.name.symbol().name.clone(),
333 ))
334 .expect("qualified");
335 if *kind == JoinKind::Left && !scope_contains(left, binding) {
336 f.nullable = true;
337 }
338 fields.push(f);
339 }
340 }
341 RowType::new(fields).map_err(|_| AdmissionError::DuplicateName)
342 }
343 Rel::Group {
344 input,
345 bind,
346 keys,
347 aggregates,
348 having,
349 } => {
350 let inrow = self.rel(input, outer)?;
351 let mut s = outer.clone();
352 self.visible(input, &inrow, &mut s)?;
353 let mut fields = Vec::new();
354 for n in keys {
355 let mut t = self.scalar(&n.scalar, &s)?;
356 t.name = n.name.clone();
357 fields.push(t);
358 }
359 for n in aggregates {
360 let mut t = self.aggregate(&n.aggregate, &s)?;
361 t.name = n.name.clone();
362 fields.push(t);
363 }
364 let row = RowType::new(fields).map_err(|_| AdmissionError::DuplicateName)?;
365 let mut hs = outer.clone();
366 Self::bind(&mut hs, bind.clone(), row.clone())?;
367 if let Some(v) = having {
368 require_bool(&self.scalar(v, &hs)?)?;
369 }
370 Ok(row)
371 }
372 Rel::Set { inputs, .. } => {
373 if inputs.len() < 2 {
374 return Err(AdmissionError::IncompatibleSet);
375 }
376 let first = self.rel(&inputs[0], outer)?;
377 for v in &inputs[1..] {
378 let r = self.rel(v, outer)?;
379 if r.fields().len() != first.fields().len() {
380 return Err(AdmissionError::IncompatibleSet);
381 }
382 for (a, b) in first.fields().iter().zip(r.fields()) {
383 compatible(a, b).map_err(|_| AdmissionError::IncompatibleSet)?;
384 }
385 }
386 Ok(first)
387 }
388 Rel::Distinct(v) => self.rel(v, outer),
389 Rel::Order { input, keys } => {
390 let row = self.rel(input, outer)?;
391 let mut s = outer.clone();
392 self.visible(input, &row, &mut s)?;
393 for k in keys {
394 let t = self.scalar(&k.scalar, &s)?;
395 require_trait(self.domains, &t.domain, DomainTrait::Ordered)?;
396 }
397 Ok(row)
398 }
399 Rel::Limit { input, .. } => self.rel(input, outer),
400 }
401 }
402}
403
404pub fn admit_query(
406 raw: Rel,
407 schema: &Schema,
408 domains: &DomainCatalog,
409 parameters: RowType,
410 limits: AdmissionLimits,
411) -> Result<CheckedQuery, AdmissionError> {
412 let a = Admit {
413 schema,
414 domains,
415 params: ¶meters,
416 limits,
417 };
418 let output = a.rel(&raw, &Scope::new())?;
419 let schema_id = schema
420 .id()
421 .map_err(|_| AdmissionError::TypeError("schema identity"))?;
422 let catalog_id =
423 RelationId::of(domains).map_err(|_| AdmissionError::TypeError("catalog identity"))?;
424 let plan_id = RelationId::of(&QueryDatum {
425 raw: &raw,
426 parameters: ¶meters,
427 })
428 .map_err(|_| AdmissionError::TypeError("plan identity"))?;
429 Ok(CheckedQuery {
430 schema_id,
431 catalog_id,
432 parameters,
433 output,
434 plan_id,
435 raw,
436 })
437}
438pub fn admit_mutation(
440 raw: Mutation,
441 schema: &Schema,
442 domains: &DomainCatalog,
443 parameters: RowType,
444 limits: AdmissionLimits,
445) -> Result<CheckedMutation, AdmissionError> {
446 let a = Admit {
447 schema,
448 domains,
449 params: ¶meters,
450 limits,
451 };
452 let mut scope = Scope::new();
453 let output = match &raw {
454 Mutation::Insert {
455 table,
456 columns,
457 input,
458 conflict,
459 returning,
460 } => {
461 let t = a.table(table)?;
462 let input_ty = a.rel(input, &scope)?;
463 if columns.len() != input_ty.fields().len() {
464 return Err(AdmissionError::TypeError("insert arity"));
465 }
466 let mut seen = BTreeSet::new();
467 for (c, f) in columns.iter().zip(input_ty.fields()) {
468 if !seen.insert(c) {
469 return Err(AdmissionError::DuplicateName);
470 }
471 let target = t
472 .columns()
473 .iter()
474 .find(|x| x.name() == c)
475 .ok_or(AdmissionError::TypeError("unknown insert column"))?;
476 compatible(
477 &FieldType {
478 name: fname("target"),
479 domain: target.domain().clone(),
480 nullable: target.nullable(),
481 },
482 f,
483 )?;
484 }
485 for c in t.columns() {
486 if !c.nullable()
487 && !c.has_default()
488 && !c.is_generated()
489 && !columns.contains(c.name())
490 {
491 return Err(AdmissionError::MissingRequiredInsertField(c.name().clone()));
492 }
493 }
494 validate_conflict(conflict, t, &a, &mut scope)?;
495 if !scope.contains_key(&bname("target")) {
496 let target_row = a.table_row(t, false)?;
497 Admit::bind(&mut scope, bname("target"), target_row)?;
498 }
499 named_row(returning.iter().map(|n| (&n.name, &n.scalar)), |v| {
500 a.scalar(v, &scope)
501 })?
502 }
503 Mutation::Update {
504 table,
505 bind,
506 assignments,
507 predicate,
508 returning,
509 } => {
510 let t = a.table(table)?;
511 Admit::bind(&mut scope, bind.clone(), a.table_row(t, false)?)?;
512 validate_assignments(assignments, t, &a, &scope)?;
513 if let Some(v) = predicate {
514 require_bool(&a.scalar(v, &scope)?)?;
515 }
516 named_row(returning.iter().map(|n| (&n.name, &n.scalar)), |v| {
517 a.scalar(v, &scope)
518 })?
519 }
520 Mutation::Delete {
521 table,
522 bind,
523 predicate,
524 returning,
525 } => {
526 let t = a.table(table)?;
527 Admit::bind(&mut scope, bind.clone(), a.table_row(t, false)?)?;
528 if let Some(v) = predicate {
529 require_bool(&a.scalar(v, &scope)?)?;
530 }
531 named_row(returning.iter().map(|n| (&n.name, &n.scalar)), |v| {
532 a.scalar(v, &scope)
533 })?
534 }
535 };
536 let schema_id = schema
537 .id()
538 .map_err(|_| AdmissionError::TypeError("schema identity"))?;
539 let catalog_id =
540 RelationId::of(domains).map_err(|_| AdmissionError::TypeError("catalog identity"))?;
541 let plan_id = RelationId::of(&MutationDatum(&raw))
542 .map_err(|_| AdmissionError::TypeError("plan identity"))?;
543 Ok(CheckedMutation {
544 schema_id,
545 catalog_id,
546 parameters,
547 output,
548 plan_id,
549 raw,
550 })
551}
552
553fn validate_assignments(
554 v: &[(ColumnName, Scalar)],
555 t: &Table,
556 a: &Admit<'_>,
557 s: &Scope,
558) -> Result<(), AdmissionError> {
559 let mut seen = BTreeSet::new();
560 for (c, x) in v {
561 if !seen.insert(c) {
562 return Err(AdmissionError::DuplicateName);
563 }
564 let col = t
565 .columns()
566 .iter()
567 .find(|z| z.name() == c)
568 .ok_or(AdmissionError::TypeError("unknown assignment column"))?;
569 compatible(
570 &FieldType {
571 name: fname("target"),
572 domain: col.domain().clone(),
573 nullable: col.nullable(),
574 },
575 &a.scalar(x, s)?,
576 )?;
577 }
578 Ok(())
579}
580fn validate_conflict(
581 c: &ConflictAction,
582 t: &Table,
583 a: &Admit<'_>,
584 s: &mut Scope,
585) -> Result<(), AdmissionError> {
586 let (target, updates, pred) = match c {
587 ConflictAction::Fail => return Ok(()),
588 ConflictAction::DoNothing { target } => (target, None, None),
589 ConflictAction::DoUpdate {
590 target,
591 assignments,
592 predicate,
593 } => (target, Some(assignments), predicate.as_ref()),
594 };
595 let valid = match target {
596 ConflictTarget::PrimaryKey => t
597 .constraints()
598 .iter()
599 .any(|c| matches!(c, Constraint::Primary(_))),
600 ConflictTarget::UniqueConstraint(n) => t
601 .constraints()
602 .iter()
603 .any(|c| matches!(c,Constraint::Unique(v)if &v.name==n)),
604 ConflictTarget::Columns(cols) => t.constraints().iter().any(|c| match c {
605 Constraint::Primary(v) => v.columns == *cols,
606 Constraint::Unique(v) => v.columns == *cols,
607 _ => false,
608 }),
609 };
610 if !valid {
611 return Err(AdmissionError::UnsafeConflictTarget);
612 }
613 if let Some(v) = updates {
614 Admit::bind(s, bname("target"), a.table_row(t, false)?)?;
615 Admit::bind(s, bname("excluded"), a.table_row(t, false)?)?;
616 validate_assignments(v, t, a, s)?;
617 if let Some(p) = pred {
618 require_bool(&a.scalar(p, s)?)?;
619 }
620 }
621 Ok(())
622}
623fn named_row<'a, T: 'a>(
624 values: impl Iterator<Item = (&'a FieldName, &'a T)>,
625 mut check: impl FnMut(&T) -> Result<FieldType, AdmissionError>,
626) -> Result<RowType, AdmissionError> {
627 let mut out = Vec::new();
628 for (n, v) in values {
629 let mut t = check(v)?;
630 t.name = n.clone();
631 out.push(t);
632 }
633 RowType::new(out).map_err(|_| AdmissionError::DuplicateName)
634}
635fn compatible(a: &FieldType, b: &FieldType) -> Result<(), AdmissionError> {
636 if a.domain == b.domain {
637 Ok(())
638 } else {
639 Err(AdmissionError::TypeError("domain mismatch"))
640 }
641}
642fn require_bool(v: &FieldType) -> Result<(), AdmissionError> {
643 if v.domain == sim_relation_core::BaseDomain::Bool.id() {
644 Ok(())
645 } else {
646 Err(AdmissionError::TypeError("boolean required"))
647 }
648}
649fn require_trait(c: &DomainCatalog, d: &DomainId, t: DomainTrait) -> Result<(), AdmissionError> {
650 if c.get(d).is_some_and(|x| x.traits().contains(&t)) {
651 Ok(())
652 } else {
653 Err(AdmissionError::TypeError("domain trait missing"))
654 }
655}
656fn scope_contains(rel: &Rel, binding: &BindingName) -> bool {
657 match rel {
658 Rel::Scan { bind, .. }
659 | Rel::Values { bind, .. }
660 | Rel::Project { bind, .. }
661 | Rel::Group { bind, .. } => bind == binding,
662 Rel::Join { left, right, .. } => {
663 scope_contains(left, binding) || scope_contains(right, binding)
664 }
665 Rel::Filter { input, .. }
666 | Rel::Distinct(input)
667 | Rel::Order { input, .. }
668 | Rel::Limit { input, .. } => scope_contains(input, binding),
669 Rel::Set { .. } => false,
670 }
671}
672fn fname(v: &str) -> FieldName {
673 FieldName::new(Symbol::new(v)).expect("literal")
674}
675fn bname(v: &str) -> BindingName {
676 BindingName::new(Symbol::new(v)).expect("literal")
677}