uqa_sql/semantics/view_rewrite/
rule_inputs.rs1use super::{
10 automatic_view_layer, has_instead_of_trigger, BTreeSet, SQLError, TriggerEvent,
11 ViewRewriteContext,
12};
13use crate::ast::RuleEvent;
14
15pub struct RuleInputRequirements {
16 pub columns: BTreeSet<String>,
17 pub requires_rows: bool,
18}
19
20pub fn rule_input_requirements(
21 services: ViewRewriteContext<'_>,
22 table: &str,
23 event: RuleEvent,
24) -> Result<Option<RuleInputRequirements>, SQLError> {
25 collect_requirements(services, table, event, &mut BTreeSet::new())
26}
27
28fn collect_requirements(
29 services: ViewRewriteContext<'_>,
30 table: &str,
31 event: RuleEvent,
32 visited: &mut BTreeSet<String>,
33) -> Result<Option<RuleInputRequirements>, SQLError> {
34 if !visited.insert(table.to_string()) {
35 return Err(SQLError::Internal(format!(
36 "cycle while resolving rewrite-rule inputs for `{table}`"
37 )));
38 }
39 let rules = services.catalog.rules_for(table, event)?;
40 let suppresses = rules
41 .iter()
42 .any(|rule| rule.definition.instead && rule.definition.condition.is_none());
43 let mut required = RuleInputRequirements {
44 columns: BTreeSet::new(),
45 requires_rows: rules
46 .iter()
47 .any(|rule| rule.definition.condition.is_some() || !rule.definition.actions.is_empty()),
48 };
49 for rule in &rules {
50 let Some(columns) = services.catalog.rule_new_row_columns(rule)? else {
51 return Ok(None);
52 };
53 required.columns.extend(columns);
54 }
55 if suppresses {
56 return Ok(Some(required));
57 }
58 let trigger = match event {
59 RuleEvent::Insert => TriggerEvent::Insert,
60 RuleEvent::Update => TriggerEvent::Update,
61 RuleEvent::Delete => TriggerEvent::Delete,
62 RuleEvent::Select => return Ok(None),
63 };
64 if has_instead_of_trigger(services, table, trigger)? {
65 return Ok(None);
66 }
67 let Some(layer) = automatic_view_layer(services, table)? else {
68 return Ok(None);
69 };
70 let Some(source) = collect_requirements(services, &layer.source_name, event, visited)? else {
71 return Ok(None);
72 };
73 required.requires_rows |= source.requires_rows;
74 for column in layer.columns {
75 if column
76 .writable_source_column
77 .as_ref()
78 .is_some_and(|name| source.columns.contains(name))
79 {
80 required.columns.insert(column.name);
81 }
82 }
83 Ok(Some(required))
84}
85
86pub fn validate_grouped_rule_inputs(
88 services: ViewRewriteContext<'_>,
89 table: &str,
90 assignments: &[crate::plan::AssignmentPlan],
91) -> Result<(), SQLError> {
92 let mut columns = assignments
93 .iter()
94 .filter(|assignment| {
95 matches!(
96 assignment.target,
97 crate::ast::AssignmentTargets::Multiple(_)
98 )
99 })
100 .flat_map(|assignment| assignment.target.column_names())
101 .map(str::to_owned)
102 .collect::<BTreeSet<_>>();
103 let mut table = table.to_owned();
104 let mut visited = BTreeSet::new();
105 while !columns.is_empty() && visited.insert(table.clone()) {
106 let rules = services.catalog.rules_for(&table, RuleEvent::Update)?;
107 for rule in &rules {
108 if services
109 .catalog
110 .rule_new_row_columns(rule)?
111 .is_none_or(|new| !new.is_disjoint(&columns))
112 {
113 return Err(SQLError::Routine {
114 sqlstate: "0A000".into(),
115 message: "NEW variables in ON UPDATE rules cannot reference columns that are part of a multiple assignment in the subject UPDATE command".into(),
116 });
117 }
118 }
119 if rules
120 .iter()
121 .any(|rule| rule.definition.instead && rule.definition.condition.is_none())
122 || has_instead_of_trigger(services, &table, TriggerEvent::Update)?
123 {
124 break;
125 }
126 let Some(layer) = automatic_view_layer(services, &table)? else {
127 break;
128 };
129 columns = layer
130 .columns
131 .into_iter()
132 .filter(|column| columns.contains(&column.name))
133 .filter_map(|column| column.writable_source_column)
134 .collect();
135 table = layer.source_name;
136 }
137 Ok(())
138}