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 Ok(self
136 .rule_relation_definitions(name)?
137 .into_iter()
138 .map(|column| (column.name, column.ty))
139 .collect())
140 }
141
142 pub fn rule_relation_definitions(
143 &self,
144 name: &str,
145 ) -> Result<Vec<crate::ast::ColumnDef>, SQLError> {
146 if let Some(columns) = self
147 .returning
148 .catalog
149 .try_describe_table_row_type(name)
150 .map_err(|error| SQLError::Internal(format!("read rule columns: {error}")))?
151 {
152 return Ok(columns);
153 }
154 let relation = RelationIdentity::from_legacy_name(name).map_err(|error| {
155 SQLError::Internal(format!("decode rule relation `{name}`: {error}"))
156 })?;
157 let view = self
158 .catalog
159 .view(&relation)
160 .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?;
161 let schema = self.catalog.stored_view_schema(&view)?;
162 Ok(schema
163 .columns()
164 .iter()
165 .enumerate()
166 .map(|(index, name)| {
167 let mut column = crate::ast::ColumnDef::nullable(
168 schema.public_name(index).unwrap_or(name),
169 schema
170 .column_type(index)
171 .cloned()
172 .unwrap_or(ColumnType::Text),
173 );
174 column.collation_oid = schema.column_collation(index);
175 column
176 })
177 .collect())
178 }
179
180 fn validate_select_rule_contract(
181 definition: &CreateRule,
182 is_view: bool,
183 ) -> Result<(), SQLError> {
184 if definition.event != RuleEvent::Select && definition.name == "_RETURN" {
185 let relation =
186 RelationIdentity::from_legacy_name(&definition.table).map_err(|error| {
187 SQLError::Internal(format!(
188 "decode rule relation `{}`: {error}",
189 definition.table
190 ))
191 })?;
192 return Err(SQLError::Routine {
193 sqlstate: "42P17".into(),
194 message: format!(
195 "non-view rule for \"{}\" must not be named \"_RETURN\"",
196 relation.name
197 ),
198 });
199 }
200 if definition.event == RuleEvent::Select {
201 if !is_view {
202 return Err(SQLError::Routine {
203 sqlstate: "42809".into(),
204 message: format!(
205 "relation \"{}\" cannot have ON SELECT rules",
206 definition.table
207 ),
208 });
209 }
210 if definition.name != "_RETURN"
211 || !definition.instead
212 || definition.condition.is_some()
213 || !matches!(definition.actions.as_slice(), [Statement::Select(_)])
214 {
215 return Err(SQLError::Routine {
216 sqlstate: "42P17".into(),
217 message: "view rule must be named \"_RETURN\", unconditional, INSTEAD, and have one SELECT action".into(),
218 });
219 }
220 }
221 Ok(())
222 }
223
224 fn validate_rule_condition(
225 &self,
226 condition: &mut Expr,
227 columns: &[crate::ast::ColumnDef],
228 event: RuleEvent,
229 stored_plan: Option<&crate::plan::ExpressionPlan>,
230 stored_binding: Option<&RuleConditionBinding>,
231 ) -> Result<Option<(crate::plan::ExpressionPlan, RuleConditionBinding)>, SQLError> {
232 let has_subquery = rule_condition_has_subquery(condition);
233 if condition.contains_aggregate()
234 || condition.contains_window()
235 || condition.any_node(&|node| {
236 matches!(node, Expr::Func { name, .. } if self.routines.has_registered_aggregate_function(name))
237 })
238 {
239 return Err(SQLError::Routine {
240 sqlstate: "42803".into(),
241 message: "aggregate and window functions are not allowed in rule WHERE conditions"
242 .into(),
243 });
244 }
245 if !has_subquery && condition.any_node(&|node| matches!(node, Expr::Column(_))) {
246 *condition = bind_expr(condition, &mut RuleConditionNameResolver { columns, event })?;
247 }
248 if let Some(reference) = first_invalid_rule_condition_qualifier(condition) {
249 return Err(SQLError::Routine {
250 sqlstate: "42P10".into(),
251 message: format!("rule WHERE condition cannot refer to relation \"{reference}\""),
252 });
253 }
254 if !has_subquery {
255 let bound = bind_expr(
256 condition,
257 &mut RuleRowTypeResolver::for_condition(columns, event),
258 )?;
259 let lowered = crate::plan::ExpressionPlan::lower(bound);
260 match crate::common_context_expression_type(
261 &lowered.scalar,
262 &crate::RowSchema::default(),
263 &[],
264 Some(self.routines),
265 )? {
266 Some(ty) if !is_boolean_type(&ty) => {
267 return Err(SQLError::TypeMismatch(format!(
268 "argument of WHERE must be type boolean, not type {}",
269 ty.sql_name()
270 )))
271 }
272 None => {
273 if let Expr::Literal(value @ (Value::Str(_) | Value::FixedChar(_))) = condition
274 {
275 *value = crate::expr::cast_value(value, "boolean")?;
276 } else {
277 *condition = Expr::Cast {
278 implicit: true,
279 expr: Box::new(condition.clone()),
280 ty: "boolean".into(),
281 };
282 }
283 }
284 Some(_) => {}
285 }
286 }
287 let (mut plan, binding, reused) = if let Some((plan, binding)) =
288 stored_plan.zip(stored_binding)
289 {
290 let mut plan = plan.clone();
291 let binding = binding.reallocate_plan_relations(&mut plan);
292 (plan, binding, true)
293 } else {
294 let plan = crate::plan::ExpressionPlan::lower_with(condition.clone(), &|name: &str| {
295 self.routines.has_registered_aggregate_function(name)
296 });
297 let column_names = columns
298 .iter()
299 .map(|column| column.name.clone())
300 .collect::<Vec<_>>();
301 (
302 plan,
303 RuleConditionBinding::for_event(&column_names, event),
304 false,
305 )
306 };
307 if !reused {
308 for subquery in &mut plan.subqueries {
309 self.bind_rule_condition_subquery_relations(subquery)?;
310 }
311 }
312 let schema = binding.row_schema(columns);
313 let ty = self
314 .stored_routines
315 .bind_rule_condition(&mut plan, &schema, event)?;
316 if let Some(ty) = ty {
317 if !is_boolean_type(&ty) {
318 return Err(SQLError::TypeMismatch(format!(
319 "argument of WHERE must be type boolean, not type {}",
320 ty.sql_name()
321 )));
322 }
323 }
324 crate::catalog::regrole_dependencies::reject_stored_regrole_constants_with(
325 self.regroles,
326 condition,
327 None,
328 )?;
329 Ok(Some((plan, binding)))
330 }
331
332 fn bind_rule_condition_subquery_relations(
333 &self,
334 subquery: &mut crate::plan::QueryPlan,
335 ) -> Result<(), SQLError> {
336 let namespace = self.namespaces.stored_query_namespace();
337 crate::binding::stored_relations::bind_stored_query_relations(
338 &crate::binding::stored_relations::StoredQueryBindingContext {
339 relations: self.relations,
340 lookup_mode: RelationLookupMode::Dynamic,
341 sequences: self.sequences,
342 temporary_schema: &namespace.temporary_schema,
343 transition_relations: &namespace.transition_relations,
344 },
345 subquery,
346 "CREATE RULE",
347 false,
348 false,
349 )?;
350 Ok(())
351 }
352
353 fn canonicalize_rule_action_target(
354 &self,
355 action: &mut Statement,
356 lookup_mode: RelationLookupMode,
357 ) -> Result<(), SQLError> {
358 let (target, target_relation_bound) = match action {
359 Statement::Insert(statement) => {
360 (&mut statement.table, &mut statement.target_relation_bound)
361 }
362 Statement::Update(statement) => {
363 (&mut statement.table, &mut statement.target_relation_bound)
364 }
365 Statement::Delete(statement) => {
366 (&mut statement.table, &mut statement.target_relation_bound)
367 }
368 _ => return Ok(()),
369 };
370 let relation = if lookup_mode == RelationLookupMode::Bound || *target_relation_bound {
371 self.resolve_rule_relation(target)?
372 } else {
373 self.resolve_visible_rule_action_relation(target)?
374 };
375 *target_relation_bound = true;
376 *target = relation.qualified_name();
377 Ok(())
378 }
379
380 pub fn rule_action_target_columns(
381 &self,
382 action: &Statement,
383 ) -> Result<std::collections::BTreeSet<String>, SQLError> {
384 crate::semantics::rules::action_binding::rule_action_target_columns(self.sources, action)
385 }
386
387 fn rule_action_target_row_type(
388 &self,
389 action: &Statement,
390 ) -> Result<Vec<(String, ColumnType)>, SQLError> {
391 crate::semantics::rules::action_binding::rule_action_target_row_type(self.sources, action)
392 }
393
394 fn validate_rule_action_definition(
395 &self,
396 action: &mut Statement,
397 event_columns: &[crate::ast::ColumnDef],
398 event: RuleEvent,
399 lookup_mode: RelationLookupMode,
400 ) -> Result<RuleDependencies, SQLError> {
401 self.canonicalize_rule_action_target(action, lookup_mode)?;
402 let mut dependencies =
403 crate::binding::stored_relations::bind_rule_action_relation_dependencies(
404 self.relations,
405 action,
406 lookup_mode,
407 )?;
408 let event_row_type = event_columns
409 .iter()
410 .map(|column| (column.name.clone(), column.ty.clone()))
411 .collect::<Vec<_>>();
412 let action_row_type = self.rule_action_target_row_type(action)?;
413 let action_columns: std::collections::BTreeSet<String> = action_row_type
414 .iter()
415 .map(|(column, _)| column.clone())
416 .collect();
417 if lookup_mode == RelationLookupMode::Dynamic {
418 *action = crate::semantics::rules::action_binding::expand_rule_action_row_stars(
419 self.sources,
420 action,
421 &action_columns,
422 &event_row_type,
423 event,
424 )?;
425 *action = crate::semantics::rules::action_binding::expand_rule_action_returning_stars(
426 action,
427 &action_row_type,
428 );
429 }
430 dependencies.columns.extend(
431 crate::binding::stored_columns::bind_rule_action_column_dependencies(
432 self.columns,
433 action,
434 )?,
435 );
436 validate_rule_action_reference_scopes(self.sources, action)?;
437 let mut row_resolver = RuleRowTypeResolver::for_action(event_columns, event);
438 let bound = crate::semantics::rules::action_binding::bind_rule_action(
439 self.sources,
440 action,
441 &action_columns,
442 &mut row_resolver,
443 )?;
444 let schema = crate::semantics::returning::dml_statement_returning_schema(
445 self.returning,
446 bound.clone(),
447 )?;
448 let mut stored_plan = crate::plan::UnifiedPlan::lower_with(bound, &|name: &str| {
449 self.routines.has_registered_aggregate_function(name)
450 });
451 crate::catalog::regrole_dependencies::reject_stored_plan_regrole_constants_with(
452 self.regroles,
453 &mut stored_plan,
454 )?;
455 let bound_routines = self.stored_routines.bind_statement(&stored_plan)?;
456 row_resolver.validate_event_references()?;
457 if let Some(routine_plan) = &bound_routines.query {
458 crate::catalog::events::dependencies::collect_query_routine_dependencies(
459 routine_plan,
460 &mut dependencies,
461 );
462 }
463 crate::catalog::stored_ast::bind_stored_statement_sites(action, &bound_routines.sites)?;
464 if let Some(schema) = schema {
465 validate_rule_returning_shape(&schema, &event_row_type)?;
466 }
467 Ok(dependencies)
468 }
469
470 fn bind_rule_condition_object_dependencies(
471 &self,
472 condition: &mut Expr,
473 condition_plan: Option<&crate::plan::ExpressionPlan>,
474 columns: &[crate::ast::ColumnDef],
475 event: RuleEvent,
476 dependencies: &mut RuleDependencies,
477 ) -> Result<(), SQLError> {
478 if let Some(plan) = condition_plan {
479 let lowered =
481 crate::plan::ExpressionPlan::lower_with(condition.clone(), &|name: &str| {
482 self.routines.has_registered_aggregate_function(name)
483 });
484 let sites = crate::binding::syntax_sites::expression_syntax_sites(&lowered, plan)?;
485 crate::catalog::stored_ast::bind_stored_expression_sites(condition, &sites)?;
486 crate::catalog::events::dependencies::collect_expression_routine_dependencies(
487 plan,
488 dependencies,
489 );
490 for subquery in &plan.subqueries {
491 crate::catalog::events::dependencies::collect_query_relation_dependencies(
492 subquery,
493 dependencies,
494 &std::collections::BTreeSet::new(),
495 )?;
496 }
497 let routine_references =
498 crate::binding::stored_routines::collect_expression_routine_references(plan)?;
499 crate::catalog::stored_ast::bind_stored_expression_routines(
500 condition,
501 &routine_references,
502 )?;
503 return Ok(());
504 }
505
506 let bound = bind_expr(
507 condition,
508 &mut RuleRowTypeResolver::for_condition(columns, event),
509 )?;
510 let lowered = crate::plan::ExpressionPlan::lower_with(bound, &|name: &str| {
511 self.routines.has_registered_aggregate_function(name)
512 });
513 let mut dependency_plan = lowered.clone();
514 self.stored_routines.bind_expression(
515 &mut dependency_plan,
516 &[],
517 &crate::RowSchema::default(),
518 )?;
519 crate::catalog::events::dependencies::collect_expression_routine_dependencies(
520 &dependency_plan,
521 dependencies,
522 );
523 let sites =
524 crate::binding::syntax_sites::expression_syntax_sites(&lowered, &dependency_plan)?;
525 crate::catalog::stored_ast::bind_stored_expression_sites(condition, &sites).map(|_| ())
526 }
527
528 pub fn validate_rule_definition(
529 &self,
530 definition: &mut CreateRule,
531 lookup_mode: RelationLookupMode,
532 stored_condition_plan: Option<&crate::plan::ExpressionPlan>,
533 stored_condition_binding: Option<&RuleConditionBinding>,
534 ) -> Result<
535 (
536 RelationIdentity,
537 Option<crate::plan::ExpressionPlan>,
538 Option<RuleConditionBinding>,
539 RuleDependencies,
540 ),
541 SQLError,
542 > {
543 let (relation, _) =
544 self.resolve_rule_event_relation_kind(&definition.table, lookup_mode)?;
545 definition.table = relation.qualified_name();
546 if lookup_mode == RelationLookupMode::Dynamic {
547 self.ensure_event_relation_owner(&relation, None)?;
548 }
549 let stored_view_kind = self.catalog.view_kind(&relation);
550 if stored_view_kind == Some(StoredViewKind::Materialized) {
551 return Err(SQLError::Routine {
552 sqlstate: "0A000".into(),
553 message: "rules on materialized views are not supported".into(),
554 });
555 }
556 let is_view = stored_view_kind == Some(StoredViewKind::View);
557 Self::validate_select_rule_contract(definition, is_view)?;
558 let columns = self.rule_relation_definitions(&definition.table)?;
559 let mut dependencies = RuleDependencies::default();
560 if let Some(condition) = &mut definition.condition {
561 let condition_dependencies =
562 crate::binding::stored_relations::bind_rule_condition_relation_dependencies(
563 self.relations,
564 condition,
565 lookup_mode,
566 )?;
567 dependencies
568 .relations
569 .extend(condition_dependencies.relations);
570 dependencies.columns.extend(
571 crate::binding::stored_columns::bind_rule_condition_column_dependencies(
572 self.columns,
573 condition,
574 )?,
575 );
576 }
577 let condition = definition
578 .condition
579 .as_mut()
580 .map(|condition| {
581 self.validate_rule_condition(
582 condition,
583 &columns,
584 definition.event,
585 stored_condition_plan,
586 stored_condition_binding,
587 )
588 })
589 .transpose()?
590 .flatten();
591 let (condition_plan, condition_binding) = condition.map_or_else(
592 || (None, None),
593 |(plan, binding)| (Some(plan), Some(binding)),
594 );
595 if let Some(condition) = &mut definition.condition {
596 self.bind_rule_condition_object_dependencies(
597 condition,
598 condition_plan.as_ref(),
599 &columns,
600 definition.event,
601 &mut dependencies,
602 )?;
603 dependencies.columns.extend(
604 crate::semantics::rules::action_binding::rule_expr_row_columns(condition)
605 .into_iter()
606 .map(|column| crate::catalog::events::RuleColumnDependency {
607 relation: relation.clone(),
608 column,
609 }),
610 );
611 }
612 validate_rule_action_contract(definition)?;
613 for action in &mut definition.actions {
614 let action_dependencies = self.validate_rule_action_definition(
615 action,
616 &columns,
617 definition.event,
618 lookup_mode,
619 )?;
620 dependencies.relations.extend(action_dependencies.relations);
621 dependencies.columns.extend(action_dependencies.columns);
622 dependencies.routines.extend(action_dependencies.routines);
623 let action_columns = self.rule_action_target_columns(action)?;
624 dependencies.columns.extend(
625 crate::semantics::rules::action_binding::rule_statement_row_columns(
626 self.sources,
627 action,
628 &action_columns,
629 )?
630 .into_iter()
631 .map(|column| crate::catalog::events::RuleColumnDependency {
632 relation: relation.clone(),
633 column,
634 }),
635 );
636 }
637 Ok((relation, condition_plan, condition_binding, dependencies))
638 }
639}