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