1use crate::{
10 ast::{
11 ColumnType, CreateTrigger, Expr, FromClause, OnConflictAction, RuleEvent, SelectStmt,
12 Statement, TableHierarchy, TriggerEvent, TriggerTiming, TriggerTransitionRelation,
13 },
14 plpgsql::{ResolvedVariable, VariableResolver},
15 semantics::rules::action_binding::{
16 first_rule_row_reference_in_expr, first_rule_row_reference_in_select, RuleSourceCatalog,
17 },
18 SQLError,
19};
20use uqa_core::Value;
21
22pub struct TriggerConditionTypeResolver<'a> {
23 pub columns: &'a [crate::ast::ColumnDef],
24}
25
26pub struct RuleRowTypeResolver<'a> {
27 pub columns: &'a [(String, ColumnType)],
28 pub event: RuleEvent,
29}
30
31pub struct RuleConditionNameResolver<'a> {
32 pub columns: &'a [(String, ColumnType)],
33 pub event: RuleEvent,
34}
35
36impl VariableResolver for RuleConditionNameResolver<'_> {
37 fn resolve_name(&mut self, _name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
38 Ok(None)
39 }
40
41 fn resolve_qualified(
42 &mut self,
43 _qualifier: &str,
44 _column: &str,
45 ) -> Result<Option<ResolvedVariable>, SQLError> {
46 Ok(None)
47 }
48
49 fn resolve_param(&mut self, _index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
50 Ok(None)
51 }
52
53 fn rewrite_name(&mut self, name: &str) -> Result<Option<Expr>, SQLError> {
54 if !self.columns.iter().any(|(column, _)| column == name) {
55 if name.eq_ignore_ascii_case("old") || name.eq_ignore_ascii_case("new") {
56 return Ok(None);
57 }
58 return Err(SQLError::UnknownColumn(name.to_string()));
59 }
60 let qualifier = match self.event {
61 RuleEvent::Insert => "new",
62 RuleEvent::Delete => "old",
63 RuleEvent::Update => return Err(SQLError::AmbiguousColumn(name.to_string())),
64 RuleEvent::Select => return Ok(None),
65 };
66 Ok(Some(Expr::qualified_column(qualifier, name)))
67 }
68}
69
70impl RuleRowTypeResolver<'_> {
71 fn validate_row_qualifier(&self, qualifier: &str) -> Result<bool, SQLError> {
72 let is_old = qualifier.eq_ignore_ascii_case("old");
73 let is_new = qualifier.eq_ignore_ascii_case("new");
74 if !is_old && !is_new {
75 return Ok(false);
76 }
77 if is_old && matches!(self.event, RuleEvent::Insert | RuleEvent::Select) {
78 return Err(SQLError::Routine {
79 sqlstate: "42P17".into(),
80 message: format!(
81 "there is no OLD relation for {event} rule",
82 event = rule_event_name(self.event)
83 ),
84 });
85 }
86 if is_new && matches!(self.event, RuleEvent::Delete | RuleEvent::Select) {
87 return Err(SQLError::Routine {
88 sqlstate: "42P17".into(),
89 message: format!(
90 "there is no NEW relation for {event} rule",
91 event = rule_event_name(self.event)
92 ),
93 });
94 }
95 Ok(true)
96 }
97
98 fn resolve_record_field(
99 &self,
100 qualifier: &str,
101 column: &str,
102 ) -> Result<Option<ResolvedVariable>, SQLError> {
103 if !self.validate_row_qualifier(qualifier)? {
104 return Ok(None);
105 }
106 let (_, ty) = self
107 .columns
108 .iter()
109 .find(|(name, _)| name == column)
110 .ok_or_else(|| SQLError::UnknownColumn(format!("{qualifier}.{column}")))?;
111 Ok(Some(ResolvedVariable {
112 value: Value::Null,
113 declared_type: Some(ty.sql_name()),
114 }))
115 }
116}
117
118impl VariableResolver for RuleRowTypeResolver<'_> {
119 fn resolve_name(&mut self, name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
120 Ok(self
121 .validate_row_qualifier(name)?
122 .then(|| ResolvedVariable::untyped(Value::Record(Vec::new()))))
123 }
124
125 fn resolve_qualified(
126 &mut self,
127 qualifier: &str,
128 column: &str,
129 ) -> Result<Option<ResolvedVariable>, SQLError> {
130 self.resolve_record_field(qualifier, column)
131 }
132
133 fn resolve_param(&mut self, _index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
134 Ok(None)
135 }
136
137 fn rewrite_qualified_whole_row(&mut self, qualifier: &str) -> Result<Option<Expr>, SQLError> {
138 Ok(self
139 .validate_row_qualifier(qualifier)?
140 .then(|| Expr::Literal(Value::Record(Vec::new()))))
141 }
142}
143
144const fn rule_event_name(event: RuleEvent) -> &'static str {
145 match event {
146 RuleEvent::Select => "SELECT",
147 RuleEvent::Insert => "INSERT",
148 RuleEvent::Update => "UPDATE",
149 RuleEvent::Delete => "DELETE",
150 }
151}
152
153impl VariableResolver for TriggerConditionTypeResolver<'_> {
154 fn resolve_name(&mut self, _name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
155 Ok(None)
156 }
157
158 fn resolve_qualified(
159 &mut self,
160 qualifier: &str,
161 column: &str,
162 ) -> Result<Option<ResolvedVariable>, SQLError> {
163 if !qualifier.eq_ignore_ascii_case("old") && !qualifier.eq_ignore_ascii_case("new") {
164 return Ok(None);
165 }
166 Ok(self
167 .columns
168 .iter()
169 .find(|definition| definition.name == column)
170 .map(|definition| ResolvedVariable {
171 value: Value::Null,
172 declared_type: Some(definition.ty.sql_name()),
173 }))
174 }
175
176 fn resolve_param(&mut self, _index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
177 Ok(None)
178 }
179}
180
181pub fn is_boolean_type(ty: &ColumnType) -> bool {
182 match ty {
183 ColumnType::Boolean => true,
184 ColumnType::Domain { base, .. } => is_boolean_type(base),
185 _ => false,
186 }
187}
188
189pub fn rule_action_has_returning(action: &Statement) -> bool {
190 match action {
191 Statement::Insert(statement) => !statement.returning.is_empty(),
192 Statement::Update(statement) => !statement.returning.is_empty(),
193 Statement::Delete(statement) => !statement.returning.is_empty(),
194 _ => false,
195 }
196}
197
198fn same_rule_returning_type_with_different_modifier(
199 actual: &ColumnType,
200 expected: &ColumnType,
201) -> bool {
202 match (actual, expected) {
203 (ColumnType::Varchar(_), ColumnType::Varchar(_))
204 | (ColumnType::Character(_), ColumnType::Character(_))
205 | (ColumnType::Numeric { .. }, ColumnType::Numeric { .. })
206 | (ColumnType::Vector(_), ColumnType::Vector(_))
207 | (ColumnType::Tensor(_), ColumnType::Tensor(_)) => true,
208 (ColumnType::Array(actual), ColumnType::Array(expected)) => {
209 same_rule_returning_type_with_different_modifier(actual, expected)
210 }
211 _ => false,
212 }
213}
214
215pub fn validate_rule_returning_shape(
216 schema: &crate::RowSchema,
217 columns: &[(String, ColumnType)],
218) -> Result<(), SQLError> {
219 if schema.len() < columns.len() {
220 return Err(SQLError::Routine {
221 sqlstate: "42P17".into(),
222 message: "RETURNING list has too few entries".into(),
223 });
224 }
225 if schema.len() > columns.len() {
226 return Err(SQLError::Routine {
227 sqlstate: "42P17".into(),
228 message: "RETURNING list has too many entries".into(),
229 });
230 }
231 for (position, (column, expected)) in columns.iter().enumerate() {
232 let Some(actual) = schema.column_type(position) else {
233 continue;
236 };
237 if actual == expected {
238 continue;
239 }
240 let difference = if same_rule_returning_type_with_different_modifier(actual, expected) {
241 "size"
242 } else {
243 "type"
244 };
245 return Err(SQLError::Routine {
246 sqlstate: "42P17".into(),
247 message: format!(
248 "RETURNING list's entry {} has different {difference} from column \"{column}\"\nDETAIL: RETURNING list entry has type {}, but column has type {}.",
249 position + 1,
250 actual.sql_name(),
251 expected.sql_name()
252 ),
253 });
254 }
255 Ok(())
256}
257
258pub fn validate_trigger_condition_references(
259 definition: &CreateTrigger,
260 columns: &[crate::ast::ColumnDef],
261 condition: &Expr,
262) -> Result<(), SQLError> {
263 if condition.any_node(&|node| {
264 matches!(
265 node,
266 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. }
267 )
268 }) {
269 return Err(SQLError::Routine {
270 sqlstate: "0A000".into(),
271 message: "cannot use subquery in trigger WHEN condition".into(),
272 });
273 }
274 if condition.any_node(&|node| matches!(node, Expr::Column(_))) {
275 return Err(SQLError::Routine {
276 sqlstate: "42P01".into(),
277 message: "trigger WHEN condition must qualify row columns with OLD or NEW".into(),
278 });
279 }
280 let references_old = condition.any_node(&|node| {
281 matches!(node, Expr::QualifiedColumn { qualifier, .. } if qualifier.eq_ignore_ascii_case("old"))
282 });
283 let references_new = condition.any_node(&|node| {
284 matches!(node, Expr::QualifiedColumn { qualifier, .. } if qualifier.eq_ignore_ascii_case("new"))
285 });
286 if !definition.row && (references_old || references_new) {
287 return Err(SQLError::Routine {
288 sqlstate: "42P01".into(),
289 message: "statement trigger's WHEN condition cannot reference row values".into(),
290 });
291 }
292 if references_old && definition.events.contains(&TriggerEvent::Insert) {
293 return Err(SQLError::Routine {
294 sqlstate: "42P17".into(),
295 message: "INSERT trigger's WHEN condition cannot reference OLD values".into(),
296 });
297 }
298 if references_new && definition.events.contains(&TriggerEvent::Delete) {
299 return Err(SQLError::Routine {
300 sqlstate: "42P17".into(),
301 message: "DELETE trigger's WHEN condition cannot reference NEW values".into(),
302 });
303 }
304 let invalid_qualified_reference = std::cell::RefCell::new(None);
305 let _ = condition.any_node(&|node| {
306 let Expr::QualifiedColumn { qualifier, column } = node else {
307 return false;
308 };
309 if !qualifier.eq_ignore_ascii_case("old") && !qualifier.eq_ignore_ascii_case("new") {
310 *invalid_qualified_reference.borrow_mut() = Some(format!("{qualifier}.{column}"));
311 return true;
312 }
313 if !columns.iter().any(|definition| definition.name == *column) {
314 *invalid_qualified_reference.borrow_mut() = Some(column.clone());
315 return true;
316 }
317 false
318 });
319 if let Some(reference) = invalid_qualified_reference.into_inner() {
320 return Err(SQLError::UnknownColumn(reference));
321 }
322 if definition.timing == TriggerTiming::Before && references_new {
323 let generated = columns
324 .iter()
325 .filter(|column| column.generated.is_some())
326 .map(|column| column.name.as_str())
327 .collect::<std::collections::BTreeSet<_>>();
328 if condition.any_node(&|node| {
329 matches!(
330 node,
331 Expr::QualifiedColumn { qualifier, column }
332 if qualifier.eq_ignore_ascii_case("new")
333 && generated.contains(column.as_str())
334 )
335 }) {
336 return Err(SQLError::Routine {
337 sqlstate: "42P17".into(),
338 message: "BEFORE trigger's WHEN condition cannot reference NEW generated columns"
339 .into(),
340 });
341 }
342 }
343 Ok(())
344}
345
346pub fn first_invalid_rule_condition_qualifier(condition: &Expr) -> Option<String> {
347 let invalid = std::cell::RefCell::new(None);
348 let _ = condition.any_node(&|node| {
349 let Expr::QualifiedColumn { qualifier, .. } = node else {
350 return false;
351 };
352 if qualifier.eq_ignore_ascii_case("old") || qualifier.eq_ignore_ascii_case("new") {
353 return false;
354 }
355 *invalid.borrow_mut() = Some(qualifier.clone());
356 true
357 });
358 invalid.into_inner()
359}
360
361fn invalid_rule_cte_reference(qualifier: &str) -> SQLError {
362 SQLError::Routine {
363 sqlstate: "0A000".into(),
364 message: format!(
365 "cannot refer to {} within WITH query",
366 qualifier.to_ascii_uppercase()
367 ),
368 }
369}
370
371fn invalid_rule_set_operation_reference() -> SQLError {
372 SQLError::Routine {
373 sqlstate: "42P10".into(),
374 message:
375 "UNION/INTERSECT/EXCEPT member statement cannot refer to other relations of same query level"
376 .into(),
377 }
378}
379
380pub(super) use crate::semantics::rules::action_binding::invalid_rule_action_reference;
381
382fn ambiguous_rule_pseudo_relation(qualifier: &str) -> SQLError {
383 SQLError::Routine {
384 sqlstate: "42P09".into(),
385 message: format!("table reference \"{qualifier}\" is ambiguous"),
386 }
387}
388
389fn duplicate_rule_pseudo_relation(qualifier: &str) -> SQLError {
390 SQLError::Routine {
391 sqlstate: "42712".into(),
392 message: format!("table name \"{qualifier}\" specified more than once"),
393 }
394}
395
396fn rule_pseudo_relation_name(name: &str) -> Option<String> {
397 (name.eq_ignore_ascii_case("old") || name.eq_ignore_ascii_case("new"))
398 .then(|| name.to_ascii_lowercase())
399}
400
401fn first_rule_pseudo_relation_in_from(from: &FromClause) -> Option<String> {
402 match from {
403 FromClause::Table {
404 name,
405 qualifier,
406 alias,
407 ..
408 } => alias
409 .as_deref()
410 .and_then(rule_pseudo_relation_name)
411 .or_else(|| rule_pseudo_relation_name(qualifier))
412 .or_else(|| rule_pseudo_relation_name(name))
413 .or_else(|| {
414 name.rsplit_once('.')
415 .and_then(|(_, local)| rule_pseudo_relation_name(local.trim_matches('"')))
416 }),
417 FromClause::Join {
418 left, right, alias, ..
419 } => alias
420 .as_deref()
421 .and_then(rule_pseudo_relation_name)
422 .or_else(|| first_rule_pseudo_relation_in_from(left))
423 .or_else(|| first_rule_pseudo_relation_in_from(right)),
424 FromClause::Values { alias, .. } | FromClause::Subquery { alias, .. } => {
425 alias.as_deref().and_then(rule_pseudo_relation_name)
426 }
427 FromClause::Function {
428 output_name, alias, ..
429 } => rule_pseudo_relation_name(alias.as_deref().unwrap_or(output_name)),
430 FromClause::FunctionGroup {
431 functions, alias, ..
432 } => alias
433 .as_deref()
434 .and_then(rule_pseudo_relation_name)
435 .or_else(|| {
436 functions
437 .iter()
438 .find_map(|function| rule_pseudo_relation_name(&function.output_name))
439 }),
440 }
441}
442
443fn validate_rule_action_select_namespace(select: &SelectStmt) -> Result<(), SQLError> {
444 let duplicate = select
445 .with
446 .iter()
447 .find_map(|cte| rule_pseudo_relation_name(&cte.name))
448 .or_else(|| {
449 select
450 .from
451 .as_ref()
452 .and_then(first_rule_pseudo_relation_in_from)
453 });
454 if let Some(qualifier) = duplicate {
455 return Err(duplicate_rule_pseudo_relation(&qualifier));
456 }
457 Ok(())
458}
459
460fn validate_rule_action_namespace(
461 catalog: &dyn RuleSourceCatalog,
462 action: &Statement,
463) -> Result<(), SQLError> {
464 let (ctes, source) = match action {
465 Statement::Select(select) => return validate_rule_action_select_namespace(select),
466 Statement::Insert(insert) => (insert.with.as_slice(), insert.select_source.as_deref()),
467 Statement::Update(update) => {
468 if let Some(qualifier) = update
469 .from
470 .as_ref()
471 .and_then(first_rule_pseudo_relation_in_from)
472 {
473 return Err(duplicate_rule_pseudo_relation(&qualifier));
474 }
475 if let Some(qualifier) = rule_pseudo_relation_name(&update.target_qualifier) {
476 if crate::semantics::rules::action_binding::action_target_qualifier_referenced(
477 catalog, action, &qualifier,
478 ) {
479 return Err(ambiguous_rule_pseudo_relation(&qualifier));
480 }
481 }
482 (update.with.as_slice(), None)
483 }
484 Statement::Delete(delete) => {
485 if let Some(qualifier) = delete
486 .using
487 .as_ref()
488 .and_then(first_rule_pseudo_relation_in_from)
489 {
490 return Err(duplicate_rule_pseudo_relation(&qualifier));
491 }
492 if let Some(qualifier) = rule_pseudo_relation_name(&delete.target_qualifier) {
493 if crate::semantics::rules::action_binding::action_target_qualifier_referenced(
494 catalog, action, &qualifier,
495 ) {
496 return Err(ambiguous_rule_pseudo_relation(&qualifier));
497 }
498 }
499 (delete.with.as_slice(), None)
500 }
501 _ => return Ok(()),
502 };
503 if let Some(qualifier) = ctes
504 .iter()
505 .find_map(|cte| rule_pseudo_relation_name(&cte.name))
506 {
507 return Err(duplicate_rule_pseudo_relation(&qualifier));
508 }
509 if let Some(select) = source {
510 validate_rule_action_select_namespace(select)?;
511 }
512 Ok(())
513}
514
515fn validate_rule_ctes(
516 catalog: &dyn RuleSourceCatalog,
517 ctes: &[crate::ast::CTE],
518) -> Result<(), SQLError> {
519 for cte in ctes {
520 let statement = cte.body.clone().into_statement();
521 if let Some(qualifier) =
522 crate::semantics::rules::action_binding::first_rule_row_reference_in_statement(
523 catalog, &statement,
524 )?
525 {
526 return Err(invalid_rule_cte_reference(&qualifier));
527 }
528 validate_rule_action_reference_scopes(catalog, &statement)?;
529 }
530 Ok(())
531}
532
533fn validate_rule_select_scopes(
534 catalog: &dyn RuleSourceCatalog,
535 select: &SelectStmt,
536) -> Result<(), SQLError> {
537 validate_rule_ctes(catalog, &select.with)?;
538 if let Some(set_op) = &select.set_op {
539 let member_references_rule_row = set_op
540 .left
541 .as_deref()
542 .and_then(|left| first_rule_row_reference_in_select(catalog, left))
543 .or_else(|| first_rule_row_reference_in_select(catalog, &set_op.right));
544 if member_references_rule_row.is_some() {
545 return Err(invalid_rule_set_operation_reference());
546 }
547 if let Some(left) = set_op.left.as_deref() {
548 validate_rule_select_scopes(catalog, left)?;
549 }
550 validate_rule_select_scopes(catalog, &set_op.right)?;
551 for order in &set_op.combined_order_by {
552 validate_rule_expr_scopes(catalog, &order.expr)?;
553 }
554 if let Some(limit) = &set_op.combined_limit {
555 validate_rule_expr_scopes(catalog, limit)?;
556 }
557 if let Some(offset) = &set_op.combined_offset {
558 validate_rule_expr_scopes(catalog, offset)?;
559 }
560 }
561 for projection in &select.projections {
562 validate_rule_expr_scopes(catalog, &projection.expr)?;
563 }
564 for expr in select.values.iter().flatten() {
565 validate_rule_expr_scopes(catalog, expr)?;
566 }
567 if let Some(from) = &select.from {
568 validate_rule_from_scopes(catalog, from)?;
569 }
570 for expr in select
571 .r#where
572 .iter()
573 .chain(select.group_by.iter())
574 .chain(select.grouping_sets.iter().flatten())
575 .chain(select.having.iter())
576 .chain(select.order_by.iter().map(|order| &order.expr))
577 .chain(select.limit.iter())
578 .chain(select.offset.iter())
579 .chain(select.distinct_on.iter())
580 {
581 validate_rule_expr_scopes(catalog, expr)?;
582 }
583 Ok(())
584}
585
586fn validate_rule_from_scopes(
587 catalog: &dyn RuleSourceCatalog,
588 from: &FromClause,
589) -> Result<(), SQLError> {
590 match from {
591 FromClause::Table { .. } => {}
592 FromClause::Join {
593 left, right, on, ..
594 } => {
595 validate_rule_from_scopes(catalog, left)?;
596 validate_rule_from_scopes(catalog, right)?;
597 if let Some(on) = on {
598 validate_rule_expr_scopes(catalog, on)?;
599 }
600 }
601 FromClause::Values { rows, .. } => {
602 for expr in rows.iter().flatten() {
603 validate_rule_expr_scopes(catalog, expr)?;
604 }
605 }
606 FromClause::Function { args, .. } => {
607 for expr in args {
608 validate_rule_expr_scopes(catalog, expr)?;
609 }
610 }
611 FromClause::FunctionGroup { functions, .. } => {
612 for expr in functions.iter().flat_map(|function| &function.args) {
613 validate_rule_expr_scopes(catalog, expr)?;
614 }
615 }
616 FromClause::Subquery { body, .. } => validate_rule_select_scopes(catalog, body)?,
617 }
618 Ok(())
619}
620
621fn validate_rule_expr_scopes(catalog: &dyn RuleSourceCatalog, expr: &Expr) -> Result<(), SQLError> {
622 match expr {
623 Expr::Func {
624 args,
625 order_by,
626 filter,
627 ..
628 } => {
629 for expr in args {
630 validate_rule_expr_scopes(catalog, expr)?;
631 }
632 for order in order_by {
633 validate_rule_expr_scopes(catalog, &order.expr)?;
634 }
635 if let Some(filter) = filter {
636 validate_rule_expr_scopes(catalog, filter)?;
637 }
638 }
639 Expr::Array(items) | Expr::Row(items) | Expr::And(items) | Expr::Or(items) => {
640 for expr in items {
641 validate_rule_expr_scopes(catalog, expr)?;
642 }
643 }
644 Expr::Binary { lhs, rhs, .. } => {
645 validate_rule_expr_scopes(catalog, lhs)?;
646 validate_rule_expr_scopes(catalog, rhs)?;
647 }
648 Expr::UnaryMinus(expr)
649 | Expr::Not(expr)
650 | Expr::IsNull { expr, .. }
651 | Expr::Cast { expr, .. } => validate_rule_expr_scopes(catalog, expr)?,
652 Expr::Between { expr, low, high } => {
653 validate_rule_expr_scopes(catalog, expr)?;
654 validate_rule_expr_scopes(catalog, low)?;
655 validate_rule_expr_scopes(catalog, high)?;
656 }
657 Expr::InList { expr, list, .. } => {
658 validate_rule_expr_scopes(catalog, expr)?;
659 for item in list {
660 validate_rule_expr_scopes(catalog, item)?;
661 }
662 }
663 Expr::WindowCall { args, spec, .. } => {
664 for expr in args.iter().chain(spec.partition_by.iter()) {
665 validate_rule_expr_scopes(catalog, expr)?;
666 }
667 for order in &spec.order_by {
668 validate_rule_expr_scopes(catalog, &order.expr)?;
669 }
670 }
671 Expr::Case {
672 base,
673 when,
674 else_branch,
675 } => {
676 if let Some(base) = base {
677 validate_rule_expr_scopes(catalog, base)?;
678 }
679 for (condition, result) in when {
680 validate_rule_expr_scopes(catalog, condition)?;
681 validate_rule_expr_scopes(catalog, result)?;
682 }
683 if let Some(else_branch) = else_branch {
684 validate_rule_expr_scopes(catalog, else_branch)?;
685 }
686 }
687 Expr::ScalarSubquery(body) | Expr::Exists { body, .. } => {
688 validate_rule_select_scopes(catalog, body)?;
689 }
690 Expr::InSubquery { expr, body, .. } => {
691 validate_rule_expr_scopes(catalog, expr)?;
692 validate_rule_select_scopes(catalog, body)?;
693 }
694 Expr::Default
695 | Expr::Literal(_)
696 | Expr::TypedLiteral { .. }
697 | Expr::Star
698 | Expr::QualifiedStar(_)
699 | Expr::Column(_)
700 | Expr::QualifiedColumn { .. }
701 | Expr::InternalColumn(_)
702 | Expr::Param(_) => {}
703 }
704 Ok(())
705}
706
707pub fn validate_rule_action_reference_scopes(
708 catalog: &dyn RuleSourceCatalog,
709 action: &Statement,
710) -> Result<(), SQLError> {
711 validate_rule_action_namespace(catalog, action)?;
712 match action {
713 Statement::Select(select) => validate_rule_select_scopes(catalog, select),
714 Statement::Insert(insert) => {
715 validate_rule_ctes(catalog, &insert.with)?;
716 for expr in insert.rows.iter().flatten() {
717 validate_rule_expr_scopes(catalog, expr)?;
718 }
719 if let Some(select) = &insert.select_source {
720 validate_rule_select_scopes(catalog, select)?;
721 }
722 if let Some(conflict) = &insert.on_conflict {
723 for expression in &conflict.expressions {
724 validate_rule_expr_scopes(catalog, expression)?;
725 }
726 if let Some(predicate) = conflict.predicate.as_deref() {
727 validate_rule_expr_scopes(catalog, predicate)?;
728 }
729 if let OnConflictAction::Update {
730 assignments,
731 r#where,
732 } = &conflict.action
733 {
734 let reference = assignments
735 .iter()
736 .find_map(|(_, expr)| {
737 let mut shadowed = std::collections::BTreeSet::new();
738 shadowed.insert(insert.target_qualifier.to_ascii_lowercase());
739 first_rule_row_reference_in_expr(expr, &shadowed)
740 })
741 .or_else(|| {
742 r#where.as_ref().and_then(|expr| {
743 let mut shadowed = std::collections::BTreeSet::new();
744 shadowed.insert(insert.target_qualifier.to_ascii_lowercase());
745 first_rule_row_reference_in_expr(expr, &shadowed)
746 })
747 });
748 if let Some(qualifier) = reference {
749 return Err(invalid_rule_action_reference(&qualifier));
750 }
751 for (_, expr) in assignments {
752 validate_rule_expr_scopes(catalog, expr)?;
753 }
754 if let Some(r#where) = r#where {
755 validate_rule_expr_scopes(catalog, r#where)?;
756 }
757 }
758 }
759 for projection in &insert.returning {
760 validate_rule_expr_scopes(catalog, &projection.expr)?;
761 }
762 Ok(())
763 }
764 Statement::Update(update) => {
765 validate_rule_ctes(catalog, &update.with)?;
766 if let Some(from) = &update.from {
767 validate_rule_from_scopes(catalog, from)?;
768 }
769 for expr in update
770 .assignments
771 .iter()
772 .map(|(_, expr)| expr)
773 .chain(update.r#where.iter())
774 .chain(update.returning.iter().map(|projection| &projection.expr))
775 {
776 validate_rule_expr_scopes(catalog, expr)?;
777 }
778 Ok(())
779 }
780 Statement::Delete(delete) => {
781 validate_rule_ctes(catalog, &delete.with)?;
782 if let Some(using) = &delete.using {
783 validate_rule_from_scopes(catalog, using)?;
784 }
785 for expr in delete
786 .r#where
787 .iter()
788 .chain(delete.returning.iter().map(|projection| &projection.expr))
789 {
790 validate_rule_expr_scopes(catalog, expr)?;
791 }
792 Ok(())
793 }
794 _ => Ok(()),
795 }
796}
797
798pub fn validate_trigger_transition_relation(
799 definition: &CreateTrigger,
800 hierarchy: &TableHierarchy,
801 transition: &TriggerTransitionRelation,
802) -> Result<(), SQLError> {
803 if !transition.is_table {
804 return Err(SQLError::Routine {
805 sqlstate: "0A000".into(),
806 message: "ROW variable naming in the REFERENCING clause is not supported".into(),
807 });
808 }
809 if definition.row && !hierarchy.parents.is_empty() {
810 return Err(SQLError::Routine {
811 sqlstate: "0A000".into(),
812 message: if hierarchy.partition_bound.is_some() {
813 "ROW triggers with transition tables are not supported on partitions".into()
814 } else {
815 "ROW triggers with transition tables are not supported on inheritance children"
816 .into()
817 },
818 });
819 }
820 if definition.timing != TriggerTiming::After {
821 return Err(SQLError::Routine {
822 sqlstate: "42P17".into(),
823 message: "transition table name can only be specified for an AFTER trigger".into(),
824 });
825 }
826 if definition.events.contains(&TriggerEvent::Truncate) {
827 return Err(SQLError::Routine {
828 sqlstate: "0A000".into(),
829 message: "TRUNCATE triggers with transition tables are not supported".into(),
830 });
831 }
832 let mutation_events = definition
833 .events
834 .iter()
835 .filter(|event| {
836 matches!(
837 event,
838 TriggerEvent::Insert | TriggerEvent::Update | TriggerEvent::Delete
839 )
840 })
841 .count();
842 if mutation_events != 1 {
843 return Err(SQLError::Routine {
844 sqlstate: "0A000".into(),
845 message: "transition tables cannot be specified for triggers with more than one event"
846 .into(),
847 });
848 }
849 if !definition.update_columns.is_empty() {
850 return Err(SQLError::Routine {
851 sqlstate: "0A000".into(),
852 message: "transition tables cannot be specified for triggers with column lists".into(),
853 });
854 }
855 let valid_event = definition.events.iter().any(|event| {
856 if transition.is_new {
857 matches!(event, TriggerEvent::Insert | TriggerEvent::Update)
858 } else {
859 matches!(event, TriggerEvent::Delete | TriggerEvent::Update)
860 }
861 });
862 if !valid_event {
863 return Err(SQLError::Routine {
864 sqlstate: "42P17".into(),
865 message: format!(
866 "{} TABLE can only be specified for {} trigger",
867 if transition.is_new { "NEW" } else { "OLD" },
868 if transition.is_new {
869 "an INSERT or UPDATE"
870 } else {
871 "a DELETE or UPDATE"
872 }
873 ),
874 });
875 }
876 Ok(())
877}
878
879#[cfg(test)]
880mod tests;