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