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_assignment_target(
461    target: &crate::ast::AssignmentTarget,
462    resolver: &mut dyn VariableResolver,
463    scope: &RuleBindingScope,
464    context: &RuleBindingContext<'_>,
465) -> Result<crate::ast::AssignmentTarget, SQLError> {
466    let mut target = target.clone();
467    for expression in target.expressions_mut() {
468        *expression = bind_rule_expr_with_scope(expression, resolver, scope, context)?;
469    }
470    Ok(target)
471}
472
473fn bind_update(
474    update: &UpdateStmt,
475    resolver: &mut dyn VariableResolver,
476    inherited: &RuleBindingScope,
477    context: &RuleBindingContext<'_>,
478) -> Result<UpdateStmt, SQLError> {
479    let mut output = update.clone();
480    output.with = bind_ctes(&update.with, resolver, inherited, context)?;
481    let context = context.with_ctes(&update.with)?;
482    let mut target_scope = inherited.clone();
483    target_scope.insert_qualifier(&update.target_qualifier);
484    output.from = update
485        .from
486        .as_ref()
487        .map(|source| bind_from(source, resolver, &target_scope, &context))
488        .transpose()?;
489    let mut expression_scope = target_scope;
490    if let Some(source) = &update.from {
491        collect_visible_scope(source, &context, &mut expression_scope)?;
492    }
493    output.assignments = update
494        .assignments
495        .iter()
496        .map(|(column, expr)| {
497            Ok((
498                bind_assignment_target(column, resolver, &expression_scope, &context)?,
499                bind_rule_expr_with_scope(expr, resolver, &expression_scope, &context)?,
500            ))
501        })
502        .collect::<Result<Vec<_>, SQLError>>()?;
503    output.r#where = bind_optional_expr(
504        update.r#where.as_ref(),
505        resolver,
506        &expression_scope,
507        &context,
508    )?;
509    output.returning.clear();
510    Ok(output)
511}
512
513fn bind_select_with_scope(
514    select: &SelectStmt,
515    resolver: &mut dyn VariableResolver,
516    inherited: &RuleBindingScope,
517    context: &RuleBindingContext<'_>,
518) -> Result<SelectStmt, SQLError> {
519    let local_context = context.with_ctes(&select.with)?;
520    let mut scope = inherited.clone();
521    if let Some(source) = &select.from {
522        collect_visible_scope(source, &local_context, &mut scope)?;
523    }
524    Ok(SelectStmt {
525        projections: bind_projections(&select.projections, resolver, &scope, &local_context)?,
526        values: select
527            .values
528            .iter()
529            .map(|row| bind_expanding_exprs(row, resolver, &scope, &local_context))
530            .collect::<Result<Vec<_>, SQLError>>()?,
531        from: select
532            .from
533            .as_ref()
534            .map(|source| bind_from(source, resolver, inherited, &local_context))
535            .transpose()?,
536        r#where: bind_optional_expr(select.r#where.as_ref(), resolver, &scope, &local_context)?,
537        group_by: bind_exprs(&select.group_by, resolver, &scope, &local_context)?,
538        grouping_sets: select
539            .grouping_sets
540            .iter()
541            .map(|set| bind_exprs(set, resolver, &scope, &local_context))
542            .collect::<Result<Vec<_>, SQLError>>()?,
543        group_distinct: select.group_distinct,
544        having: bind_optional_expr(select.having.as_ref(), resolver, &scope, &local_context)?,
545        order_by: bind_orders(&select.order_by, resolver, &scope, &local_context)?,
546        limit: bind_optional_expr(select.limit.as_ref(), resolver, &scope, &local_context)?,
547        with_ties: select.with_ties,
548        offset: bind_optional_expr(select.offset.as_ref(), resolver, &scope, &local_context)?,
549        with: bind_ctes(&select.with, resolver, inherited, context)?,
550        set_op: select
551            .set_op
552            .as_ref()
553            .map(|operation| -> Result<Box<crate::ast::SetOp>, SQLError> {
554                Ok(Box::new(crate::ast::SetOp {
555                    kind: operation.kind,
556                    all: operation.all,
557                    left: operation
558                        .left
559                        .as_deref()
560                        .map(|left| {
561                            bind_select_with_scope(left, resolver, inherited, &local_context)
562                                .map(Box::new)
563                        })
564                        .transpose()?,
565                    right: bind_select_with_scope(
566                        &operation.right,
567                        resolver,
568                        inherited,
569                        &local_context,
570                    )?,
571                    combined_order_by: bind_orders(
572                        &operation.combined_order_by,
573                        resolver,
574                        inherited,
575                        &local_context,
576                    )?,
577                    combined_limit: bind_optional_expr(
578                        operation.combined_limit.as_ref(),
579                        resolver,
580                        inherited,
581                        &local_context,
582                    )?,
583                    combined_with_ties: operation.combined_with_ties,
584                    combined_offset: bind_optional_expr(
585                        operation.combined_offset.as_ref(),
586                        resolver,
587                        inherited,
588                        &local_context,
589                    )?,
590                }))
591            })
592            .transpose()?,
593        distinct: select.distinct,
594        distinct_on: bind_exprs(&select.distinct_on, resolver, &scope, &local_context)?,
595        locking: select.locking.clone(),
596    })
597}
598
599fn bind_from(
600    from: &FromClause,
601    resolver: &mut dyn VariableResolver,
602    inherited: &RuleBindingScope,
603    context: &RuleBindingContext<'_>,
604) -> Result<FromClause, SQLError> {
605    Ok(match from {
606        FromClause::Table { .. } => from.clone(),
607        FromClause::Join { .. } => bind_join_from(from, resolver, inherited, context)?,
608        FromClause::Values {
609            rows,
610            alias,
611            column_aliases,
612            internal_relation,
613            internal_column_types,
614        } => FromClause::Values {
615            rows: rows
616                .iter()
617                .map(|row| bind_exprs(row, resolver, inherited, context))
618                .collect::<Result<Vec<_>, SQLError>>()?,
619            alias: alias.clone(),
620            column_aliases: column_aliases.clone(),
621            internal_relation: *internal_relation,
622            internal_column_types: internal_column_types.clone(),
623        },
624        FromClause::Function {
625            name,
626            binding,
627            output_name,
628            relations,
629            args,
630            alias,
631            column_aliases,
632            ordinality,
633            column_types,
634        } => FromClause::Function {
635            name: name.clone(),
636            binding: binding.clone(),
637            output_name: output_name.clone(),
638            relations: relations.clone(),
639            args: bind_exprs(args, resolver, inherited, context)?,
640            alias: alias.clone(),
641            column_aliases: column_aliases.clone(),
642            ordinality: *ordinality,
643            column_types: column_types.clone(),
644        },
645        FromClause::FunctionGroup {
646            functions,
647            alias,
648            column_aliases,
649            ordinality,
650        } => FromClause::FunctionGroup {
651            functions: functions
652                .iter()
653                .map(|function| {
654                    Ok(crate::ast::TableFunction {
655                        name: function.name.clone(),
656                        binding: function.binding.clone(),
657                        output_name: function.output_name.clone(),
658                        relations: function.relations.clone(),
659                        args: bind_exprs(&function.args, resolver, inherited, context)?,
660                        column_aliases: function.column_aliases.clone(),
661                        column_types: function.column_types.clone(),
662                    })
663                })
664                .collect::<Result<Vec<_>, SQLError>>()?,
665            alias: alias.clone(),
666            column_aliases: column_aliases.clone(),
667            ordinality: *ordinality,
668        },
669        FromClause::Subquery {
670            body,
671            alias,
672            column_aliases,
673        } => FromClause::Subquery {
674            body: Box::new(bind_select_with_scope(body, resolver, inherited, context)?),
675            alias: alias.clone(),
676            column_aliases: column_aliases.clone(),
677        },
678    })
679}
680
681fn bind_join_from(
682    from: &FromClause,
683    resolver: &mut dyn VariableResolver,
684    inherited: &RuleBindingScope,
685    context: &RuleBindingContext<'_>,
686) -> Result<FromClause, SQLError> {
687    let FromClause::Join {
688        left,
689        right,
690        kind,
691        on,
692        using,
693        natural,
694        alias,
695        column_aliases,
696        lateral,
697    } = from
698    else {
699        unreachable!("join binder received a non-join source")
700    };
701    let bound_left = bind_from(left, resolver, inherited, context)?;
702    let mut right_scope = inherited.clone();
703    if *lateral {
704        collect_visible_scope(left, context, &mut right_scope)?;
705    }
706    let bound_right = bind_from(right, resolver, &right_scope, context)?;
707    let mut on_scope = inherited.clone();
708    collect_visible_scope(left, context, &mut on_scope)?;
709    collect_visible_scope(right, context, &mut on_scope)?;
710    Ok(FromClause::Join {
711        left: Box::new(bound_left),
712        right: Box::new(bound_right),
713        kind: *kind,
714        on: bind_optional_expr(on.as_ref(), resolver, &on_scope, context)?,
715        using: using.clone(),
716        natural: *natural,
717        alias: alias.clone(),
718        column_aliases: column_aliases.clone(),
719        lateral: *lateral,
720    })
721}
722
723fn bind_ctes(
724    ctes: &[CTE],
725    resolver: &mut dyn VariableResolver,
726    inherited: &RuleBindingScope,
727    context: &RuleBindingContext<'_>,
728) -> Result<Vec<CTE>, SQLError> {
729    let mut visible = context.clone();
730    let mut bound = Vec::with_capacity(ctes.len());
731    for cte in ctes {
732        if cte.recursive {
733            visible
734                .ctes
735                .entry(cte.name.to_ascii_lowercase())
736                .or_default();
737            let mut columns = cte_output_columns(&cte.body, &visible)?;
738            apply_positional_aliases(&mut columns, &cte.columns);
739            visible.ctes.insert(cte.name.to_ascii_lowercase(), columns);
740        }
741        bound.push(CTE {
742            name: cte.name.clone(),
743            columns: cte.columns.clone(),
744            recursive: cte.recursive,
745            materialization: cte.materialization,
746            search: cte.search.clone(),
747            cycle: cte
748                .cycle
749                .as_ref()
750                .map(|cycle| -> Result<crate::ast::CteCycleClause, SQLError> {
751                    Ok(crate::ast::CteCycleClause {
752                        columns: cycle.columns.clone(),
753                        mark_column: cycle.mark_column.clone(),
754                        mark_value: bind_rule_expr_with_scope(
755                            &cycle.mark_value,
756                            resolver,
757                            inherited,
758                            &visible,
759                        )?,
760                        mark_default: bind_rule_expr_with_scope(
761                            &cycle.mark_default,
762                            resolver,
763                            inherited,
764                            &visible,
765                        )?,
766                        path_column: cycle.path_column.clone(),
767                    })
768                })
769                .transpose()?,
770            body: match &cte.body {
771                crate::ast::CteBody::Query(query) => crate::ast::CteBody::Query(Box::new(
772                    bind_select_with_scope(query, resolver, inherited, &visible)?,
773                )),
774                // Command CTEs cannot reference a rule's event row. Their own DML row-image aliases belong to RETURNING and must retain their local identities.
775                body => body.clone(),
776            },
777        });
778        let mut columns = cte_output_columns(&cte.body, &visible)?;
779        apply_positional_aliases(&mut columns, &cte.columns);
780        if let Some(search) = &cte.search {
781            columns.push(search.sequence_column.clone());
782        }
783        if let Some(cycle) = &cte.cycle {
784            columns.push(cycle.mark_column.clone());
785            columns.push(cycle.path_column.clone());
786        }
787        visible.ctes.insert(cte.name.to_ascii_lowercase(), columns);
788    }
789    Ok(bound)
790}
791
792fn bind_frame_bound(
793    bound: &FrameBound,
794    resolver: &mut dyn VariableResolver,
795    scope: &RuleBindingScope,
796    context: &RuleBindingContext<'_>,
797) -> Result<FrameBound, SQLError> {
798    Ok(match bound {
799        FrameBound::Preceding(expr) => FrameBound::Preceding(Box::new(bind_rule_expr_with_scope(
800            expr, resolver, scope, context,
801        )?)),
802        FrameBound::Following(expr) => FrameBound::Following(Box::new(bind_rule_expr_with_scope(
803            expr, resolver, scope, context,
804        )?)),
805        _ => bound.clone(),
806    })
807}
808
809fn bind_projections(
810    projections: &[Projection],
811    resolver: &mut dyn VariableResolver,
812    scope: &RuleBindingScope,
813    context: &RuleBindingContext<'_>,
814) -> Result<Vec<Projection>, SQLError> {
815    let mut bound = Vec::with_capacity(projections.len());
816    for projection in projections {
817        if let Some(expressions) = expand_qualified_star(&projection.expr, resolver, scope)? {
818            bound.extend(expressions.into_iter().map(|expr| Projection {
819                expr,
820                alias: projection.alias.clone(),
821            }));
822        } else {
823            bound.push(Projection {
824                expr: bind_rule_expr_with_scope(&projection.expr, resolver, scope, context)?,
825                alias: projection.alias.clone(),
826            });
827        }
828    }
829    Ok(bound)
830}
831
832fn bind_expanding_exprs(
833    expressions: &[Expr],
834    resolver: &mut dyn VariableResolver,
835    scope: &RuleBindingScope,
836    context: &RuleBindingContext<'_>,
837) -> Result<Vec<Expr>, SQLError> {
838    let mut bound = Vec::with_capacity(expressions.len());
839    for expression in expressions {
840        if let Some(expressions) = expand_qualified_star(expression, resolver, scope)? {
841            bound.extend(expressions);
842        } else {
843            bound.push(bind_rule_expr_with_scope(
844                expression, resolver, scope, context,
845            )?);
846        }
847    }
848    Ok(bound)
849}
850
851fn expand_qualified_star(
852    expression: &Expr,
853    resolver: &mut dyn VariableResolver,
854    scope: &RuleBindingScope,
855) -> Result<Option<Vec<Expr>>, SQLError> {
856    let Expr::QualifiedStar(qualifier) = expression else {
857        return Ok(None);
858    };
859    if scope.qualifier_is_shadowed(qualifier) {
860        return Ok(None);
861    }
862    resolver.rewrite_qualified_star(qualifier)
863}
864
865fn bind_exprs(
866    expressions: &[Expr],
867    resolver: &mut dyn VariableResolver,
868    scope: &RuleBindingScope,
869    context: &RuleBindingContext<'_>,
870) -> Result<Vec<Expr>, SQLError> {
871    expressions
872        .iter()
873        .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context))
874        .collect()
875}
876
877fn bind_optional_expr(
878    expression: Option<&Expr>,
879    resolver: &mut dyn VariableResolver,
880    scope: &RuleBindingScope,
881    context: &RuleBindingContext<'_>,
882) -> Result<Option<Expr>, SQLError> {
883    expression
884        .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context))
885        .transpose()
886}
887
888fn bind_orders(
889    orders: &[OrderBy],
890    resolver: &mut dyn VariableResolver,
891    scope: &RuleBindingScope,
892    context: &RuleBindingContext<'_>,
893) -> Result<Vec<OrderBy>, SQLError> {
894    orders
895        .iter()
896        .map(|order| {
897            Ok(OrderBy {
898                expr: bind_rule_expr_with_scope(&order.expr, resolver, scope, context)?,
899                descending: order.descending,
900                nulls: order.nulls,
901            })
902        })
903        .collect()
904}
905
906pub fn invalid_rule_action_reference(qualifier: &str) -> SQLError {
907    SQLError::Routine {
908        sqlstate: "42P01".into(),
909        message: format!(
910            "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."
911        ),
912    }
913}