1use super::{action_binding::RuleSourceCatalog, binding::RuleColumnMetadata, RuleCatalog};
9use crate::semantics::returning::ReturningCatalog;
10use crate::{ast::RuleEvent, SQLError};
11use std::collections::{BTreeMap, BTreeSet};
12#[derive(Clone, Copy)]
13pub struct RuleAnalysisContext<'a> {
14 pub rules: &'a dyn RuleCatalog,
15 pub sources: &'a dyn RuleSourceCatalog,
16 pub returning: &'a dyn ReturningCatalog,
17}
18pub fn rule_condition_plan_references_row(rule: &crate::catalog::events::StoredRule) -> bool {
19 rule.bound_condition_plan().is_some_and(|(plan, binding)| {
20 super::action_binding::rule_condition_plan_references_whole_row(plan)
21 || !super::action_binding::rule_condition_plan_row_columns(plan, binding).is_empty()
22 })
23}
24
25pub fn relation_suppresses_original_query(
26 context: RuleAnalysisContext<'_>,
27 table: &str,
28 event: RuleEvent,
29) -> Result<bool, SQLError> {
30 Ok(context
31 .rules
32 .rules_for(table, event)?
33 .iter()
34 .any(|rule| rule.definition.instead && rule.definition.condition.is_none()))
35}
36
37pub fn relation_rules_reference_row(
38 context: RuleAnalysisContext<'_>,
39 table: &str,
40 event: RuleEvent,
41) -> Result<bool, SQLError> {
42 for rule in context.rules.rules_for(table, event)? {
43 let condition_references_row = if rule.bound_condition_plan().is_some() {
44 rule_condition_plan_references_row(&rule)
45 } else {
46 rule.definition
47 .condition
48 .as_ref()
49 .is_some_and(super::action_binding::rule_expr_references_row)
50 };
51 if condition_references_row {
52 return Ok(true);
53 }
54 for action in &rule.definition.actions {
55 let target_columns =
56 super::action_binding::rule_action_target_columns(context.sources, action)?;
57 if super::action_binding::rule_statement_references_row(
58 context.sources,
59 action,
60 &target_columns,
61 )? {
62 return Ok(true);
63 }
64 }
65 }
66 Ok(false)
67}
68
69pub fn relation_rules_require_event_rows(
70 context: RuleAnalysisContext<'_>,
71 table: &str,
72 event: RuleEvent,
73) -> Result<bool, SQLError> {
74 Ok(context
75 .rules
76 .rules_for(table, event)?
77 .iter()
78 .any(|rule| rule.definition.condition.is_some() || !rule.definition.actions.is_empty()))
79}
80
81pub fn surviving_view_rules_reference_row(
82 context: RuleAnalysisContext<'_>,
83 relations: &[String],
84 event: RuleEvent,
85) -> Result<bool, SQLError> {
86 let mut references_row = false;
87 for relation in relations {
88 references_row |= relation_rules_reference_row(context, relation, event)?;
89 if relation_suppresses_original_query(context, relation, event)? {
90 break;
91 }
92 }
93 Ok(references_row)
94}
95
96pub fn surviving_view_rules_require_event_rows(
97 context: RuleAnalysisContext<'_>,
98 relations: &[String],
99 event: RuleEvent,
100) -> Result<bool, SQLError> {
101 let mut requires_rows = false;
102 for relation in relations {
103 requires_rows |= relation_rules_require_event_rows(context, relation, event)?;
104 if relation_suppresses_original_query(context, relation, event)? {
105 break;
106 }
107 }
108 Ok(requires_rows)
109}
110
111pub fn relation_condition_row_columns(
112 context: RuleAnalysisContext<'_>,
113 table: &str,
114 event: RuleEvent,
115) -> Result<BTreeSet<String>, SQLError> {
116 let mut columns = BTreeSet::new();
117 for rule in context.rules.rules_for(table, event)? {
118 if let Some((plan, binding)) = rule.bound_condition_plan() {
119 columns.extend(super::action_binding::rule_condition_plan_row_columns(
120 plan, binding,
121 ));
122 continue;
123 }
124 if let Some(condition) = rule.definition.condition.as_ref() {
125 columns.extend(super::action_binding::rule_expr_row_columns(condition));
126 }
127 }
128 Ok(columns)
129}
130
131pub fn relation_rule_row_columns(
132 context: RuleAnalysisContext<'_>,
133 table: &str,
134 event: RuleEvent,
135) -> Result<Option<BTreeSet<String>>, SQLError> {
136 let mut columns = BTreeSet::new();
137 let mut references_row = false;
138 let mut references_whole_row = false;
139 for rule in context.rules.rules_for(table, event)? {
140 if let Some((plan, binding)) = rule.bound_condition_plan() {
141 let plan_references_row = rule_condition_plan_references_row(&rule);
142 references_row |= plan_references_row;
143 references_whole_row |=
144 super::action_binding::rule_condition_plan_references_whole_row(plan);
145 columns.extend(super::action_binding::rule_condition_plan_row_columns(
146 plan, binding,
147 ));
148 } else if let Some(condition) = rule.definition.condition.as_ref() {
149 references_row |= super::action_binding::rule_expr_references_row(condition);
150 references_whole_row |=
151 super::action_binding::rule_expr_references_whole_row(condition);
152 columns.extend(super::action_binding::rule_expr_row_columns(condition));
153 }
154 for action in &rule.definition.actions {
155 let action_columns =
156 super::action_binding::rule_action_target_columns(context.sources, action)?;
157 references_row |= super::action_binding::rule_statement_references_row(
158 context.sources,
159 action,
160 &action_columns,
161 )?;
162 references_whole_row |= super::action_binding::rule_statement_references_whole_row(
163 context.sources,
164 action,
165 &action_columns,
166 )?;
167 columns.extend(super::action_binding::rule_statement_row_columns(
168 context.sources,
169 action,
170 &action_columns,
171 )?);
172 }
173 }
174 if references_whole_row || references_row && columns.is_empty() {
175 Ok(None)
176 } else {
177 Ok(Some(columns))
178 }
179}
180
181pub fn rule_columns(
182 context: RuleAnalysisContext<'_>,
183 table: &str,
184) -> Result<BTreeMap<String, RuleColumnMetadata>, SQLError> {
185 let columns = context
186 .returning
187 .try_describe_table_row_type(table)
188 .map_err(|error| SQLError::Internal(format!("read rule row type: {error}")))?;
189 if let Some(columns) = columns {
190 return Ok(columns
191 .into_iter()
192 .enumerate()
193 .map(|(position, column)| {
194 let uses_document_id = column.primary_key && column.ty.is_integer();
195 (
196 column.name,
197 RuleColumnMetadata {
198 ty: column.ty,
199 uses_document_id,
200 position,
201 },
202 )
203 })
204 .collect());
205 }
206 Ok(context
207 .sources
208 .rule_relation_columns(table)?
209 .into_iter()
210 .enumerate()
211 .map(|(position, (name, ty))| {
212 (
213 name,
214 RuleColumnMetadata {
215 ty,
216 uses_document_id: false,
217 position,
218 },
219 )
220 })
221 .collect())
222}
223
224pub fn rule_returning_columns(
225 context: RuleAnalysisContext<'_>,
226 table: &str,
227) -> Result<Vec<crate::ast::ColumnDef>, SQLError> {
228 let columns = context
229 .returning
230 .try_describe_table_row_type(table)
231 .map_err(|error| SQLError::Internal(format!("read rule RETURNING row type: {error}")))?;
232 if let Some(columns) = columns {
233 return Ok(columns);
234 }
235 Ok(context
236 .sources
237 .rule_relation_columns(table)?
238 .into_iter()
239 .map(|(name, ty)| crate::ast::ColumnDef {
240 name,
241 ty,
242 object_id: None,
243 missing_value: None,
244 primary_key: false,
245 not_null: false,
246 not_null_explicit: false,
247 not_null_name: None,
248 not_null_validated: true,
249 not_null_no_inherit: false,
250 not_null_is_local: true,
251 auto_increment: None,
252 unique: false,
253 default: None,
254 generated: None,
255 check: None,
256 check_name: None,
257 check_enforced: true,
258 check_validated: true,
259 check_no_inherit: false,
260 check_is_local: true,
261 check_object_id: None,
262 references: None,
263 })
264 .collect())
265}