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::CompositeRow { items, binding } => Expr::CompositeRow {
252            items: bind_expanding_exprs(items, resolver, scope, context)?,
253            binding: binding.clone(),
254        },
255        Expr::Binary { op, lhs, rhs } => Expr::Binary {
256            op: *op,
257            lhs: Box::new(bind_rule_expr_with_scope(lhs, resolver, scope, context)?),
258            rhs: Box::new(bind_rule_expr_with_scope(rhs, resolver, scope, context)?),
259        },
260        Expr::UnaryMinus(inner) => Expr::UnaryMinus(Box::new(bind_rule_expr_with_scope(
261            inner, resolver, scope, context,
262        )?)),
263        Expr::Not(inner) => Expr::Not(Box::new(bind_rule_expr_with_scope(
264            inner, resolver, scope, context,
265        )?)),
266        Expr::And(items) => Expr::And(bind_exprs(items, resolver, scope, context)?),
267        Expr::Or(items) => Expr::Or(bind_exprs(items, resolver, scope, context)?),
268        Expr::IsNull { expr, negated } => Expr::IsNull {
269            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
270            negated: *negated,
271        },
272        Expr::Between { expr, low, high } => Expr::Between {
273            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
274            low: Box::new(bind_rule_expr_with_scope(low, resolver, scope, context)?),
275            high: Box::new(bind_rule_expr_with_scope(high, resolver, scope, context)?),
276        },
277        Expr::InList {
278            expr,
279            list,
280            negated,
281        } => Expr::InList {
282            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
283            list: bind_exprs(list, resolver, scope, context)?,
284            negated: *negated,
285        },
286        Expr::WindowCall { .. } => bind_rule_window_expression(expr, resolver, scope, context)?,
287        Expr::Case { .. } => bind_rule_case_expression(expr, resolver, scope, context)?,
288        Expr::Cast { expr, ty, implicit } => Expr::Cast {
289            implicit: *implicit,
290            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
291            ty: ty.clone(),
292        },
293        Expr::ScalarSubquery(body) => Expr::ScalarSubquery(Box::new(bind_select_with_scope(
294            body, resolver, scope, context,
295        )?)),
296        Expr::Exists { body, negated } => Expr::Exists {
297            body: Box::new(bind_select_with_scope(body, resolver, scope, context)?),
298            negated: *negated,
299        },
300        Expr::InSubquery {
301            expr,
302            body,
303            negated,
304        } => Expr::InSubquery {
305            expr: Box::new(bind_rule_expr_with_scope(expr, resolver, scope, context)?),
306            body: Box::new(bind_select_with_scope(body, resolver, scope, context)?),
307            negated: *negated,
308        },
309    })
310}
311
312fn bind_rule_function_expression(
313    expr: &Expr,
314    resolver: &mut dyn VariableResolver,
315    scope: &RuleBindingScope,
316    context: &RuleBindingContext<'_>,
317) -> Result<Expr, SQLError> {
318    let Expr::Func {
319        order_syntax,
320        name,
321        binding,
322        args,
323        distinct,
324        order_by,
325        filter,
326    } = expr
327    else {
328        unreachable!("function binder received a non-function expression")
329    };
330    Ok(Expr::Func {
331        order_syntax: *order_syntax,
332        name: name.clone(),
333        binding: binding.clone(),
334        args: bind_exprs(args, resolver, scope, context)?,
335        distinct: *distinct,
336        order_by: bind_orders(order_by, resolver, scope, context)?,
337        filter: filter
338            .as_deref()
339            .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context).map(Box::new))
340            .transpose()?,
341    })
342}
343
344fn bind_window_spec(
345    spec: &crate::ast::WindowSpec,
346    resolver: &mut dyn VariableResolver,
347    scope: &RuleBindingScope,
348    context: &RuleBindingContext<'_>,
349) -> Result<crate::ast::WindowSpec, SQLError> {
350    Ok(crate::ast::WindowSpec {
351        definition: spec.definition,
352        raw_definition: None,
353        reference: spec.reference.clone(),
354        partition_by: bind_exprs(&spec.partition_by, resolver, scope, context)?,
355        order_by: bind_orders(&spec.order_by, resolver, scope, context)?,
356        frame: spec
357            .frame
358            .as_ref()
359            .map(|frame| -> Result<crate::ast::WindowFrame, SQLError> {
360                Ok(crate::ast::WindowFrame {
361                    mode: frame.mode,
362                    start: bind_frame_bound(&frame.start, resolver, scope, context)?,
363                    end: bind_frame_bound(&frame.end, resolver, scope, context)?,
364                    between: frame.between,
365                    exclusion: frame.exclusion,
366                })
367            })
368            .transpose()?,
369    })
370}
371
372fn bind_rule_window_expression(
373    expr: &Expr,
374    resolver: &mut dyn VariableResolver,
375    scope: &RuleBindingScope,
376    context: &RuleBindingContext<'_>,
377) -> Result<Expr, SQLError> {
378    let Expr::WindowCall {
379        name,
380        args,
381        spec,
382        filter,
383        modifiers,
384    } = expr
385    else {
386        unreachable!("window binder received a non-window expression")
387    };
388    Ok(Expr::WindowCall {
389        modifiers: *modifiers,
390        name: name.clone(),
391        args: bind_exprs(args, resolver, scope, context)?,
392        filter: filter
393            .as_deref()
394            .map(|filter| bind_rule_expr_with_scope(filter, resolver, scope, context).map(Box::new))
395            .transpose()?,
396        spec: Box::new(bind_window_spec(spec, resolver, scope, context)?),
397    })
398}
399
400fn bind_rule_case_expression(
401    expr: &Expr,
402    resolver: &mut dyn VariableResolver,
403    scope: &RuleBindingScope,
404    context: &RuleBindingContext<'_>,
405) -> Result<Expr, SQLError> {
406    let Expr::Case {
407        base,
408        when,
409        else_branch,
410    } = expr
411    else {
412        unreachable!("CASE binder received a non-CASE expression")
413    };
414    Ok(Expr::Case {
415        base: base
416            .as_deref()
417            .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context).map(Box::new))
418            .transpose()?,
419        when: when
420            .iter()
421            .map(|(condition, result)| {
422                Ok((
423                    bind_rule_expr_with_scope(condition, resolver, scope, context)?,
424                    bind_rule_expr_with_scope(result, resolver, scope, context)?,
425                ))
426            })
427            .collect::<Result<Vec<_>, SQLError>>()?,
428        else_branch: else_branch
429            .as_deref()
430            .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context).map(Box::new))
431            .transpose()?,
432    })
433}
434
435fn bind_rule_select_scoped(
436    catalog: &dyn RuleSourceCatalog,
437    select: &SelectStmt,
438    resolver: &mut dyn VariableResolver,
439) -> Result<SelectStmt, SQLError> {
440    bind_select_with_scope(
441        select,
442        resolver,
443        &RuleBindingScope::default(),
444        &RuleBindingContext::with_catalog(catalog, false),
445    )
446}
447
448fn bind_rule_statement_body(
449    statement: &Statement,
450    resolver: &mut dyn VariableResolver,
451    inherited: &RuleBindingScope,
452    context: &RuleBindingContext<'_>,
453) -> Result<Statement, SQLError> {
454    Ok(match statement {
455        Statement::Select(select) => Statement::Select(Box::new(bind_select_with_scope(
456            select, resolver, inherited, context,
457        )?)),
458        Statement::Insert(insert) => {
459            Statement::Insert(bind_insert(insert, resolver, inherited, context)?)
460        }
461        Statement::Update(update) => {
462            Statement::Update(bind_update(update, resolver, inherited, context)?)
463        }
464        Statement::Delete(delete) => {
465            let mut output = delete.clone();
466            output.with = bind_ctes(&delete.with, resolver, inherited, context)?;
467            let context = context.with_ctes(&delete.with)?;
468            let mut target_scope = inherited.clone();
469            target_scope.insert_qualifier(&delete.target_qualifier);
470            output.using = delete
471                .using
472                .as_ref()
473                .map(|source| bind_from(source, resolver, &target_scope, &context))
474                .transpose()?;
475            let mut expression_scope = target_scope;
476            if let Some(source) = &delete.using {
477                collect_visible_scope(source, &context, &mut expression_scope)?;
478            }
479            output.r#where = bind_optional_expr(
480                delete.r#where.as_ref(),
481                resolver,
482                &expression_scope,
483                &context,
484            )?;
485            output.returning.clear();
486            Statement::Delete(output)
487        }
488        _ => return bind_statement(statement, resolver),
489    })
490}
491
492fn bind_assignment_targets(
493    targets: &crate::ast::AssignmentTargets,
494    resolver: &mut dyn VariableResolver,
495    scope: &RuleBindingScope,
496    context: &RuleBindingContext<'_>,
497) -> Result<crate::ast::AssignmentTargets, SQLError> {
498    let mut targets = targets.clone();
499    for expression in targets.expressions_mut() {
500        *expression = bind_rule_expr_with_scope(expression, resolver, scope, context)?;
501    }
502    Ok(targets)
503}
504
505fn bind_assignment_target(
506    target: &crate::ast::AssignmentTarget,
507    resolver: &mut dyn VariableResolver,
508    scope: &RuleBindingScope,
509    context: &RuleBindingContext<'_>,
510) -> Result<crate::ast::AssignmentTarget, SQLError> {
511    let mut target = target.clone();
512    for expression in target.expressions_mut() {
513        *expression = bind_rule_expr_with_scope(expression, resolver, scope, context)?;
514    }
515    Ok(target)
516}
517
518fn bind_update(
519    update: &UpdateStmt,
520    resolver: &mut dyn VariableResolver,
521    inherited: &RuleBindingScope,
522    context: &RuleBindingContext<'_>,
523) -> Result<UpdateStmt, SQLError> {
524    let mut output = update.clone();
525    output.with = bind_ctes(&update.with, resolver, inherited, context)?;
526    let context = context.with_ctes(&update.with)?;
527    let mut target_scope = inherited.clone();
528    target_scope.insert_qualifier(&update.target_qualifier);
529    output.from = update
530        .from
531        .as_ref()
532        .map(|source| bind_from(source, resolver, &target_scope, &context))
533        .transpose()?;
534    let mut expression_scope = target_scope;
535    if let Some(source) = &update.from {
536        collect_visible_scope(source, &context, &mut expression_scope)?;
537    }
538    output.assignments = update
539        .assignments
540        .iter()
541        .map(|(column, expr)| {
542            Ok((
543                bind_assignment_targets(column, resolver, &expression_scope, &context)?,
544                bind_rule_expr_with_scope(expr, resolver, &expression_scope, &context)?,
545            ))
546        })
547        .collect::<Result<Vec<_>, SQLError>>()?;
548    output.r#where = bind_optional_expr(
549        update.r#where.as_ref(),
550        resolver,
551        &expression_scope,
552        &context,
553    )?;
554    output.returning.clear();
555    Ok(output)
556}
557
558fn bind_select_with_scope(
559    select: &SelectStmt,
560    resolver: &mut dyn VariableResolver,
561    inherited: &RuleBindingScope,
562    context: &RuleBindingContext<'_>,
563) -> Result<SelectStmt, SQLError> {
564    let local_context = context.with_ctes(&select.with)?;
565    let mut scope = inherited.clone();
566    if let Some(source) = &select.from {
567        collect_visible_scope(source, &local_context, &mut scope)?;
568    }
569    Ok(SelectStmt {
570        windows: select
571            .windows
572            .iter()
573            .map(|window| {
574                Ok(crate::ast::WindowDefinition {
575                    name: window.name.clone(),
576                    inherited: window.inherited,
577                    spec: bind_window_spec(&window.spec, resolver, &scope, &local_context)?,
578                })
579            })
580            .collect::<Result<Vec<_>, SQLError>>()?,
581        projections: bind_projections(&select.projections, resolver, &scope, &local_context)?,
582        values: select
583            .values
584            .iter()
585            .map(|row| bind_expanding_exprs(row, resolver, &scope, &local_context))
586            .collect::<Result<Vec<_>, SQLError>>()?,
587        from: select
588            .from
589            .as_ref()
590            .map(|source| bind_from(source, resolver, inherited, &local_context))
591            .transpose()?,
592        r#where: bind_optional_expr(select.r#where.as_ref(), resolver, &scope, &local_context)?,
593        group_by: bind_exprs(&select.group_by, resolver, &scope, &local_context)?,
594        grouping_sets: select
595            .grouping_sets
596            .iter()
597            .map(|set| bind_exprs(set, resolver, &scope, &local_context))
598            .collect::<Result<Vec<_>, SQLError>>()?,
599        group_distinct: select.group_distinct,
600        having: bind_optional_expr(select.having.as_ref(), resolver, &scope, &local_context)?,
601        order_by: bind_orders(&select.order_by, resolver, &scope, &local_context)?,
602        limit: bind_optional_expr(select.limit.as_ref(), resolver, &scope, &local_context)?,
603        with_ties: select.with_ties,
604        offset: bind_optional_expr(select.offset.as_ref(), resolver, &scope, &local_context)?,
605        with: bind_ctes(&select.with, resolver, inherited, context)?,
606        set_op: select
607            .set_op
608            .as_ref()
609            .map(|operation| -> Result<Box<crate::ast::SetOp>, SQLError> {
610                Ok(Box::new(crate::ast::SetOp {
611                    kind: operation.kind,
612                    all: operation.all,
613                    left: operation
614                        .left
615                        .as_deref()
616                        .map(|left| {
617                            bind_select_with_scope(left, resolver, inherited, &local_context)
618                                .map(Box::new)
619                        })
620                        .transpose()?,
621                    right: bind_select_with_scope(
622                        &operation.right,
623                        resolver,
624                        inherited,
625                        &local_context,
626                    )?,
627                    combined_order_by: bind_orders(
628                        &operation.combined_order_by,
629                        resolver,
630                        inherited,
631                        &local_context,
632                    )?,
633                    combined_limit: bind_optional_expr(
634                        operation.combined_limit.as_ref(),
635                        resolver,
636                        inherited,
637                        &local_context,
638                    )?,
639                    combined_with_ties: operation.combined_with_ties,
640                    combined_offset: bind_optional_expr(
641                        operation.combined_offset.as_ref(),
642                        resolver,
643                        inherited,
644                        &local_context,
645                    )?,
646                }))
647            })
648            .transpose()?,
649        distinct: select.distinct,
650        distinct_on: bind_exprs(&select.distinct_on, resolver, &scope, &local_context)?,
651        locking: select.locking.clone(),
652    })
653}
654
655fn bind_from(
656    from: &FromClause,
657    resolver: &mut dyn VariableResolver,
658    inherited: &RuleBindingScope,
659    context: &RuleBindingContext<'_>,
660) -> Result<FromClause, SQLError> {
661    Ok(match from {
662        FromClause::Table { .. } => from.clone(),
663        FromClause::Join { .. } => bind_join_from(from, resolver, inherited, context)?,
664        FromClause::Values {
665            rows,
666            alias,
667            column_aliases,
668            internal_relation,
669            internal_column_types,
670        } => FromClause::Values {
671            rows: rows
672                .iter()
673                .map(|row| bind_exprs(row, resolver, inherited, context))
674                .collect::<Result<Vec<_>, SQLError>>()?,
675            alias: alias.clone(),
676            column_aliases: column_aliases.clone(),
677            internal_relation: *internal_relation,
678            internal_column_types: internal_column_types.clone(),
679        },
680        FromClause::Function {
681            name,
682            binding,
683            output_name,
684            relations,
685            args,
686            alias,
687            column_aliases,
688            ordinality,
689            column_types,
690        } => FromClause::Function {
691            name: name.clone(),
692            binding: binding.clone(),
693            output_name: output_name.clone(),
694            relations: relations.clone(),
695            args: bind_exprs(args, resolver, inherited, context)?,
696            alias: alias.clone(),
697            column_aliases: column_aliases.clone(),
698            ordinality: *ordinality,
699            column_types: column_types.clone(),
700        },
701        FromClause::FunctionGroup {
702            functions,
703            alias,
704            column_aliases,
705            ordinality,
706        } => FromClause::FunctionGroup {
707            functions: functions
708                .iter()
709                .map(|function| {
710                    Ok(crate::ast::TableFunction {
711                        name: function.name.clone(),
712                        binding: function.binding.clone(),
713                        output_name: function.output_name.clone(),
714                        relations: function.relations.clone(),
715                        args: bind_exprs(&function.args, resolver, inherited, context)?,
716                        column_aliases: function.column_aliases.clone(),
717                        column_types: function.column_types.clone(),
718                    })
719                })
720                .collect::<Result<Vec<_>, SQLError>>()?,
721            alias: alias.clone(),
722            column_aliases: column_aliases.clone(),
723            ordinality: *ordinality,
724        },
725        FromClause::Subquery {
726            body,
727            alias,
728            column_aliases,
729        } => FromClause::Subquery {
730            body: Box::new(bind_select_with_scope(body, resolver, inherited, context)?),
731            alias: alias.clone(),
732            column_aliases: column_aliases.clone(),
733        },
734    })
735}
736
737fn bind_join_from(
738    from: &FromClause,
739    resolver: &mut dyn VariableResolver,
740    inherited: &RuleBindingScope,
741    context: &RuleBindingContext<'_>,
742) -> Result<FromClause, SQLError> {
743    let FromClause::Join {
744        left,
745        right,
746        kind,
747        on,
748        using,
749        natural,
750        alias,
751        column_aliases,
752        lateral,
753    } = from
754    else {
755        unreachable!("join binder received a non-join source")
756    };
757    let bound_left = bind_from(left, resolver, inherited, context)?;
758    let mut right_scope = inherited.clone();
759    if *lateral {
760        collect_visible_scope(left, context, &mut right_scope)?;
761    }
762    let bound_right = bind_from(right, resolver, &right_scope, context)?;
763    let mut on_scope = inherited.clone();
764    collect_visible_scope(left, context, &mut on_scope)?;
765    collect_visible_scope(right, context, &mut on_scope)?;
766    Ok(FromClause::Join {
767        left: Box::new(bound_left),
768        right: Box::new(bound_right),
769        kind: *kind,
770        on: bind_optional_expr(on.as_ref(), resolver, &on_scope, context)?,
771        using: using.clone(),
772        natural: *natural,
773        alias: alias.clone(),
774        column_aliases: column_aliases.clone(),
775        lateral: *lateral,
776    })
777}
778
779fn bind_ctes(
780    ctes: &[CTE],
781    resolver: &mut dyn VariableResolver,
782    inherited: &RuleBindingScope,
783    context: &RuleBindingContext<'_>,
784) -> Result<Vec<CTE>, SQLError> {
785    let mut visible = context.clone();
786    let mut bound = Vec::with_capacity(ctes.len());
787    for cte in ctes {
788        if cte.recursive {
789            visible
790                .ctes
791                .entry(cte.name.to_ascii_lowercase())
792                .or_default();
793            let mut columns = cte_output_columns(&cte.body, &visible)?;
794            apply_positional_aliases(&mut columns, &cte.columns);
795            visible.ctes.insert(cte.name.to_ascii_lowercase(), columns);
796        }
797        bound.push(CTE {
798            name: cte.name.clone(),
799            columns: cte.columns.clone(),
800            recursive: cte.recursive,
801            materialization: cte.materialization,
802            search: cte.search.clone(),
803            cycle: cte
804                .cycle
805                .as_ref()
806                .map(|cycle| -> Result<crate::ast::CteCycleClause, SQLError> {
807                    Ok(crate::ast::CteCycleClause {
808                        columns: cycle.columns.clone(),
809                        mark_column: cycle.mark_column.clone(),
810                        mark_value: bind_rule_expr_with_scope(
811                            &cycle.mark_value,
812                            resolver,
813                            inherited,
814                            &visible,
815                        )?,
816                        mark_default: bind_rule_expr_with_scope(
817                            &cycle.mark_default,
818                            resolver,
819                            inherited,
820                            &visible,
821                        )?,
822                        path_column: cycle.path_column.clone(),
823                    })
824                })
825                .transpose()?,
826            body: match &cte.body {
827                crate::ast::CteBody::Query(query) => crate::ast::CteBody::Query(Box::new(
828                    bind_select_with_scope(query, resolver, inherited, &visible)?,
829                )),
830                // Command CTEs cannot reference a rule's event row. Their own DML row-image aliases belong to RETURNING and must retain their local identities.
831                body => body.clone(),
832            },
833        });
834        let mut columns = cte_output_columns(&cte.body, &visible)?;
835        apply_positional_aliases(&mut columns, &cte.columns);
836        if let Some(search) = &cte.search {
837            columns.push(search.sequence_column.clone());
838        }
839        if let Some(cycle) = &cte.cycle {
840            columns.push(cycle.mark_column.clone());
841            columns.push(cycle.path_column.clone());
842        }
843        visible.ctes.insert(cte.name.to_ascii_lowercase(), columns);
844    }
845    Ok(bound)
846}
847
848fn bind_frame_bound(
849    bound: &FrameBound,
850    resolver: &mut dyn VariableResolver,
851    scope: &RuleBindingScope,
852    context: &RuleBindingContext<'_>,
853) -> Result<FrameBound, SQLError> {
854    Ok(match bound {
855        FrameBound::Preceding(expr) => FrameBound::Preceding(Box::new(bind_rule_expr_with_scope(
856            expr, resolver, scope, context,
857        )?)),
858        FrameBound::Following(expr) => FrameBound::Following(Box::new(bind_rule_expr_with_scope(
859            expr, resolver, scope, context,
860        )?)),
861        _ => bound.clone(),
862    })
863}
864
865fn bind_projections(
866    projections: &[Projection],
867    resolver: &mut dyn VariableResolver,
868    scope: &RuleBindingScope,
869    context: &RuleBindingContext<'_>,
870) -> Result<Vec<Projection>, SQLError> {
871    let mut bound = Vec::with_capacity(projections.len());
872    for projection in projections {
873        if let Some(expressions) = expand_qualified_star(&projection.expr, resolver, scope)? {
874            bound.extend(expressions.into_iter().map(|expr| Projection {
875                expr,
876                alias: projection.alias.clone(),
877            }));
878        } else {
879            bound.push(Projection {
880                expr: bind_rule_expr_with_scope(&projection.expr, resolver, scope, context)?,
881                alias: projection.alias.clone(),
882            });
883        }
884    }
885    Ok(bound)
886}
887
888fn bind_expanding_exprs(
889    expressions: &[Expr],
890    resolver: &mut dyn VariableResolver,
891    scope: &RuleBindingScope,
892    context: &RuleBindingContext<'_>,
893) -> Result<Vec<Expr>, SQLError> {
894    let mut bound = Vec::with_capacity(expressions.len());
895    for expression in expressions {
896        if let Some(expressions) = expand_qualified_star(expression, resolver, scope)? {
897            bound.extend(expressions);
898        } else {
899            bound.push(bind_rule_expr_with_scope(
900                expression, resolver, scope, context,
901            )?);
902        }
903    }
904    Ok(bound)
905}
906
907fn expand_qualified_star(
908    expression: &Expr,
909    resolver: &mut dyn VariableResolver,
910    scope: &RuleBindingScope,
911) -> Result<Option<Vec<Expr>>, SQLError> {
912    let Expr::QualifiedStar(qualifier) = expression else {
913        return Ok(None);
914    };
915    if scope.qualifier_is_shadowed(qualifier) {
916        return Ok(None);
917    }
918    resolver.rewrite_qualified_star(qualifier)
919}
920
921fn bind_exprs(
922    expressions: &[Expr],
923    resolver: &mut dyn VariableResolver,
924    scope: &RuleBindingScope,
925    context: &RuleBindingContext<'_>,
926) -> Result<Vec<Expr>, SQLError> {
927    expressions
928        .iter()
929        .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context))
930        .collect()
931}
932
933fn bind_optional_expr(
934    expression: Option<&Expr>,
935    resolver: &mut dyn VariableResolver,
936    scope: &RuleBindingScope,
937    context: &RuleBindingContext<'_>,
938) -> Result<Option<Expr>, SQLError> {
939    expression
940        .map(|expr| bind_rule_expr_with_scope(expr, resolver, scope, context))
941        .transpose()
942}
943
944fn bind_orders(
945    orders: &[OrderBy],
946    resolver: &mut dyn VariableResolver,
947    scope: &RuleBindingScope,
948    context: &RuleBindingContext<'_>,
949) -> Result<Vec<OrderBy>, SQLError> {
950    orders
951        .iter()
952        .map(|order| {
953            Ok(OrderBy {
954                expr: bind_rule_expr_with_scope(&order.expr, resolver, scope, context)?,
955                descending: order.descending,
956                nulls: order.nulls,
957            })
958        })
959        .collect()
960}
961
962pub fn invalid_rule_action_reference(qualifier: &str) -> SQLError {
963    SQLError::Diagnostic {
964        sqlstate: "42P01".into(),
965        message: format!("invalid reference to FROM-clause entry for table \"{qualifier}\""),
966        detail: Some(format!(
967            "There is an entry for table \"{qualifier}\", but it cannot be referenced from this part of the query."
968        )),
969        hint: None,
970    }
971}