Skip to main content

uqa_sql/catalog/events/
validation.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Trigger condition and rule pseudo-relation validation over SQL metadata.
8
9use 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            // PostgreSQL resolves an unknown literal against the event row's
271            // declared type when the rule target list is installed.
272            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;