1use super::EventAnalysisContext;
8use crate::{
9 ast::{ColumnType, CreateRule, Expr, RuleEvent, Statement},
10 catalog::{
11 events::{
12 validation::{
13 first_invalid_rule_condition_qualifier, is_boolean_type, rule_action_has_returning,
14 validate_rule_action_reference_scopes, validate_rule_returning_shape,
15 RuleConditionNameResolver, RuleRowTypeResolver,
16 },
17 RuleConditionBinding, RuleDependencies,
18 },
19 resolution::{RelationLookupMode, RelationResolution},
20 view::StoredViewKind,
21 },
22 plpgsql::bind_expr,
23 semantics::rules::action_binding::rule_action_has_set_operation,
24 SQLError,
25};
26use uqa_core::{RelationIdentity, Value};
27
28#[cfg(test)]
29mod tests;
30
31fn rule_condition_has_subquery(condition: &Expr) -> bool {
32 condition.any_node(&|node| {
33 matches!(
34 node,
35 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. }
36 )
37 })
38}
39
40fn validate_rule_action_contract(definition: &CreateRule) -> Result<(), SQLError> {
41 if definition.condition.is_some()
42 && definition.actions.iter().any(rule_action_has_set_operation)
43 {
44 return Err(SQLError::Routine {
45 sqlstate: "0A000".into(),
46 message: "conditional UNION/INTERSECT/EXCEPT statements are not implemented".into(),
47 });
48 }
49 let returning_actions = definition
50 .actions
51 .iter()
52 .filter(|action| rule_action_has_returning(action))
53 .count();
54 if returning_actions > 1 {
55 return Err(SQLError::Routine {
56 sqlstate: "0A000".into(),
57 message: "cannot have multiple RETURNING lists in a rule".into(),
58 });
59 }
60 if returning_actions != 0 && definition.condition.is_some() {
61 return Err(SQLError::Routine {
62 sqlstate: "0A000".into(),
63 message: "RETURNING lists are not supported in conditional rules".into(),
64 });
65 }
66 if returning_actions != 0 && !definition.instead {
67 return Err(SQLError::Routine {
68 sqlstate: "0A000".into(),
69 message: "RETURNING lists are not supported in non-INSTEAD rules".into(),
70 });
71 }
72 Ok(())
73}
74
75impl EventAnalysisContext<'_> {
76 fn resolve_rule_event_relation_kind(
77 &self,
78 name: &str,
79 lookup_mode: RelationLookupMode,
80 ) -> Result<(RelationIdentity, &'static str), SQLError> {
81 let resolution = match lookup_mode {
82 RelationLookupMode::Dynamic => self.relations.resolve_visible_relation_kind(name)?,
83 RelationLookupMode::Bound => self.relations.resolve_bound_relation_kind(name)?,
84 };
85 if let RelationResolution::Found(canonical, "foreign table") = &resolution {
86 let relation = RelationIdentity::from_legacy_name(canonical).map_err(|error| {
87 SQLError::Internal(format!(
88 "decode resolved rule relation `{canonical}`: {error}"
89 ))
90 })?;
91 return Err(SQLError::Routine {
92 sqlstate: "42809".into(),
93 message: format!("\"{}\" is a foreign table", relation.name),
94 });
95 }
96 Self::event_relation_from_resolution(name, resolution)
97 }
98
99 fn resolve_visible_rule_action_relation(
100 &self,
101 name: &str,
102 ) -> Result<RelationIdentity, SQLError> {
103 let Some((canonical, kind)) = self
104 .relations
105 .resolve_visible_relation_kind(name)?
106 .into_found()
107 else {
108 return Err(SQLError::UnknownTable(name.to_string()));
109 };
110 if !matches!(kind, "table" | "view" | "materialized view") {
111 return Err(SQLError::UnknownTable(name.to_string()));
112 }
113 RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
114 SQLError::Internal(format!(
115 "decode resolved rule action relation `{canonical}`: {error}"
116 ))
117 })
118 }
119
120 pub fn resolve_rule_relation(&self, name: &str) -> Result<RelationIdentity, SQLError> {
121 let RelationResolution::Found(canonical, kind) =
122 self.relations.resolve_bound_relation_kind(name)?
123 else {
124 return Err(SQLError::UnknownTable(name.to_string()));
125 };
126 if !matches!(kind, "table" | "view" | "materialized view") {
127 return Err(SQLError::UnknownTable(name.to_string()));
128 }
129 RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
130 SQLError::Internal(format!("decode bound rule relation `{canonical}`: {error}"))
131 })
132 }
133
134 pub fn rule_relation_columns(&self, name: &str) -> Result<Vec<(String, ColumnType)>, SQLError> {
135 if let Some(columns) = self
136 .returning
137 .catalog
138 .try_describe_table_row_type(name)
139 .map_err(|error| SQLError::Internal(format!("read rule columns: {error}")))?
140 {
141 return Ok(columns
142 .into_iter()
143 .map(|column| (column.name, column.ty))
144 .collect());
145 }
146 let relation = RelationIdentity::from_legacy_name(name).map_err(|error| {
147 SQLError::Internal(format!("decode rule relation `{name}`: {error}"))
148 })?;
149 let view = self
150 .catalog
151 .view(&relation)
152 .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?;
153 let schema = self.catalog.stored_view_schema(&view)?;
154 Ok(schema
155 .columns()
156 .iter()
157 .enumerate()
158 .map(|(index, name)| {
159 (
160 schema.public_name(index).unwrap_or(name).to_string(),
161 schema
162 .column_type(index)
163 .cloned()
164 .unwrap_or(ColumnType::Text),
165 )
166 })
167 .collect())
168 }
169
170 fn validate_select_rule_contract(
171 definition: &CreateRule,
172 is_view: bool,
173 ) -> Result<(), SQLError> {
174 if definition.event != RuleEvent::Select && definition.name == "_RETURN" {
175 let relation =
176 RelationIdentity::from_legacy_name(&definition.table).map_err(|error| {
177 SQLError::Internal(format!(
178 "decode rule relation `{}`: {error}",
179 definition.table
180 ))
181 })?;
182 return Err(SQLError::Routine {
183 sqlstate: "42P17".into(),
184 message: format!(
185 "non-view rule for \"{}\" must not be named \"_RETURN\"",
186 relation.name
187 ),
188 });
189 }
190 if definition.event == RuleEvent::Select {
191 if !is_view {
192 return Err(SQLError::Routine {
193 sqlstate: "42809".into(),
194 message: format!(
195 "relation \"{}\" cannot have ON SELECT rules",
196 definition.table
197 ),
198 });
199 }
200 if definition.name != "_RETURN"
201 || !definition.instead
202 || definition.condition.is_some()
203 || !matches!(definition.actions.as_slice(), [Statement::Select(_)])
204 {
205 return Err(SQLError::Routine {
206 sqlstate: "42P17".into(),
207 message: "view rule must be named \"_RETURN\", unconditional, INSTEAD, and have one SELECT action".into(),
208 });
209 }
210 }
211 Ok(())
212 }
213
214 fn validate_rule_condition(
215 &self,
216 condition: &mut Expr,
217 columns: &[(String, ColumnType)],
218 event: RuleEvent,
219 stored_plan: Option<&crate::plan::ExpressionPlan>,
220 stored_binding: Option<&RuleConditionBinding>,
221 ) -> Result<Option<(crate::plan::ExpressionPlan, RuleConditionBinding)>, SQLError> {
222 let has_subquery = rule_condition_has_subquery(condition);
223 if condition.contains_aggregate()
224 || condition.contains_window()
225 || condition.any_node(&|node| {
226 matches!(node, Expr::Func { name, .. } if self.routines.has_registered_aggregate_function(name))
227 })
228 {
229 return Err(SQLError::Routine {
230 sqlstate: "42803".into(),
231 message: "aggregate and window functions are not allowed in rule WHERE conditions"
232 .into(),
233 });
234 }
235 if !has_subquery && condition.any_node(&|node| matches!(node, Expr::Column(_))) {
236 *condition = bind_expr(condition, &mut RuleConditionNameResolver { columns, event })?;
237 }
238 if let Some(reference) = first_invalid_rule_condition_qualifier(condition) {
239 return Err(SQLError::Routine {
240 sqlstate: "42P10".into(),
241 message: format!("rule WHERE condition cannot refer to relation \"{reference}\""),
242 });
243 }
244 if !has_subquery {
245 let bound = bind_expr(condition, &mut RuleRowTypeResolver { columns, event })?;
246 let lowered = crate::plan::ExpressionPlan::lower(bound);
247 match crate::common_context_expression_type(
248 &lowered.scalar,
249 &crate::RowSchema::default(),
250 &[],
251 Some(self.routines),
252 )? {
253 Some(ty) if !is_boolean_type(&ty) => {
254 return Err(SQLError::TypeMismatch(format!(
255 "argument of WHERE must be type boolean, not type {}",
256 ty.sql_name()
257 )))
258 }
259 None => {
260 if let Expr::Literal(value @ (Value::Str(_) | Value::FixedChar(_))) = condition
261 {
262 *value = crate::expr::cast_value(value, "boolean")?;
263 } else {
264 *condition = Expr::Cast {
265 implicit: true,
266 expr: Box::new(condition.clone()),
267 ty: "boolean".into(),
268 };
269 }
270 }
271 Some(_) => {}
272 }
273 }
274 let (mut plan, binding, reused) = if let Some((plan, binding)) =
275 stored_plan.zip(stored_binding)
276 {
277 let mut plan = plan.clone();
278 let binding = binding.reallocate_plan_relations(&mut plan);
279 (plan, binding, true)
280 } else {
281 let plan = crate::plan::ExpressionPlan::lower_with(condition.clone(), &|name: &str| {
282 self.routines.has_registered_aggregate_function(name)
283 });
284 let column_names = columns
285 .iter()
286 .map(|(name, _)| name.clone())
287 .collect::<Vec<_>>();
288 (
289 plan,
290 RuleConditionBinding::for_event(&column_names, event),
291 false,
292 )
293 };
294 if !reused {
295 for subquery in &mut plan.subqueries {
296 self.bind_rule_condition_subquery_relations(subquery)?;
297 }
298 }
299 let schema = binding.row_schema(columns);
300 let ty = self
301 .stored_routines
302 .bind_expression(&mut plan, &[], &schema)?;
303 if let Some(ty) = ty {
304 if !is_boolean_type(&ty) {
305 return Err(SQLError::TypeMismatch(format!(
306 "argument of WHERE must be type boolean, not type {}",
307 ty.sql_name()
308 )));
309 }
310 }
311 crate::catalog::regrole_dependencies::reject_stored_regrole_constants_with(
312 self.regroles,
313 condition,
314 None,
315 )?;
316 Ok(Some((plan, binding)))
317 }
318
319 fn bind_rule_condition_subquery_relations(
320 &self,
321 subquery: &mut crate::plan::QueryPlan,
322 ) -> Result<(), SQLError> {
323 let namespace = self.namespaces.stored_query_namespace();
324 crate::binding::stored_relations::bind_stored_query_relations(
325 &crate::binding::stored_relations::StoredQueryBindingContext {
326 relations: self.relations,
327 lookup_mode: RelationLookupMode::Dynamic,
328 sequences: self.sequences,
329 temporary_schema: &namespace.temporary_schema,
330 transition_relations: &namespace.transition_relations,
331 },
332 subquery,
333 "CREATE RULE",
334 false,
335 false,
336 )?;
337 Ok(())
338 }
339
340 fn canonicalize_rule_action_target(
341 &self,
342 action: &mut Statement,
343 lookup_mode: RelationLookupMode,
344 ) -> Result<(), SQLError> {
345 let (target, target_relation_bound) = match action {
346 Statement::Insert(statement) => {
347 (&mut statement.table, &mut statement.target_relation_bound)
348 }
349 Statement::Update(statement) => {
350 (&mut statement.table, &mut statement.target_relation_bound)
351 }
352 Statement::Delete(statement) => {
353 (&mut statement.table, &mut statement.target_relation_bound)
354 }
355 _ => return Ok(()),
356 };
357 let relation = if lookup_mode == RelationLookupMode::Bound || *target_relation_bound {
358 self.resolve_rule_relation(target)?
359 } else {
360 self.resolve_visible_rule_action_relation(target)?
361 };
362 *target_relation_bound = true;
363 *target = relation.qualified_name();
364 Ok(())
365 }
366
367 pub fn rule_action_target_columns(
368 &self,
369 action: &Statement,
370 ) -> Result<std::collections::BTreeSet<String>, SQLError> {
371 crate::semantics::rules::action_binding::rule_action_target_columns(self.sources, action)
372 }
373
374 fn rule_action_target_row_type(
375 &self,
376 action: &Statement,
377 ) -> Result<Vec<(String, ColumnType)>, SQLError> {
378 crate::semantics::rules::action_binding::rule_action_target_row_type(self.sources, action)
379 }
380
381 fn validate_rule_action_definition(
382 &self,
383 action: &mut Statement,
384 event_columns: &[(String, ColumnType)],
385 event: RuleEvent,
386 lookup_mode: RelationLookupMode,
387 ) -> Result<RuleDependencies, SQLError> {
388 self.canonicalize_rule_action_target(action, lookup_mode)?;
389 let mut dependencies =
390 crate::binding::stored_relations::bind_rule_action_relation_dependencies(
391 self.relations,
392 action,
393 lookup_mode,
394 )?;
395 let action_row_type = self.rule_action_target_row_type(action)?;
396 let action_columns: std::collections::BTreeSet<String> = action_row_type
397 .iter()
398 .map(|(column, _)| column.clone())
399 .collect();
400 if lookup_mode == RelationLookupMode::Dynamic {
401 *action = crate::semantics::rules::action_binding::expand_rule_action_row_stars(
402 self.sources,
403 action,
404 &action_columns,
405 event_columns,
406 event,
407 )?;
408 *action = crate::semantics::rules::action_binding::expand_rule_action_returning_stars(
409 action,
410 &action_row_type,
411 );
412 }
413 dependencies.columns.extend(
414 crate::binding::stored_columns::bind_rule_action_column_dependencies(
415 self.columns,
416 action,
417 )?,
418 );
419 validate_rule_action_reference_scopes(self.sources, action)?;
420 let bound = crate::semantics::rules::action_binding::bind_rule_action(
421 self.sources,
422 action,
423 &action_columns,
424 &mut RuleRowTypeResolver {
425 columns: event_columns,
426 event,
427 },
428 )?;
429 let schema = crate::semantics::returning::dml_statement_returning_schema(
430 self.returning,
431 bound.clone(),
432 )?;
433 let mut stored_plan = crate::plan::UnifiedPlan::lower_with(bound, &|name: &str| {
434 self.routines.has_registered_aggregate_function(name)
435 });
436 crate::catalog::regrole_dependencies::reject_stored_plan_regrole_constants_with(
437 self.regroles,
438 &mut stored_plan,
439 )?;
440 let bound_routines = self.stored_routines.bind_statement(&stored_plan)?;
441 if let Some(routine_plan) = &bound_routines.query {
442 crate::catalog::events::dependencies::collect_query_routine_dependencies(
443 routine_plan,
444 &mut dependencies,
445 );
446 }
447 crate::catalog::stored_ast::bind_stored_statement_sites(action, &bound_routines.sites)?;
448 if let Some(schema) = schema {
449 validate_rule_returning_shape(&schema, event_columns)?;
450 }
451 Ok(dependencies)
452 }
453
454 fn bind_rule_condition_object_dependencies(
455 &self,
456 condition: &mut Expr,
457 condition_plan: Option<&crate::plan::ExpressionPlan>,
458 columns: &[(String, ColumnType)],
459 event: RuleEvent,
460 dependencies: &mut RuleDependencies,
461 ) -> Result<(), SQLError> {
462 if let Some(plan) = condition_plan {
463 let lowered =
465 crate::plan::ExpressionPlan::lower_with(condition.clone(), &|name: &str| {
466 self.routines.has_registered_aggregate_function(name)
467 });
468 let sites = crate::binding::syntax_sites::expression_syntax_sites(&lowered, plan)?;
469 crate::catalog::stored_ast::bind_stored_expression_sites(condition, &sites)?;
470 crate::catalog::events::dependencies::collect_expression_routine_dependencies(
471 plan,
472 dependencies,
473 );
474 for subquery in &plan.subqueries {
475 crate::catalog::events::dependencies::collect_query_relation_dependencies(
476 subquery,
477 dependencies,
478 &std::collections::BTreeSet::new(),
479 )?;
480 }
481 let routine_references =
482 crate::binding::stored_routines::collect_expression_routine_references(plan)?;
483 crate::catalog::stored_ast::bind_stored_expression_routines(
484 condition,
485 &routine_references,
486 )?;
487 return Ok(());
488 }
489
490 let bound = bind_expr(condition, &mut RuleRowTypeResolver { columns, event })?;
491 let lowered = crate::plan::ExpressionPlan::lower_with(bound, &|name: &str| {
492 self.routines.has_registered_aggregate_function(name)
493 });
494 let mut dependency_plan = lowered.clone();
495 self.stored_routines.bind_expression(
496 &mut dependency_plan,
497 &[],
498 &crate::RowSchema::default(),
499 )?;
500 crate::catalog::events::dependencies::collect_expression_routine_dependencies(
501 &dependency_plan,
502 dependencies,
503 );
504 let sites =
505 crate::binding::syntax_sites::expression_syntax_sites(&lowered, &dependency_plan)?;
506 crate::catalog::stored_ast::bind_stored_expression_sites(condition, &sites).map(|_| ())
507 }
508
509 pub fn validate_rule_definition(
510 &self,
511 definition: &mut CreateRule,
512 lookup_mode: RelationLookupMode,
513 stored_condition_plan: Option<&crate::plan::ExpressionPlan>,
514 stored_condition_binding: Option<&RuleConditionBinding>,
515 ) -> Result<
516 (
517 RelationIdentity,
518 Option<crate::plan::ExpressionPlan>,
519 Option<RuleConditionBinding>,
520 RuleDependencies,
521 ),
522 SQLError,
523 > {
524 let (relation, _) =
525 self.resolve_rule_event_relation_kind(&definition.table, lookup_mode)?;
526 definition.table = relation.qualified_name();
527 if lookup_mode == RelationLookupMode::Dynamic {
528 self.ensure_event_relation_owner(&relation, None)?;
529 }
530 let stored_view_kind = self.catalog.view_kind(&relation);
531 if stored_view_kind == Some(StoredViewKind::Materialized) {
532 return Err(SQLError::Routine {
533 sqlstate: "0A000".into(),
534 message: "rules on materialized views are not supported".into(),
535 });
536 }
537 let is_view = stored_view_kind == Some(StoredViewKind::View);
538 Self::validate_select_rule_contract(definition, is_view)?;
539 let columns = self.rule_relation_columns(&definition.table)?;
540 let mut dependencies = RuleDependencies::default();
541 if let Some(condition) = &mut definition.condition {
542 let condition_dependencies =
543 crate::binding::stored_relations::bind_rule_condition_relation_dependencies(
544 self.relations,
545 condition,
546 lookup_mode,
547 )?;
548 dependencies
549 .relations
550 .extend(condition_dependencies.relations);
551 dependencies.columns.extend(
552 crate::binding::stored_columns::bind_rule_condition_column_dependencies(
553 self.columns,
554 condition,
555 )?,
556 );
557 }
558 let condition = definition
559 .condition
560 .as_mut()
561 .map(|condition| {
562 self.validate_rule_condition(
563 condition,
564 &columns,
565 definition.event,
566 stored_condition_plan,
567 stored_condition_binding,
568 )
569 })
570 .transpose()?
571 .flatten();
572 let (condition_plan, condition_binding) = condition.map_or_else(
573 || (None, None),
574 |(plan, binding)| (Some(plan), Some(binding)),
575 );
576 if let Some(condition) = &mut definition.condition {
577 self.bind_rule_condition_object_dependencies(
578 condition,
579 condition_plan.as_ref(),
580 &columns,
581 definition.event,
582 &mut dependencies,
583 )?;
584 dependencies.columns.extend(
585 crate::semantics::rules::action_binding::rule_expr_row_columns(condition)
586 .into_iter()
587 .map(|column| crate::catalog::events::RuleColumnDependency {
588 relation: relation.clone(),
589 column,
590 }),
591 );
592 }
593 validate_rule_action_contract(definition)?;
594 for action in &mut definition.actions {
595 let action_dependencies = self.validate_rule_action_definition(
596 action,
597 &columns,
598 definition.event,
599 lookup_mode,
600 )?;
601 dependencies.relations.extend(action_dependencies.relations);
602 dependencies.columns.extend(action_dependencies.columns);
603 dependencies.routines.extend(action_dependencies.routines);
604 let action_columns = self.rule_action_target_columns(action)?;
605 dependencies.columns.extend(
606 crate::semantics::rules::action_binding::rule_statement_row_columns(
607 self.sources,
608 action,
609 &action_columns,
610 )?
611 .into_iter()
612 .map(|column| crate::catalog::events::RuleColumnDependency {
613 relation: relation.clone(),
614 column,
615 }),
616 );
617 }
618 Ok((relation, condition_plan, condition_binding, dependencies))
619 }
620}