uqa_sql/semantics/
rules.rs1use crate::{
10 ast::{RuleEvent, Statement},
11 catalog::events::StoredRule,
12 SQLError,
13};
14use uqa_core::RelationIdentity;
15
16pub trait RuleCatalog {
18 fn relation_has_rules(&self, table: &str) -> Result<bool, SQLError>;
19 fn resolve_rule_relation(&self, table: &str) -> Result<RelationIdentity, SQLError>;
20 fn rules_for(&self, table: &str, event: RuleEvent) -> Result<Vec<StoredRule>, SQLError>;
21 fn resolve_mutation_target(&self, name: &str, bound: bool) -> Result<String, SQLError>;
22}
23
24pub mod binding;
25pub fn validate_rule_returning_contract(
26 catalog: &dyn RuleCatalog,
27 table: &str,
28 event: RuleEvent,
29 requested: bool,
30) -> Result<(), SQLError> {
31 if !requested {
32 return Ok(());
33 }
34 let table = catalog.resolve_rule_relation(table)?.qualified_name();
35 let rules = catalog.rules_for(&table, event)?;
36 if rules.is_empty() || !rules.iter().any(|rule| rule.definition.instead) {
37 return Ok(());
38 }
39 let providers = rules
40 .iter()
41 .flat_map(|rule| &rule.definition.actions)
42 .filter(|action| statement_has_returning(action))
43 .count();
44 if providers > 1 {
45 return Err(SQLError::Routine {
46 sqlstate: "0A000".into(),
47 message: "cannot have RETURNING lists in multiple rules".into(),
48 });
49 }
50 if providers == 1 {
51 return Ok(());
52 }
53 let relation = RelationIdentity::from_legacy_name(&table)
54 .map_err(|error| SQLError::Internal(format!("decode rule relation `{table}`: {error}")))?;
55 let event = rule_event_name(event);
56 Err(SQLError::Diagnostic {
57 sqlstate: "0A000".into(),
58 message: format!(
59 "cannot perform {event} RETURNING on relation \"{}\"",
60 relation.name
61 ),
62 detail: None,
63 hint: Some(format!(
64 "You need an unconditional ON {event} DO INSTEAD rule with a RETURNING clause."
65 )),
66 })
67}
68
69const fn rule_event_name(event: RuleEvent) -> &'static str {
70 match event {
71 RuleEvent::Select => "SELECT",
72 RuleEvent::Insert => "INSERT",
73 RuleEvent::Update => "UPDATE",
74 RuleEvent::Delete => "DELETE",
75 }
76}
77
78pub fn statement_has_returning(statement: &Statement) -> bool {
79 match statement {
80 Statement::Insert(statement) => !statement.returning.is_empty(),
81 Statement::Update(statement) => !statement.returning.is_empty(),
82 Statement::Delete(statement) => !statement.returning.is_empty(),
83 _ => false,
84 }
85}
86
87pub fn clear_statement_returning(statement: &mut Statement) {
88 match statement {
89 Statement::Insert(statement) => statement.returning.clear(),
90 Statement::Update(statement) => statement.returning.clear(),
91 Statement::Delete(statement) => statement.returning.clear(),
92 _ => {}
93 }
94}
95
96pub mod action_binding;
97
98pub mod analysis;
99pub mod returning;
100
101pub mod insert_inputs;