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::Diagnostic {
246 sqlstate: "42P17".into(),
247 message: format!(
248 "RETURNING list's entry {} has different {difference} from column \"{column}\"",
249 position + 1
250 ),
251 detail: Some(format!(
252 "RETURNING list entry has type {}, but column has type {}.",
253 actual.sql_name(),
254 expected.sql_name()
255 )),
256 hint: None,
257 });
258 }
259 Ok(())
260}
261
262pub fn validate_trigger_condition_references(
263 definition: &CreateTrigger,
264 columns: &[crate::ast::ColumnDef],
265 condition: &Expr,
266) -> Result<(), SQLError> {
267 if condition.any_node(&|node| {
268 matches!(
269 node,
270 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. }
271 )
272 }) {
273 return Err(SQLError::Routine {
274 sqlstate: "0A000".into(),
275 message: "cannot use subquery in trigger WHEN condition".into(),
276 });
277 }
278 if condition.any_node(&|node| matches!(node, Expr::Column(_))) {
279 return Err(SQLError::Routine {
280 sqlstate: "42P01".into(),
281 message: "trigger WHEN condition must qualify row columns with OLD or NEW".into(),
282 });
283 }
284 let references_old = condition.any_node(&|node| {
285 matches!(node, Expr::QualifiedColumn { qualifier, .. } if qualifier.eq_ignore_ascii_case("old"))
286 });
287 let references_new = condition.any_node(&|node| {
288 matches!(node, Expr::QualifiedColumn { qualifier, .. } if qualifier.eq_ignore_ascii_case("new"))
289 });
290 if !definition.row && (references_old || references_new) {
291 return Err(SQLError::Routine {
292 sqlstate: "42P01".into(),
293 message: "statement trigger's WHEN condition cannot reference row values".into(),
294 });
295 }
296 if references_old && definition.events.contains(&TriggerEvent::Insert) {
297 return Err(SQLError::Routine {
298 sqlstate: "42P17".into(),
299 message: "INSERT trigger's WHEN condition cannot reference OLD values".into(),
300 });
301 }
302 if references_new && definition.events.contains(&TriggerEvent::Delete) {
303 return Err(SQLError::Routine {
304 sqlstate: "42P17".into(),
305 message: "DELETE trigger's WHEN condition cannot reference NEW values".into(),
306 });
307 }
308 let invalid_qualified_reference = std::cell::RefCell::new(None);
309 let _ = condition.any_node(&|node| {
310 let Expr::QualifiedColumn { qualifier, column } = node else {
311 return false;
312 };
313 if !qualifier.eq_ignore_ascii_case("old") && !qualifier.eq_ignore_ascii_case("new") {
314 *invalid_qualified_reference.borrow_mut() = Some(format!("{qualifier}.{column}"));
315 return true;
316 }
317 if !columns.iter().any(|definition| definition.name == *column) {
318 *invalid_qualified_reference.borrow_mut() = Some(column.clone());
319 return true;
320 }
321 false
322 });
323 if let Some(reference) = invalid_qualified_reference.into_inner() {
324 return Err(SQLError::UnknownColumn(reference));
325 }
326 if definition.timing == TriggerTiming::Before && references_new {
327 let generated = columns
328 .iter()
329 .filter(|column| column.generated.is_some())
330 .map(|column| column.name.as_str())
331 .collect::<std::collections::BTreeSet<_>>();
332 if condition.any_node(&|node| {
333 matches!(
334 node,
335 Expr::QualifiedColumn { qualifier, column }
336 if qualifier.eq_ignore_ascii_case("new")
337 && generated.contains(column.as_str())
338 )
339 }) {
340 return Err(SQLError::Routine {
341 sqlstate: "42P17".into(),
342 message: "BEFORE trigger's WHEN condition cannot reference NEW generated columns"
343 .into(),
344 });
345 }
346 }
347 Ok(())
348}
349
350pub fn first_invalid_rule_condition_qualifier(condition: &Expr) -> Option<String> {
351 let invalid = std::cell::RefCell::new(None);
352 let _ = condition.any_node(&|node| {
353 let Expr::QualifiedColumn { qualifier, .. } = node else {
354 return false;
355 };
356 if qualifier.eq_ignore_ascii_case("old") || qualifier.eq_ignore_ascii_case("new") {
357 return false;
358 }
359 *invalid.borrow_mut() = Some(qualifier.clone());
360 true
361 });
362 invalid.into_inner()
363}
364
365fn invalid_rule_cte_reference(qualifier: &str) -> SQLError {
366 SQLError::Routine {
367 sqlstate: "0A000".into(),
368 message: format!(
369 "cannot refer to {} within WITH query",
370 qualifier.to_ascii_uppercase()
371 ),
372 }
373}
374
375fn invalid_rule_set_operation_reference() -> SQLError {
376 SQLError::Routine {
377 sqlstate: "42P10".into(),
378 message:
379 "UNION/INTERSECT/EXCEPT member statement cannot refer to other relations of same query level"
380 .into(),
381 }
382}
383
384pub(super) use crate::semantics::rules::action_binding::invalid_rule_action_reference;
385
386fn ambiguous_rule_pseudo_relation(qualifier: &str) -> SQLError {
387 SQLError::Routine {
388 sqlstate: "42P09".into(),
389 message: format!("table reference \"{qualifier}\" is ambiguous"),
390 }
391}
392
393fn duplicate_rule_pseudo_relation(qualifier: &str) -> SQLError {
394 SQLError::Routine {
395 sqlstate: "42712".into(),
396 message: format!("table name \"{qualifier}\" specified more than once"),
397 }
398}
399
400fn rule_pseudo_relation_name(name: &str) -> Option<String> {
401 (name.eq_ignore_ascii_case("old") || name.eq_ignore_ascii_case("new"))
402 .then(|| name.to_ascii_lowercase())
403}
404
405fn first_rule_pseudo_relation_in_from(from: &FromClause) -> Option<String> {
406 match from {
407 FromClause::Table {
408 name,
409 qualifier,
410 alias,
411 ..
412 } => alias
413 .as_deref()
414 .and_then(rule_pseudo_relation_name)
415 .or_else(|| rule_pseudo_relation_name(qualifier))
416 .or_else(|| rule_pseudo_relation_name(name))
417 .or_else(|| {
418 name.rsplit_once('.')
419 .and_then(|(_, local)| rule_pseudo_relation_name(local.trim_matches('"')))
420 }),
421 FromClause::Join {
422 left, right, alias, ..
423 } => alias
424 .as_deref()
425 .and_then(rule_pseudo_relation_name)
426 .or_else(|| first_rule_pseudo_relation_in_from(left))
427 .or_else(|| first_rule_pseudo_relation_in_from(right)),
428 FromClause::Values { alias, .. } | FromClause::Subquery { alias, .. } => {
429 alias.as_deref().and_then(rule_pseudo_relation_name)
430 }
431 FromClause::Function {
432 output_name, alias, ..
433 } => rule_pseudo_relation_name(alias.as_deref().unwrap_or(output_name)),
434 FromClause::FunctionGroup {
435 functions, alias, ..
436 } => alias
437 .as_deref()
438 .and_then(rule_pseudo_relation_name)
439 .or_else(|| {
440 functions
441 .iter()
442 .find_map(|function| rule_pseudo_relation_name(&function.output_name))
443 }),
444 }
445}
446
447fn validate_rule_action_select_namespace(select: &SelectStmt) -> Result<(), SQLError> {
448 let duplicate = select
449 .with
450 .iter()
451 .find_map(|cte| rule_pseudo_relation_name(&cte.name))
452 .or_else(|| {
453 select
454 .from
455 .as_ref()
456 .and_then(first_rule_pseudo_relation_in_from)
457 });
458 if let Some(qualifier) = duplicate {
459 return Err(duplicate_rule_pseudo_relation(&qualifier));
460 }
461 Ok(())
462}
463
464fn validate_rule_action_namespace(
465 catalog: &dyn RuleSourceCatalog,
466 action: &Statement,
467) -> Result<(), SQLError> {
468 let (ctes, source) = match action {
469 Statement::Select(select) => return validate_rule_action_select_namespace(select),
470 Statement::Insert(insert) => (insert.with.as_slice(), insert.select_source.as_deref()),
471 Statement::Update(update) => {
472 if let Some(qualifier) = update
473 .from
474 .as_ref()
475 .and_then(first_rule_pseudo_relation_in_from)
476 {
477 return Err(duplicate_rule_pseudo_relation(&qualifier));
478 }
479 if let Some(qualifier) = rule_pseudo_relation_name(&update.target_qualifier) {
480 if crate::semantics::rules::action_binding::action_target_qualifier_referenced(
481 catalog, action, &qualifier,
482 ) {
483 return Err(ambiguous_rule_pseudo_relation(&qualifier));
484 }
485 }
486 (update.with.as_slice(), None)
487 }
488 Statement::Delete(delete) => {
489 if let Some(qualifier) = delete
490 .using
491 .as_ref()
492 .and_then(first_rule_pseudo_relation_in_from)
493 {
494 return Err(duplicate_rule_pseudo_relation(&qualifier));
495 }
496 if let Some(qualifier) = rule_pseudo_relation_name(&delete.target_qualifier) {
497 if crate::semantics::rules::action_binding::action_target_qualifier_referenced(
498 catalog, action, &qualifier,
499 ) {
500 return Err(ambiguous_rule_pseudo_relation(&qualifier));
501 }
502 }
503 (delete.with.as_slice(), None)
504 }
505 _ => return Ok(()),
506 };
507 if let Some(qualifier) = ctes
508 .iter()
509 .find_map(|cte| rule_pseudo_relation_name(&cte.name))
510 {
511 return Err(duplicate_rule_pseudo_relation(&qualifier));
512 }
513 if let Some(select) = source {
514 validate_rule_action_select_namespace(select)?;
515 }
516 Ok(())
517}
518
519fn validate_rule_ctes(
520 catalog: &dyn RuleSourceCatalog,
521 ctes: &[crate::ast::CTE],
522) -> Result<(), SQLError> {
523 for cte in ctes {
524 let statement = cte.body.clone().into_statement();
525 if let Some(qualifier) =
526 crate::semantics::rules::action_binding::first_rule_row_reference_in_statement(
527 catalog, &statement,
528 )?
529 {
530 return Err(invalid_rule_cte_reference(&qualifier));
531 }
532 validate_rule_action_reference_scopes(catalog, &statement)?;
533 }
534 Ok(())
535}
536
537fn validate_rule_select_scopes(
538 catalog: &dyn RuleSourceCatalog,
539 select: &SelectStmt,
540) -> Result<(), SQLError> {
541 validate_rule_ctes(catalog, &select.with)?;
542 if let Some(set_op) = &select.set_op {
543 let member_references_rule_row = set_op
544 .left
545 .as_deref()
546 .and_then(|left| first_rule_row_reference_in_select(catalog, left))
547 .or_else(|| first_rule_row_reference_in_select(catalog, &set_op.right));
548 if member_references_rule_row.is_some() {
549 return Err(invalid_rule_set_operation_reference());
550 }
551 if let Some(left) = set_op.left.as_deref() {
552 validate_rule_select_scopes(catalog, left)?;
553 }
554 validate_rule_select_scopes(catalog, &set_op.right)?;
555 for order in &set_op.combined_order_by {
556 validate_rule_expr_scopes(catalog, &order.expr)?;
557 }
558 if let Some(limit) = &set_op.combined_limit {
559 validate_rule_expr_scopes(catalog, limit)?;
560 }
561 if let Some(offset) = &set_op.combined_offset {
562 validate_rule_expr_scopes(catalog, offset)?;
563 }
564 }
565 for projection in &select.projections {
566 validate_rule_expr_scopes(catalog, &projection.expr)?;
567 }
568 for expr in select.values.iter().flatten() {
569 validate_rule_expr_scopes(catalog, expr)?;
570 }
571 if let Some(from) = &select.from {
572 validate_rule_from_scopes(catalog, from)?;
573 }
574 for expr in select
575 .r#where
576 .iter()
577 .chain(select.group_by.iter())
578 .chain(select.grouping_sets.iter().flatten())
579 .chain(select.having.iter())
580 .chain(select.order_by.iter().map(|order| &order.expr))
581 .chain(select.limit.iter())
582 .chain(select.offset.iter())
583 .chain(select.distinct_on.iter())
584 {
585 validate_rule_expr_scopes(catalog, expr)?;
586 }
587 Ok(())
588}
589
590fn validate_rule_from_scopes(
591 catalog: &dyn RuleSourceCatalog,
592 from: &FromClause,
593) -> Result<(), SQLError> {
594 match from {
595 FromClause::Table { .. } => {}
596 FromClause::Join {
597 left, right, on, ..
598 } => {
599 validate_rule_from_scopes(catalog, left)?;
600 validate_rule_from_scopes(catalog, right)?;
601 if let Some(on) = on {
602 validate_rule_expr_scopes(catalog, on)?;
603 }
604 }
605 FromClause::Values { rows, .. } => {
606 for expr in rows.iter().flatten() {
607 validate_rule_expr_scopes(catalog, expr)?;
608 }
609 }
610 FromClause::Function { args, .. } => {
611 for expr in args {
612 validate_rule_expr_scopes(catalog, expr)?;
613 }
614 }
615 FromClause::FunctionGroup { functions, .. } => {
616 for expr in functions.iter().flat_map(|function| &function.args) {
617 validate_rule_expr_scopes(catalog, expr)?;
618 }
619 }
620 FromClause::Subquery { body, .. } => validate_rule_select_scopes(catalog, body)?,
621 }
622 Ok(())
623}
624
625fn validate_rule_expr_scopes(catalog: &dyn RuleSourceCatalog, expr: &Expr) -> Result<(), SQLError> {
626 match expr {
627 Expr::Func {
628 args,
629 order_by,
630 filter,
631 ..
632 } => {
633 for expr in args {
634 validate_rule_expr_scopes(catalog, expr)?;
635 }
636 for order in order_by {
637 validate_rule_expr_scopes(catalog, &order.expr)?;
638 }
639 if let Some(filter) = filter {
640 validate_rule_expr_scopes(catalog, filter)?;
641 }
642 }
643 Expr::Array(items) | Expr::Row(items) | Expr::And(items) | Expr::Or(items) => {
644 for expr in items {
645 validate_rule_expr_scopes(catalog, expr)?;
646 }
647 }
648 Expr::Binary { lhs, rhs, .. } => {
649 validate_rule_expr_scopes(catalog, lhs)?;
650 validate_rule_expr_scopes(catalog, rhs)?;
651 }
652 Expr::UnaryMinus(expr)
653 | Expr::Not(expr)
654 | Expr::IsNull { expr, .. }
655 | Expr::Cast { expr, .. } => validate_rule_expr_scopes(catalog, expr)?,
656 Expr::Between { expr, low, high } => {
657 validate_rule_expr_scopes(catalog, expr)?;
658 validate_rule_expr_scopes(catalog, low)?;
659 validate_rule_expr_scopes(catalog, high)?;
660 }
661 Expr::InList { expr, list, .. } => {
662 validate_rule_expr_scopes(catalog, expr)?;
663 for item in list {
664 validate_rule_expr_scopes(catalog, item)?;
665 }
666 }
667 Expr::WindowCall { args, spec, .. } => {
668 for expr in args.iter().chain(spec.partition_by.iter()) {
669 validate_rule_expr_scopes(catalog, expr)?;
670 }
671 for order in &spec.order_by {
672 validate_rule_expr_scopes(catalog, &order.expr)?;
673 }
674 }
675 Expr::Case {
676 base,
677 when,
678 else_branch,
679 } => {
680 if let Some(base) = base {
681 validate_rule_expr_scopes(catalog, base)?;
682 }
683 for (condition, result) in when {
684 validate_rule_expr_scopes(catalog, condition)?;
685 validate_rule_expr_scopes(catalog, result)?;
686 }
687 if let Some(else_branch) = else_branch {
688 validate_rule_expr_scopes(catalog, else_branch)?;
689 }
690 }
691 Expr::ScalarSubquery(body) | Expr::Exists { body, .. } => {
692 validate_rule_select_scopes(catalog, body)?;
693 }
694 Expr::InSubquery { expr, body, .. } => {
695 validate_rule_expr_scopes(catalog, expr)?;
696 validate_rule_select_scopes(catalog, body)?;
697 }
698 Expr::Default
699 | Expr::Literal(_)
700 | Expr::TypedLiteral { .. }
701 | Expr::Star
702 | Expr::QualifiedStar(_)
703 | Expr::Column(_)
704 | Expr::QualifiedColumn { .. }
705 | Expr::InternalColumn(_)
706 | Expr::Param(_) => {}
707 }
708 Ok(())
709}
710
711pub fn validate_rule_action_reference_scopes(
712 catalog: &dyn RuleSourceCatalog,
713 action: &Statement,
714) -> Result<(), SQLError> {
715 validate_rule_action_namespace(catalog, action)?;
716 match action {
717 Statement::Select(select) => validate_rule_select_scopes(catalog, select),
718 Statement::Insert(insert) => {
719 validate_rule_ctes(catalog, &insert.with)?;
720 for expr in insert
721 .columns
722 .iter()
723 .flat_map(crate::ast::AssignmentTarget::expressions)
724 .chain(insert.rows.iter().flatten())
725 {
726 validate_rule_expr_scopes(catalog, expr)?;
727 }
728 if let Some(select) = &insert.select_source {
729 validate_rule_select_scopes(catalog, select)?;
730 }
731 if let Some(conflict) = &insert.on_conflict {
732 for expression in &conflict.expressions {
733 validate_rule_expr_scopes(catalog, expression)?;
734 }
735 if let Some(predicate) = conflict.predicate.as_deref() {
736 validate_rule_expr_scopes(catalog, predicate)?;
737 }
738 if let OnConflictAction::Update {
739 assignments,
740 r#where,
741 } = &conflict.action
742 {
743 let reference = assignments
744 .iter()
745 .flat_map(|(target, value)| {
746 target.expressions().chain(std::iter::once(value))
747 })
748 .find_map(|expr| {
749 let mut shadowed = std::collections::BTreeSet::new();
750 shadowed.insert(insert.target_qualifier.to_ascii_lowercase());
751 first_rule_row_reference_in_expr(expr, &shadowed)
752 })
753 .or_else(|| {
754 r#where.as_ref().and_then(|expr| {
755 let mut shadowed = std::collections::BTreeSet::new();
756 shadowed.insert(insert.target_qualifier.to_ascii_lowercase());
757 first_rule_row_reference_in_expr(expr, &shadowed)
758 })
759 });
760 if let Some(qualifier) = reference {
761 return Err(invalid_rule_action_reference(&qualifier));
762 }
763 for expr in assignments.iter().flat_map(|(target, value)| {
764 target.expressions().chain(std::iter::once(value))
765 }) {
766 validate_rule_expr_scopes(catalog, expr)?;
767 }
768 if let Some(r#where) = r#where {
769 validate_rule_expr_scopes(catalog, r#where)?;
770 }
771 }
772 }
773 for projection in &insert.returning {
774 validate_rule_expr_scopes(catalog, &projection.expr)?;
775 }
776 Ok(())
777 }
778 Statement::Update(update) => {
779 validate_rule_ctes(catalog, &update.with)?;
780 if let Some(from) = &update.from {
781 validate_rule_from_scopes(catalog, from)?;
782 }
783 for expr in update
784 .assignments
785 .iter()
786 .flat_map(|(target, value)| target.expressions().chain(std::iter::once(value)))
787 .chain(update.r#where.iter())
788 .chain(update.returning.iter().map(|projection| &projection.expr))
789 {
790 validate_rule_expr_scopes(catalog, expr)?;
791 }
792 Ok(())
793 }
794 Statement::Delete(delete) => {
795 validate_rule_ctes(catalog, &delete.with)?;
796 if let Some(using) = &delete.using {
797 validate_rule_from_scopes(catalog, using)?;
798 }
799 for expr in delete
800 .r#where
801 .iter()
802 .chain(delete.returning.iter().map(|projection| &projection.expr))
803 {
804 validate_rule_expr_scopes(catalog, expr)?;
805 }
806 Ok(())
807 }
808 _ => Ok(()),
809 }
810}
811
812pub fn validate_trigger_transition_relation(
813 definition: &CreateTrigger,
814 hierarchy: &TableHierarchy,
815 transition: &TriggerTransitionRelation,
816) -> Result<(), SQLError> {
817 if !transition.is_table {
818 return Err(SQLError::Routine {
819 sqlstate: "0A000".into(),
820 message: "ROW variable naming in the REFERENCING clause is not supported".into(),
821 });
822 }
823 if definition.row && !hierarchy.parents.is_empty() {
824 return Err(SQLError::Routine {
825 sqlstate: "0A000".into(),
826 message: if hierarchy.partition_bound.is_some() {
827 "ROW triggers with transition tables are not supported on partitions".into()
828 } else {
829 "ROW triggers with transition tables are not supported on inheritance children"
830 .into()
831 },
832 });
833 }
834 if definition.timing != TriggerTiming::After {
835 return Err(SQLError::Routine {
836 sqlstate: "42P17".into(),
837 message: "transition table name can only be specified for an AFTER trigger".into(),
838 });
839 }
840 if definition.events.contains(&TriggerEvent::Truncate) {
841 return Err(SQLError::Routine {
842 sqlstate: "0A000".into(),
843 message: "TRUNCATE triggers with transition tables are not supported".into(),
844 });
845 }
846 let mutation_events = definition
847 .events
848 .iter()
849 .filter(|event| {
850 matches!(
851 event,
852 TriggerEvent::Insert | TriggerEvent::Update | TriggerEvent::Delete
853 )
854 })
855 .count();
856 if mutation_events != 1 {
857 return Err(SQLError::Routine {
858 sqlstate: "0A000".into(),
859 message: "transition tables cannot be specified for triggers with more than one event"
860 .into(),
861 });
862 }
863 if !definition.update_columns.is_empty() {
864 return Err(SQLError::Routine {
865 sqlstate: "0A000".into(),
866 message: "transition tables cannot be specified for triggers with column lists".into(),
867 });
868 }
869 let valid_event = definition.events.iter().any(|event| {
870 if transition.is_new {
871 matches!(event, TriggerEvent::Insert | TriggerEvent::Update)
872 } else {
873 matches!(event, TriggerEvent::Delete | TriggerEvent::Update)
874 }
875 });
876 if !valid_event {
877 return Err(SQLError::Routine {
878 sqlstate: "42P17".into(),
879 message: format!(
880 "{} TABLE can only be specified for {} trigger",
881 if transition.is_new { "NEW" } else { "OLD" },
882 if transition.is_new {
883 "an INSERT or UPDATE"
884 } else {
885 "a DELETE or UPDATE"
886 }
887 ),
888 });
889 }
890 Ok(())
891}
892
893#[cfg(test)]
894mod tests;