Skip to main content

uqa_sql/semantics/rules/
action_binding.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Query-scope-aware binding for rewrite-rule OLD/NEW pseudo-relations.
8
9use std::collections::{BTreeMap, BTreeSet};
10
11use crate::ast::{
12    Expr, FrameBound, FromClause, OrderBy, Projection, RuleEvent, SelectStmt, Statement,
13    UpdateStmt, CTE,
14};
15use crate::plpgsql::{bind_statement, ResolvedVariable, VariableResolver};
16use crate::SQLError;
17
18mod insert;
19use insert::bind_insert;
20mod namespace;
21mod references;
22mod returning;
23mod row_expansion;
24mod scope;
25pub use references::{
26    first_rule_row_reference_in_expr, first_rule_row_reference_in_select,
27    first_rule_row_reference_in_statement, rule_action_has_set_operation,
28    rule_condition_plan_references_whole_row, rule_condition_plan_row_columns,
29    rule_expr_references_row, rule_expr_references_whole_row, rule_expr_row_columns,
30    rule_new_row_columns, rule_statement_references_row, rule_statement_references_whole_row,
31    rule_statement_row_columns,
32};
33pub use returning::expand_rule_action_returning_stars;
34pub use row_expansion::expand_rule_action_row_stars;
35use scope::{
36    apply_positional_aliases, collect_visible_scope, cte_output_columns, RuleBindingScope,
37};
38
39pub fn action_target_qualifier_referenced(
40    catalog: &dyn RuleSourceCatalog,
41    action: &Statement,
42    qualifier: &str,
43) -> bool {
44    namespace::action_target_qualifier_referenced(catalog, action, qualifier)
45}
46
47mod context;
48use context::RuleBindingContext;
49pub use context::{rule_action_target_columns, rule_action_target_row_type, RuleSourceCatalog};
50
51/// Bind an action's event-row OLD/NEW references while preserving the action
52/// row-image aliases visible to its DML RETURNING clause.
53pub fn bind_rule_action(
54    catalog: &dyn RuleSourceCatalog,
55    action: &Statement,
56    action_columns: &BTreeSet<String>,
57    resolver: &mut dyn VariableResolver,
58) -> Result<Statement, SQLError> {
59    let context = RuleBindingContext::with_catalog(catalog, true);
60    let (returning, action_event) = match action {
61        Statement::Insert(statement) => (&statement.returning, RuleEvent::Insert),
62        Statement::Update(statement) => (&statement.returning, RuleEvent::Update),
63        Statement::Delete(statement) => (&statement.returning, RuleEvent::Delete),
64        _ => {
65            return bind_rule_statement_body(
66                action,
67                resolver,
68                &RuleBindingScope::default(),
69                &context,
70            )
71        }
72    };
73    let mut body = action.clone();
74    let aliases = match &mut body {
75        Statement::Insert(statement) => {
76            statement.returning.clear();
77            statement.returning_aliases.clone()
78        }
79        Statement::Update(statement) => {
80            statement.returning.clear();
81            statement.returning_aliases.clone()
82        }
83        Statement::Delete(statement) => {
84            statement.returning.clear();
85            statement.returning_aliases.clone()
86        }
87        _ => unreachable!("DML rule action changed statement kind"),
88    };
89    let mut bound = {
90        let mut body_resolver = RuleActionBodyResolver {
91            action_columns,
92            action_event,
93            event_resolver: resolver,
94        };
95        bind_rule_statement_body(
96            &body,
97            &mut body_resolver,
98            &RuleBindingScope::default(),
99            &context,
100        )?
101    };
102    if returning.is_empty() {
103        return Ok(bound);
104    }
105    let returning = returning::bind_rule_action_returning(
106        returning,
107        action_columns,
108        &aliases,
109        action_event,
110        resolver,
111        &context,
112    )?;
113    match &mut bound {
114        Statement::Insert(statement) => statement.returning = returning,
115        Statement::Update(statement) => statement.returning = returning,
116        Statement::Delete(statement) => statement.returning = returning,
117        _ => unreachable!("bound DML rule action changed statement kind"),
118    }
119    Ok(bound)
120}
121
122struct RuleActionBodyResolver<'a, 'resolver> {
123    action_columns: &'a BTreeSet<String>,
124    action_event: RuleEvent,
125    event_resolver: &'resolver mut dyn VariableResolver,
126}
127
128impl RuleActionBodyResolver<'_, '_> {
129    fn is_action_column(&self, name: &str) -> bool {
130        matches!(self.action_event, RuleEvent::Update | RuleEvent::Delete)
131            && self
132                .action_columns
133                .iter()
134                .any(|column| column.eq_ignore_ascii_case(name))
135    }
136}
137
138impl VariableResolver for RuleActionBodyResolver<'_, '_> {
139    fn resolve_name(&mut self, name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
140        if self.is_action_column(name) {
141            Ok(None)
142        } else {
143            self.event_resolver.resolve_name(name)
144        }
145    }
146
147    fn resolve_qualified(
148        &mut self,
149        qualifier: &str,
150        column: &str,
151    ) -> Result<Option<ResolvedVariable>, SQLError> {
152        self.event_resolver.resolve_qualified(qualifier, column)
153    }
154
155    fn resolve_param(&mut self, index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
156        self.event_resolver.resolve_param(index)
157    }
158
159    fn rewrite_name(&mut self, name: &str) -> Result<Option<Expr>, SQLError> {
160        if self.is_action_column(name) {
161            Ok(None)
162        } else {
163            self.event_resolver.rewrite_name(name)
164        }
165    }
166
167    fn rewrite_qualified(
168        &mut self,
169        qualifier: &str,
170        column: &str,
171    ) -> Result<Option<Expr>, SQLError> {
172        self.event_resolver.rewrite_qualified(qualifier, column)
173    }
174
175    fn rewrite_qualified_star(&mut self, qualifier: &str) -> Result<Option<Vec<Expr>>, SQLError> {
176        self.event_resolver.rewrite_qualified_star(qualifier)
177    }
178
179    fn rewrite_qualified_whole_row(&mut self, qualifier: &str) -> Result<Option<Expr>, SQLError> {
180        self.event_resolver.rewrite_qualified_whole_row(qualifier)
181    }
182
183    fn rewrite_param(&mut self, index: usize) -> Result<Option<Expr>, SQLError> {
184        self.event_resolver.rewrite_param(index)
185    }
186
187    fn rewrite_internal(
188        &mut self,
189        column: crate::ast::InternalColumnRef,
190    ) -> Result<Option<Expr>, SQLError> {
191        self.event_resolver.rewrite_internal(column)
192    }
193}
194
195/// Bind one expression while masking relation qualifiers supplied by the SQL scope around it. Nested query scopes add their own visible aliases.
196pub fn bind_rule_expr_scoped(
197    expr: &Expr,
198    resolver: &mut dyn VariableResolver,
199    shadowed: &BTreeSet<String>,
200) -> Result<Expr, SQLError> {
201    bind_rule_expr_with_scope(
202        expr,
203        resolver,
204        &RuleBindingScope::from_qualifiers(shadowed),
205        &RuleBindingContext::default(),
206    )
207}
208
209fn bind_rule_expr_with_scope(
210    expr: &Expr,
211    resolver: &mut dyn VariableResolver,
212    scope: &RuleBindingScope,
213    context: &RuleBindingContext<'_>,
214) -> Result<Expr, SQLError> {
215    Ok(match expr {
216        Expr::Column(name) => {
217            if scope.qualifier_is_shadowed(name) || scope.column_is_shadowed(name) {
218                expr.clone()
219            } else {
220                resolver.rewrite_name(name)?.unwrap_or_else(|| expr.clone())
221            }
222        }
223        Expr::QualifiedColumn { qualifier, column } => {
224            if scope.qualifier_is_shadowed(qualifier) {
225                expr.clone()
226            } else {
227                resolver
228                    .rewrite_qualified(qualifier, column)?
229                    .unwrap_or_else(|| expr.clone())
230            }
231        }
232        Expr::Param(index) => resolver
233            .rewrite_param(*index)?
234            .unwrap_or_else(|| expr.clone()),
235        Expr::InternalColumn(column) => resolver
236            .rewrite_internal(*column)?
237            .unwrap_or_else(|| expr.clone()),
238        Expr::QualifiedStar(qualifier) => {
239            if scope.qualifier_is_shadowed(qualifier) {
240                expr.clone()
241            } else {
242                resolver
243                    .rewrite_qualified_whole_row(qualifier)?
244                    .unwrap_or_else(|| expr.clone())
245            }
246        }
247        Expr::Default | Expr::Literal(_) | Expr::TypedLiteral { .. } | Expr::Star => expr.clone(),
248        Expr::Func { .. } => bind_rule_function_expression(expr, resolver, scope, context)?,
249        Expr::Array(items) => Expr::Array(bind_exprs(items, resolver, scope, context)?),
250        Expr::Row(items) => Expr::Row(bind_expanding_exprs(items, resolver, scope, context)?),
251        Expr::Binary { op, lhs, rhs } => Expr::Binary {
252            op: *op,
253            lhs: Box::new(bind_rule_expr_with_scope(lhs, resolver, scope, context)?),
254            rhs: Box::new(bind_rule_expr_with_scope(rhs, resolver, scope, context)?),
255        },
256        Expr::UnaryMinus(inner) => Expr::UnaryMinus(Box::new(bind_rule_expr_with_scope(
257            inner, resolver, scope, context,
258        )?)),
259        Expr::Not(inner) => Expr::Not(Box::new(bind_rule_expr_with_scope(
260            inner, resolver, scope, context,
261        )?)),
262        Expr::And(items) => Expr::And(bind_exprs(items, resolver, scope, context)?),
263        Expr::Or(items) => Expr::Or(bind_exprs(items, resolver, scope, context)?),
264        Expr::IsNull { expr, negated } => Expr::IsNull {
265            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
266            negated: *negated,
267        },
268        Expr::Between { expr, low, high } => Expr::Between {
269            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
270            low: Box::new(bind_rule_expr_with_scope(low, resolver, scope, context)?),
271            high: Box::new(bind_rule_expr_with_scope(high, resolver, scope, context)?),
272        },
273        Expr::InList {
274            expr,
275            list,
276            negated,
277        } => Expr::InList {
278            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
279            list: bind_exprs(list, resolver, scope, context)?,
280            negated: *negated,
281        },
282        Expr::WindowCall { .. } => bind_rule_window_expression(expr, resolver, scope, context)?,
283        Expr::Case { .. } => bind_rule_case_expression(expr, resolver, scope, context)?,
284        Expr::Cast { expr, ty } => Expr::Cast {
285            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
286            ty: ty.clone(),
287        },
288        Expr::ScalarSubquery(body) => Expr::ScalarSubquery(Box::new(bind_select_with_scope(
289            body, resolver, scope, context,
290        )?)),
291        Expr::Exists { body, negated } => Expr::Exists {
292            body: Box::new(bind_select_with_scope(body, resolver, scope, context)?),
293            negated: *negated,
294        },
295        Expr::InSubquery {
296            expr,
297            body,
298            negated,
299        } => Expr::InSubquery {
300            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
301            body: Box::new(bind_select_with_scope(body, resolver, scope, context)?),
302            negated: *negated,
303        },
304    })
305}
306
307fn bind_rule_function_expression(
308    expr: &Expr,
309    resolver: &mut dyn VariableResolver,
310    scope: &RuleBindingScope,
311    context: &RuleBindingContext<'_>,
312) -> Result<Expr, SQLError> {
313    let Expr::Func {
314        name,
315        binding,
316        args,
317        distinct,
318        order_by,
319        filter,
320    } = expr
321    else {
322        unreachable!("function binder received a non-function expression")
323    };
324    Ok(Expr::Func {
325        name: name.clone(),
326        binding: binding.clone(),
327        args: bind_exprs(args, resolver, scope, context)?,
328        distinct: *distinct,
329        order_by: bind_orders(order_by, resolver, scope, context)?,
330        filter: filter
331            .as_deref()
332            .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context).map(Box::new))
333            .transpose()?,
334    })
335}
336
337fn bind_rule_window_expression(
338    expr: &Expr,
339    resolver: &mut dyn VariableResolver,
340    scope: &RuleBindingScope,
341    context: &RuleBindingContext<'_>,
342) -> Result<Expr, SQLError> {
343    let Expr::WindowCall { name, args, spec } = expr else {
344        unreachable!("window binder received a non-window expression")
345    };
346    Ok(Expr::WindowCall {
347        name: name.clone(),
348        args: bind_exprs(args, resolver, scope, context)?,
349        spec: crate::ast::WindowSpec {
350            reference: spec.reference.clone(),
351            partition_by: bind_exprs(&spec.partition_by, resolver, scope, context)?,
352            order_by: bind_orders(&spec.order_by, resolver, scope, context)?,
353            frame: spec
354                .frame
355                .as_ref()
356                .map(|frame| -> Result<crate::ast::WindowFrame, SQLError> {
357                    Ok(crate::ast::WindowFrame {
358                        mode: frame.mode,
359                        start: bind_frame_bound(&frame.start, resolver, scope, context)?,
360                        end: bind_frame_bound(&frame.end, resolver, scope, context)?,
361                    })
362                })
363                .transpose()?,
364        },
365    })
366}
367
368fn bind_rule_case_expression(
369    expr: &Expr,
370    resolver: &mut dyn VariableResolver,
371    scope: &RuleBindingScope,
372    context: &RuleBindingContext<'_>,
373) -> Result<Expr, SQLError> {
374    let Expr::Case {
375        base,
376        when,
377        else_branch,
378    } = expr
379    else {
380        unreachable!("CASE binder received a non-CASE expression")
381    };
382    Ok(Expr::Case {
383        base: base
384            .as_deref()
385            .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context).map(Box::new))
386            .transpose()?,
387        when: when
388            .iter()
389            .map(|(condition, result)| {
390                Ok((
391                    bind_rule_expr_with_scope(condition, resolver, scope, context)?,
392                    bind_rule_expr_with_scope(result, resolver, scope, context)?,
393                ))
394            })
395            .collect::<Result<Vec<_>, SQLError>>()?,
396        else_branch: else_branch
397            .as_deref()
398            .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context).map(Box::new))
399            .transpose()?,
400    })
401}
402
403fn bind_rule_select_scoped(
404    catalog: &dyn RuleSourceCatalog,
405    select: &SelectStmt,
406    resolver: &mut dyn VariableResolver,
407) -> Result<SelectStmt, SQLError> {
408    bind_select_with_scope(
409        select,
410        resolver,
411        &RuleBindingScope::default(),
412        &RuleBindingContext::with_catalog(catalog, false),
413    )
414}
415
416fn bind_rule_statement_body(
417    statement: &Statement,
418    resolver: &mut dyn VariableResolver,
419    inherited: &RuleBindingScope,
420    context: &RuleBindingContext<'_>,
421) -> Result<Statement, SQLError> {
422    Ok(match statement {
423        Statement::Select(select) => Statement::Select(Box::new(bind_select_with_scope(
424            select, resolver, inherited, context,
425        )?)),
426        Statement::Insert(insert) => {
427            Statement::Insert(bind_insert(insert, resolver, inherited, context)?)
428        }
429        Statement::Update(update) => {
430            Statement::Update(bind_update(update, resolver, inherited, context)?)
431        }
432        Statement::Delete(delete) => {
433            let mut output = delete.clone();
434            output.with = bind_ctes(&delete.with, resolver, inherited, context)?;
435            let context = context.with_ctes(&delete.with)?;
436            let mut target_scope = inherited.clone();
437            target_scope.insert_qualifier(&delete.target_qualifier);
438            output.using = delete
439                .using
440                .as_ref()
441                .map(|source| bind_from(source, resolver, &target_scope, &context))
442                .transpose()?;
443            let mut expression_scope = target_scope;
444            if let Some(source) = &delete.using {
445                collect_visible_scope(source, &context, &mut expression_scope)?;
446            }
447            output.r#where = bind_optional_expr(
448                delete.r#where.as_ref(),
449                resolver,
450                &expression_scope,
451                &context,
452            )?;
453            output.returning.clear();
454            Statement::Delete(output)
455        }
456        _ => return bind_statement(statement, resolver),
457    })
458}
459
460fn bind_update(
461    update: &UpdateStmt,
462    resolver: &mut dyn VariableResolver,
463    inherited: &RuleBindingScope,
464    context: &RuleBindingContext<'_>,
465) -> Result<UpdateStmt, SQLError> {
466    let mut output = update.clone();
467    output.with = bind_ctes(&update.with, resolver, inherited, context)?;
468    let context = context.with_ctes(&update.with)?;
469    let mut target_scope = inherited.clone();
470    target_scope.insert_qualifier(&update.target_qualifier);
471    output.from = update
472        .from
473        .as_ref()
474        .map(|source| bind_from(source, resolver, &target_scope, &context))
475        .transpose()?;
476    let mut expression_scope = target_scope;
477    if let Some(source) = &update.from {
478        collect_visible_scope(source, &context, &mut expression_scope)?;
479    }
480    output.assignments = update
481        .assignments
482        .iter()
483        .map(|(column, expr)| {
484            Ok((
485                column.clone(),
486                bind_rule_expr_with_scope(expr, resolver, &expression_scope, &context)?,
487            ))
488        })
489        .collect::<Result<Vec<_>, SQLError>>()?;
490    output.r#where = bind_optional_expr(
491        update.r#where.as_ref(),
492        resolver,
493        &expression_scope,
494        &context,
495    )?;
496    output.returning.clear();
497    Ok(output)
498}
499
500fn bind_select_with_scope(
501    select: &SelectStmt,
502    resolver: &mut dyn VariableResolver,
503    inherited: &RuleBindingScope,
504    context: &RuleBindingContext<'_>,
505) -> Result<SelectStmt, SQLError> {
506    let local_context = context.with_ctes(&select.with)?;
507    let mut scope = inherited.clone();
508    if let Some(source) = &select.from {
509        collect_visible_scope(source, &local_context, &mut scope)?;
510    }
511    Ok(SelectStmt {
512        projections: bind_projections(&select.projections, resolver, &scope, &local_context)?,
513        values: select
514            .values
515            .iter()
516            .map(|row| bind_expanding_exprs(row, resolver, &scope, &local_context))
517            .collect::<Result<Vec<_>, SQLError>>()?,
518        from: select
519            .from
520            .as_ref()
521            .map(|source| bind_from(source, resolver, inherited, &local_context))
522            .transpose()?,
523        r#where: bind_optional_expr(select.r#where.as_ref(), resolver, &scope, &local_context)?,
524        group_by: bind_exprs(&select.group_by, resolver, &scope, &local_context)?,
525        grouping_sets: select
526            .grouping_sets
527            .iter()
528            .map(|set| bind_exprs(set, resolver, &scope, &local_context))
529            .collect::<Result<Vec<_>, SQLError>>()?,
530        group_distinct: select.group_distinct,
531        having: bind_optional_expr(select.having.as_ref(), resolver, &scope, &local_context)?,
532        order_by: bind_orders(&select.order_by, resolver, &scope, &local_context)?,
533        limit: bind_optional_expr(select.limit.as_ref(), resolver, &scope, &local_context)?,
534        with_ties: select.with_ties,
535        offset: bind_optional_expr(select.offset.as_ref(), resolver, &scope, &local_context)?,
536        with: bind_ctes(&select.with, resolver, inherited, context)?,
537        set_op: select
538            .set_op
539            .as_ref()
540            .map(|operation| -> Result<Box<crate::ast::SetOp>, SQLError> {
541                Ok(Box::new(crate::ast::SetOp {
542                    kind: operation.kind,
543                    all: operation.all,
544                    left: operation
545                        .left
546                        .as_deref()
547                        .map(|left| {
548                            bind_select_with_scope(left, resolver, inherited, &local_context)
549                                .map(Box::new)
550                        })
551                        .transpose()?,
552                    right: bind_select_with_scope(
553                        &operation.right,
554                        resolver,
555                        inherited,
556                        &local_context,
557                    )?,
558                    combined_order_by: bind_orders(
559                        &operation.combined_order_by,
560                        resolver,
561                        inherited,
562                        &local_context,
563                    )?,
564                    combined_limit: bind_optional_expr(
565                        operation.combined_limit.as_ref(),
566                        resolver,
567                        inherited,
568                        &local_context,
569                    )?,
570                    combined_with_ties: operation.combined_with_ties,
571                    combined_offset: bind_optional_expr(
572                        operation.combined_offset.as_ref(),
573                        resolver,
574                        inherited,
575                        &local_context,
576                    )?,
577                }))
578            })
579            .transpose()?,
580        distinct: select.distinct,
581        distinct_on: bind_exprs(&select.distinct_on, resolver, &scope, &local_context)?,
582        locking: select.locking.clone(),
583    })
584}
585
586fn bind_from(
587    from: &FromClause,
588    resolver: &mut dyn VariableResolver,
589    inherited: &RuleBindingScope,
590    context: &RuleBindingContext<'_>,
591) -> Result<FromClause, SQLError> {
592    Ok(match from {
593        FromClause::Table { .. } => from.clone(),
594        FromClause::Join { .. } => bind_join_from(from, resolver, inherited, context)?,
595        FromClause::Values {
596            rows,
597            alias,
598            column_aliases,
599            internal_relation,
600            internal_column_types,
601        } => FromClause::Values {
602            rows: rows
603                .iter()
604                .map(|row| bind_exprs(row, resolver, inherited, context))
605                .collect::<Result<Vec<_>, SQLError>>()?,
606            alias: alias.clone(),
607            column_aliases: column_aliases.clone(),
608            internal_relation: *internal_relation,
609            internal_column_types: internal_column_types.clone(),
610        },
611        FromClause::Function {
612            name,
613            binding,
614            output_name,
615            relations,
616            args,
617            alias,
618            column_aliases,
619            ordinality,
620            column_types,
621        } => FromClause::Function {
622            name: name.clone(),
623            binding: binding.clone(),
624            output_name: output_name.clone(),
625            relations: relations.clone(),
626            args: bind_exprs(args, resolver, inherited, context)?,
627            alias: alias.clone(),
628            column_aliases: column_aliases.clone(),
629            ordinality: *ordinality,
630            column_types: column_types.clone(),
631        },
632        FromClause::FunctionGroup {
633            functions,
634            alias,
635            column_aliases,
636            ordinality,
637        } => FromClause::FunctionGroup {
638            functions: functions
639                .iter()
640                .map(|function| {
641                    Ok(crate::ast::TableFunction {
642                        name: function.name.clone(),
643                        binding: function.binding.clone(),
644                        output_name: function.output_name.clone(),
645                        relations: function.relations.clone(),
646                        args: bind_exprs(&function.args, resolver, inherited, context)?,
647                        column_aliases: function.column_aliases.clone(),
648                        column_types: function.column_types.clone(),
649                    })
650                })
651                .collect::<Result<Vec<_>, SQLError>>()?,
652            alias: alias.clone(),
653            column_aliases: column_aliases.clone(),
654            ordinality: *ordinality,
655        },
656        FromClause::Subquery {
657            body,
658            alias,
659            column_aliases,
660        } => FromClause::Subquery {
661            body: Box::new(bind_select_with_scope(body, resolver, inherited, context)?),
662            alias: alias.clone(),
663            column_aliases: column_aliases.clone(),
664        },
665    })
666}
667
668fn bind_join_from(
669    from: &FromClause,
670    resolver: &mut dyn VariableResolver,
671    inherited: &RuleBindingScope,
672    context: &RuleBindingContext<'_>,
673) -> Result<FromClause, SQLError> {
674    let FromClause::Join {
675        left,
676        right,
677        kind,
678        on,
679        using,
680        natural,
681        alias,
682        column_aliases,
683        lateral,
684    } = from
685    else {
686        unreachable!("join binder received a non-join source")
687    };
688    let bound_left = bind_from(left, resolver, inherited, context)?;
689    let mut right_scope = inherited.clone();
690    if *lateral {
691        collect_visible_scope(left, context, &mut right_scope)?;
692    }
693    let bound_right = bind_from(right, resolver, &right_scope, context)?;
694    let mut on_scope = inherited.clone();
695    collect_visible_scope(left, context, &mut on_scope)?;
696    collect_visible_scope(right, context, &mut on_scope)?;
697    Ok(FromClause::Join {
698        left: Box::new(bound_left),
699        right: Box::new(bound_right),
700        kind: *kind,
701        on: bind_optional_expr(on.as_ref(), resolver, &on_scope, context)?,
702        using: using.clone(),
703        natural: *natural,
704        alias: alias.clone(),
705        column_aliases: column_aliases.clone(),
706        lateral: *lateral,
707    })
708}
709
710fn bind_ctes(
711    ctes: &[CTE],
712    resolver: &mut dyn VariableResolver,
713    inherited: &RuleBindingScope,
714    context: &RuleBindingContext<'_>,
715) -> Result<Vec<CTE>, SQLError> {
716    let mut visible = context.clone();
717    let mut bound = Vec::with_capacity(ctes.len());
718    for cte in ctes {
719        if cte.recursive {
720            visible
721                .ctes
722                .entry(cte.name.to_ascii_lowercase())
723                .or_default();
724            let mut columns = cte_output_columns(&cte.body, &visible)?;
725            apply_positional_aliases(&mut columns, &cte.columns);
726            visible.ctes.insert(cte.name.to_ascii_lowercase(), columns);
727        }
728        bound.push(CTE {
729            name: cte.name.clone(),
730            columns: cte.columns.clone(),
731            recursive: cte.recursive,
732            materialization: cte.materialization,
733            search: cte.search.clone(),
734            cycle: cte
735                .cycle
736                .as_ref()
737                .map(|cycle| -> Result<crate::ast::CteCycleClause, SQLError> {
738                    Ok(crate::ast::CteCycleClause {
739                        columns: cycle.columns.clone(),
740                        mark_column: cycle.mark_column.clone(),
741                        mark_value: bind_rule_expr_with_scope(
742                            &cycle.mark_value,
743                            resolver,
744                            inherited,
745                            &visible,
746                        )?,
747                        mark_default: bind_rule_expr_with_scope(
748                            &cycle.mark_default,
749                            resolver,
750                            inherited,
751                            &visible,
752                        )?,
753                        path_column: cycle.path_column.clone(),
754                    })
755                })
756                .transpose()?,
757            body: match &cte.body {
758                crate::ast::CteBody::Query(query) => crate::ast::CteBody::Query(Box::new(
759                    bind_select_with_scope(query, resolver, inherited, &visible)?,
760                )),
761                // Command CTEs cannot reference a rule's event row. Their own DML row-image aliases belong to RETURNING and must retain their local identities.
762                body => body.clone(),
763            },
764        });
765        let mut columns = cte_output_columns(&cte.body, &visible)?;
766        apply_positional_aliases(&mut columns, &cte.columns);
767        if let Some(search) = &cte.search {
768            columns.push(search.sequence_column.clone());
769        }
770        if let Some(cycle) = &cte.cycle {
771            columns.push(cycle.mark_column.clone());
772            columns.push(cycle.path_column.clone());
773        }
774        visible.ctes.insert(cte.name.to_ascii_lowercase(), columns);
775    }
776    Ok(bound)
777}
778
779fn bind_frame_bound(
780    bound: &FrameBound,
781    resolver: &mut dyn VariableResolver,
782    scope: &RuleBindingScope,
783    context: &RuleBindingContext<'_>,
784) -> Result<FrameBound, SQLError> {
785    Ok(match bound {
786        FrameBound::Preceding(expr) => FrameBound::Preceding(Box::new(bind_rule_expr_with_scope(
787            expr, resolver, scope, context,
788        )?)),
789        FrameBound::Following(expr) => FrameBound::Following(Box::new(bind_rule_expr_with_scope(
790            expr, resolver, scope, context,
791        )?)),
792        _ => bound.clone(),
793    })
794}
795
796fn bind_projections(
797    projections: &[Projection],
798    resolver: &mut dyn VariableResolver,
799    scope: &RuleBindingScope,
800    context: &RuleBindingContext<'_>,
801) -> Result<Vec<Projection>, SQLError> {
802    let mut bound = Vec::with_capacity(projections.len());
803    for projection in projections {
804        if let Some(expressions) = expand_qualified_star(&projection.expr, resolver, scope)? {
805            bound.extend(expressions.into_iter().map(|expr| Projection {
806                expr,
807                alias: projection.alias.clone(),
808            }));
809        } else {
810            bound.push(Projection {
811                expr: bind_rule_expr_with_scope(&projection.expr, resolver, scope, context)?,
812                alias: projection.alias.clone(),
813            });
814        }
815    }
816    Ok(bound)
817}
818
819fn bind_expanding_exprs(
820    expressions: &[Expr],
821    resolver: &mut dyn VariableResolver,
822    scope: &RuleBindingScope,
823    context: &RuleBindingContext<'_>,
824) -> Result<Vec<Expr>, SQLError> {
825    let mut bound = Vec::with_capacity(expressions.len());
826    for expression in expressions {
827        if let Some(expressions) = expand_qualified_star(expression, resolver, scope)? {
828            bound.extend(expressions);
829        } else {
830            bound.push(bind_rule_expr_with_scope(
831                expression, resolver, scope, context,
832            )?);
833        }
834    }
835    Ok(bound)
836}
837
838fn expand_qualified_star(
839    expression: &Expr,
840    resolver: &mut dyn VariableResolver,
841    scope: &RuleBindingScope,
842) -> Result<Option<Vec<Expr>>, SQLError> {
843    let Expr::QualifiedStar(qualifier) = expression else {
844        return Ok(None);
845    };
846    if scope.qualifier_is_shadowed(qualifier) {
847        return Ok(None);
848    }
849    resolver.rewrite_qualified_star(qualifier)
850}
851
852fn bind_exprs(
853    expressions: &[Expr],
854    resolver: &mut dyn VariableResolver,
855    scope: &RuleBindingScope,
856    context: &RuleBindingContext<'_>,
857) -> Result<Vec<Expr>, SQLError> {
858    expressions
859        .iter()
860        .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context))
861        .collect()
862}
863
864fn bind_optional_expr(
865    expression: Option<&Expr>,
866    resolver: &mut dyn VariableResolver,
867    scope: &RuleBindingScope,
868    context: &RuleBindingContext<'_>,
869) -> Result<Option<Expr>, SQLError> {
870    expression
871        .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context))
872        .transpose()
873}
874
875fn bind_orders(
876    orders: &[OrderBy],
877    resolver: &mut dyn VariableResolver,
878    scope: &RuleBindingScope,
879    context: &RuleBindingContext<'_>,
880) -> Result<Vec<OrderBy>, SQLError> {
881    orders
882        .iter()
883        .map(|order| {
884            Ok(OrderBy {
885                expr: bind_rule_expr_with_scope(&order.expr, resolver, scope, context)?,
886                descending: order.descending,
887                nulls: order.nulls,
888            })
889        })
890        .collect()
891}
892
893pub fn invalid_rule_action_reference(qualifier: &str) -> SQLError {
894    SQLError::Routine {
895        sqlstate: "42P01".into(),
896        message: format!(
897            "invalid reference to FROM-clause entry for table \"{qualifier}\"\nDETAIL: There is an entry for table \"{qualifier}\", but it cannot be referenced from this part of the query."
898        ),
899    }
900}