Skip to main content

uqa_sql/binding/
stored_columns.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Stored statement column identities, durable source shapes, and rewrite-rule binding.
8
9mod expressions;
10mod helpers;
11mod lifecycle;
12mod sources;
13mod statements;
14
15pub use lifecycle::{
16    bind_rule_action_column_dependencies, bind_rule_condition_column_dependencies,
17    bind_stored_statement_source_columns, remove_rule_source_column_aliases,
18    remove_stored_statement_source_column_aliases, rewrite_rule_column_references,
19    stored_statement_column_dependencies, stored_statement_references, StoredStatementColumns,
20};
21pub use statements::rewrite_stored_statement_column;
22
23/// Current names of a bound row relation, resolved when a binding pass needs them.
24pub trait StoredColumnCatalog {
25    fn stored_relation_column_names(&self, name: &str) -> Result<Option<Vec<String>>, SQLError>;
26}
27
28/// Catalog and namespace snapshots captured before resolving stored source columns.
29pub struct StoredSourceColumns {
30    pub catalog: crate::catalog::analysis::CatalogReadView,
31    pub resolution: crate::catalog::resolution::RelationNameResolution,
32}
33pub trait StoredSourceCatalog {
34    fn stored_source_columns(&self) -> StoredSourceColumns;
35}
36impl StoredSourceColumns {
37    pub fn bind_statement(&self, statement: &mut Statement) -> Result<bool, SQLError> {
38        bind_stored_statement_source_columns(statement, |name| {
39            Ok(
40                if let Some(table) = self.catalog.table_resolved(&self.resolution, name)? {
41                    Some(
42                        table
43                            .columns
44                            .iter()
45                            .map(|column| column.name.clone())
46                            .collect(),
47                    )
48                } else {
49                    self.catalog
50                        .foreign_table_resolved(&self.resolution, name)?
51                        .map(|table| {
52                            table
53                                .columns
54                                .iter()
55                                .map(|column| column.name.clone())
56                                .collect()
57                        })
58                },
59            )
60        })
61    }
62}
63
64#[derive(Clone, Copy)]
65pub struct StoredColumnBindingContext<'a> {
66    pub sources: &'a dyn StoredColumnCatalog,
67    pub merge: &'a dyn crate::routines::merge_columns::StoredMergeColumnCatalog,
68}
69
70use std::collections::{BTreeMap, BTreeSet};
71
72use crate::ast::{
73    DeleteStmt, Expr, FromClause, InsertStmt, OnConflictAction, Projection, SelectStmt, Statement,
74    UpdateStmt, CTE,
75};
76use crate::SQLError;
77
78use uqa_core::RelationIdentity;
79
80use crate::catalog::events::RuleColumnDependency;
81use helpers::{
82    action_returning_scope, apply_positional_aliases, is_default_values_insert, is_output_alias,
83    opaque_scope, preserve_table_column_name, same_identifier, select_output_names,
84    table_alias_count_error, unique_current_name,
85};
86
87fn cte_output_names(body: &crate::ast::CteBody) -> Vec<String> {
88    if let Some(query) = body.query() {
89        return select_output_names(query);
90    }
91    helpers::projection_output_names(body.returning().unwrap_or_default())
92}
93
94#[derive(Clone, Copy)]
95enum ColumnBindingMode<'a> {
96    Bind,
97    Rename {
98        relation: &'a RelationIdentity,
99        from: &'a str,
100        to: &'a str,
101    },
102    Drop {
103        dependencies: &'a BTreeSet<RuleColumnDependency>,
104    },
105}
106
107impl<'a> ColumnBindingMode<'a> {
108    fn column_names(self, relation: &RelationIdentity, current: &str) -> (String, String) {
109        match self {
110            Self::Rename {
111                relation: target,
112                from,
113                to,
114            } if relation == target && current == to => (from.to_string(), to.to_string()),
115            Self::Bind | Self::Rename { .. } | Self::Drop { .. } => {
116                (current.to_string(), current.to_string())
117            }
118        }
119    }
120
121    const fn is_rename(self) -> bool {
122        matches!(self, Self::Rename { .. })
123    }
124
125    const fn dropped_dependencies(self) -> Option<&'a BTreeSet<RuleColumnDependency>> {
126        match self {
127            Self::Drop { dependencies } => Some(dependencies),
128            Self::Bind | Self::Rename { .. } => None,
129        }
130    }
131}
132
133#[derive(Clone)]
134struct ScopeColumn {
135    /// Name present in the stored definition before this binding pass.
136    name: String,
137    /// Name present in the live catalog after this binding pass.
138    current_name: String,
139    /// Reference that identifies this output in the enclosing query scope.
140    reference: Expr,
141    dependencies: BTreeSet<RuleColumnDependency>,
142}
143
144#[derive(Clone, Default)]
145struct ColumnScope {
146    output: Vec<ScopeColumn>,
147    qualifiers: BTreeMap<String, Vec<ScopeColumn>>,
148}
149
150impl ColumnScope {
151    fn insert_qualifier(&mut self, qualifier: &str, columns: &[ScopeColumn]) {
152        self.qualifiers
153            .insert(qualifier.to_ascii_lowercase(), columns.to_vec());
154    }
155
156    fn qualified(&self, qualifier: &str) -> Option<&[ScopeColumn]> {
157        self.qualifiers
158            .get(&qualifier.to_ascii_lowercase())
159            .map(Vec::as_slice)
160    }
161
162    fn unqualified(&self, name: &str) -> Vec<&ScopeColumn> {
163        self.output
164            .iter()
165            .filter(|column| same_identifier(&column.name, name))
166            .collect()
167    }
168
169    fn combined(left: &Self, right: &Self) -> Self {
170        let mut output = left.output.clone();
171        output.extend(right.output.iter().cloned());
172        let mut qualifiers = left.qualifiers.clone();
173        qualifiers.extend(right.qualifiers.clone());
174        Self { output, qualifiers }
175    }
176}
177
178#[derive(Clone, Default)]
179struct ColumnBindingContext {
180    ctes: BTreeMap<String, Vec<String>>,
181}
182
183struct StoredColumnBinder<'a> {
184    catalog: StoredColumnBindingContext<'a>,
185    mode: ColumnBindingMode<'a>,
186    dependencies: BTreeSet<RuleColumnDependency>,
187    /// Column references, by qualifier and name, that no enclosing scope provides.
188    unresolved: Vec<(Option<String>, String)>,
189    alias_shape_changed: bool,
190}
191
192impl<'a> StoredColumnBinder<'a> {
193    fn new(catalog: StoredColumnBindingContext<'a>, mode: ColumnBindingMode<'a>) -> Self {
194        Self {
195            catalog,
196            mode,
197            dependencies: BTreeSet::new(),
198            unresolved: Vec::new(),
199            alias_shape_changed: false,
200        }
201    }
202
203    fn finish(self) -> BTreeSet<RuleColumnDependency> {
204        self.dependencies
205    }
206
207    const fn alias_shape_changed(&self) -> bool {
208        self.alias_shape_changed
209    }
210
211    fn remove_dropped_column_aliases(
212        &mut self,
213        column_aliases: &mut Vec<String>,
214        scope: &ColumnScope,
215    ) {
216        let Some(dependencies) = self.mode.dropped_dependencies() else {
217            return;
218        };
219        let positions = scope
220            .output
221            .iter()
222            .enumerate()
223            .filter_map(|(position, column)| {
224                (!column.dependencies.is_disjoint(dependencies)).then_some(position)
225            })
226            .collect::<Vec<_>>();
227        for position in positions.into_iter().rev() {
228            if position < column_aliases.len() {
229                column_aliases.remove(position);
230                self.alias_shape_changed = true;
231            }
232        }
233    }
234
235    fn table_scope(
236        &self,
237        name: &str,
238        qualifier: &str,
239        alias: Option<&str>,
240        column_aliases: &[String],
241        bound_columns: Option<&[String]>,
242        context: &ColumnBindingContext,
243    ) -> Result<ColumnScope, SQLError> {
244        if let Some(columns) = context.ctes.get(&name.to_ascii_lowercase()) {
245            if column_aliases.len() > columns.len() {
246                return Err(table_alias_count_error(
247                    alias.unwrap_or(qualifier),
248                    columns.len(),
249                    column_aliases.len(),
250                ));
251            }
252            let mut columns = columns.clone();
253            apply_positional_aliases(&mut columns, column_aliases);
254            return Ok(opaque_scope(&columns, Some(alias.unwrap_or(qualifier))));
255        }
256        let columns = match bound_columns {
257            Some(columns) => columns.to_vec(),
258            None => self
259                .catalog
260                .sources
261                .stored_relation_column_names(name)?
262                .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?,
263        };
264        if column_aliases.len() > columns.len() {
265            return Err(table_alias_count_error(
266                alias.unwrap_or(qualifier),
267                columns.len(),
268                column_aliases.len(),
269            ));
270        }
271        let relation = RelationIdentity::from_legacy_name(name).map_err(|error| {
272            SQLError::Internal(format!("decode bound rule relation `{name}`: {error}"))
273        })?;
274        let visible_qualifier = alias.unwrap_or(qualifier);
275        let output = columns
276            .into_iter()
277            .enumerate()
278            .map(|(position, column)| {
279                let (stored_physical, current_physical) =
280                    self.mode.column_names(&relation, &column);
281                let name = column_aliases
282                    .get(position)
283                    .cloned()
284                    .unwrap_or_else(|| stored_physical.clone());
285                let current_name = column_aliases
286                    .get(position)
287                    .cloned()
288                    .unwrap_or(current_physical);
289                ScopeColumn {
290                    reference: Expr::qualified_column(visible_qualifier, &current_name),
291                    dependencies: BTreeSet::from([RuleColumnDependency {
292                        relation: relation.clone(),
293                        column: stored_physical,
294                    }]),
295                    name,
296                    current_name,
297                }
298            })
299            .collect::<Vec<_>>();
300        let mut scope = ColumnScope {
301            output: output.clone(),
302            ..ColumnScope::default()
303        };
304        if let Some(alias) = alias {
305            scope.insert_qualifier(alias, &output);
306        } else {
307            scope.insert_qualifier(qualifier, &output);
308            scope.insert_qualifier(name, &output);
309            if let Some((_, local)) = name.rsplit_once('.') {
310                scope.insert_qualifier(local.trim_matches('"'), &output);
311            }
312        }
313        Ok(scope)
314    }
315
316    fn bind_statement(
317        &mut self,
318        statement: &mut Statement,
319        outer: &[ColumnScope],
320        context: &ColumnBindingContext,
321    ) -> Result<(), SQLError> {
322        match statement {
323            Statement::Select(select) => self.bind_select(select, outer, context),
324            Statement::Insert(insert) => self.bind_insert(insert, outer, context),
325            Statement::Update(update) => self.bind_update(update, outer, context),
326            Statement::Delete(delete) => self.bind_delete(delete, outer, context),
327            Statement::Merge(merge) => self.bind_merge(merge, outer, context),
328            Statement::Notify { .. } => Ok(()),
329            _ => Err(SQLError::Internal(
330                "validated rewrite-rule action has an unsupported statement kind".into(),
331            )),
332        }
333    }
334
335    fn bind_insert(
336        &mut self,
337        insert: &mut InsertStmt,
338        outer: &[ColumnScope],
339        context: &ColumnBindingContext,
340    ) -> Result<(), SQLError> {
341        let context = self.bind_ctes(&mut insert.with, outer, context)?;
342        for expression in insert.rows.iter_mut().flatten() {
343            self.bind_expr(expression, outer, &context)?;
344        }
345        if let Some(select) = insert.select_source.as_deref_mut() {
346            self.bind_select(select, outer, &context)?;
347        }
348        let target = self.table_scope(
349            &insert.table,
350            &insert.target_qualifier,
351            Some(&insert.target_qualifier),
352            &[],
353            None,
354            &ColumnBindingContext::default(),
355        )?;
356        if insert.columns.is_empty() && !is_default_values_insert(&insert.rows) {
357            let width = insert.rows.first().map(Vec::len).or_else(|| {
358                insert
359                    .select_source
360                    .as_deref()
361                    .map(select_output_names)
362                    .map(|columns| columns.len())
363            });
364            if let Some(width) = width {
365                insert.columns = target
366                    .output
367                    .iter()
368                    .take(width)
369                    .map(|column| column.current_name.clone().into())
370                    .collect();
371            }
372        }
373        for assignment in &mut insert.columns {
374            self.bind_target_name(&mut assignment.column, &target);
375            for expression in assignment.expressions_mut() {
376                self.bind_expr(expression, outer, &context)?;
377            }
378        }
379        let mut conflict_scope = target.clone();
380        conflict_scope.insert_qualifier("excluded", &target.output);
381        let mut target_scopes = vec![conflict_scope];
382        target_scopes.extend_from_slice(outer);
383        if let Some(conflict) = &mut insert.on_conflict {
384            self.bind_target_names(&mut conflict.conflict_columns, &target);
385            for expression in &mut conflict.expressions {
386                self.bind_expr(expression, &target_scopes, &context)?;
387            }
388            if let Some(predicate) = conflict.predicate.as_deref_mut() {
389                self.bind_expr(predicate, &target_scopes, &context)?;
390            }
391            if let OnConflictAction::Update {
392                assignments,
393                r#where,
394            } = &mut conflict.action
395            {
396                for (column, expression) in assignments {
397                    for target_column in column.targets_mut() {
398                        self.bind_target_name(&mut target_column.column, &target);
399                    }
400                    for expression in column.expressions_mut() {
401                        self.bind_expr(expression, &target_scopes, &context)?;
402                    }
403                    self.bind_expr(expression, &target_scopes, &context)?;
404                }
405                if let Some(expression) = r#where {
406                    self.bind_expr(expression, &target_scopes, &context)?;
407                }
408            }
409        }
410        let returning = action_returning_scope(
411            &target,
412            &target,
413            crate::ast::RuleEvent::Insert,
414            &insert.returning_aliases,
415        );
416        let mut returning_scopes = vec![returning.clone()];
417        returning_scopes.extend_from_slice(outer);
418        self.bind_projections(
419            &mut insert.returning,
420            Some(&returning),
421            &returning_scopes,
422            &context,
423        )
424    }
425
426    fn bind_update(
427        &mut self,
428        update: &mut UpdateStmt,
429        outer: &[ColumnScope],
430        context: &ColumnBindingContext,
431    ) -> Result<(), SQLError> {
432        let context = self.bind_ctes(&mut update.with, outer, context)?;
433        let target = self.table_scope(
434            &update.table,
435            &update.target_qualifier,
436            Some(&update.target_qualifier),
437            &[],
438            None,
439            &ColumnBindingContext::default(),
440        )?;
441        let (local, scopes) =
442            self.bind_dml_source(update.from.as_mut(), &target, outer, &context)?;
443        for (column, expression) in &mut update.assignments {
444            for target_column in column.targets_mut() {
445                self.bind_target_name(&mut target_column.column, &target);
446            }
447            for expression in column.expressions_mut() {
448                self.bind_expr(expression, &scopes, &context)?;
449            }
450            self.bind_expr(expression, &scopes, &context)?;
451        }
452        if let Some(expression) = &mut update.r#where {
453            self.bind_expr(expression, &scopes, &context)?;
454        }
455        let returning = action_returning_scope(
456            &local,
457            &target,
458            crate::ast::RuleEvent::Update,
459            &update.returning_aliases,
460        );
461        let mut returning_scopes = vec![returning.clone()];
462        returning_scopes.extend_from_slice(outer);
463        self.bind_projections(
464            &mut update.returning,
465            Some(&returning),
466            &returning_scopes,
467            &context,
468        )
469    }
470
471    fn bind_delete(
472        &mut self,
473        delete: &mut DeleteStmt,
474        outer: &[ColumnScope],
475        context: &ColumnBindingContext,
476    ) -> Result<(), SQLError> {
477        let context = self.bind_ctes(&mut delete.with, outer, context)?;
478        let target = self.table_scope(
479            &delete.table,
480            &delete.target_qualifier,
481            Some(&delete.target_qualifier),
482            &[],
483            None,
484            &ColumnBindingContext::default(),
485        )?;
486        let (local, scopes) =
487            self.bind_dml_source(delete.using.as_mut(), &target, outer, &context)?;
488        if let Some(expression) = &mut delete.r#where {
489            self.bind_expr(expression, &scopes, &context)?;
490        }
491        let returning = action_returning_scope(
492            &local,
493            &target,
494            crate::ast::RuleEvent::Delete,
495            &delete.returning_aliases,
496        );
497        let mut returning_scopes = vec![returning.clone()];
498        returning_scopes.extend_from_slice(outer);
499        self.bind_projections(
500            &mut delete.returning,
501            Some(&returning),
502            &returning_scopes,
503            &context,
504        )
505    }
506
507    fn bind_dml_source(
508        &mut self,
509        source: Option<&mut FromClause>,
510        target: &ColumnScope,
511        outer: &[ColumnScope],
512        context: &ColumnBindingContext,
513    ) -> Result<(ColumnScope, Vec<ColumnScope>), SQLError> {
514        let mut source_outer = vec![target.clone()];
515        source_outer.extend_from_slice(outer);
516        let source = source
517            .map(|source| self.bind_from(source, &source_outer, context))
518            .transpose()?;
519        let local = source.as_ref().map_or_else(
520            || target.clone(),
521            |source| ColumnScope::combined(target, source),
522        );
523        let mut scopes = vec![local.clone()];
524        scopes.extend_from_slice(outer);
525        Ok((local, scopes))
526    }
527
528    fn bind_target_names(&mut self, names: &mut [String], target: &ColumnScope) {
529        for name in names {
530            self.bind_target_name(name, target);
531        }
532    }
533
534    fn bind_target_name(&mut self, name: &mut String, target: &ColumnScope) {
535        let Some(column) = target
536            .output
537            .iter()
538            .find(|column| same_identifier(&column.name, name))
539        else {
540            return;
541        };
542        self.dependencies
543            .extend(column.dependencies.iter().cloned());
544        name.clone_from(&column.current_name);
545    }
546
547    fn bind_ctes(
548        &mut self,
549        ctes: &mut [CTE],
550        outer: &[ColumnScope],
551        context: &ColumnBindingContext,
552    ) -> Result<ColumnBindingContext, SQLError> {
553        let mut visible = context.clone();
554        let recursive_names = ctes
555            .iter()
556            .filter(|cte| cte.recursive)
557            .map(|cte| {
558                let columns = if cte.columns.is_empty() {
559                    cte_output_names(&cte.body)
560                } else {
561                    cte.columns.clone()
562                };
563                (cte.name.to_ascii_lowercase(), columns)
564            })
565            .collect::<Vec<_>>();
566        for (name, columns) in recursive_names {
567            visible.ctes.entry(name).or_insert(columns);
568        }
569        for cte in ctes {
570            let mut statement = cte.body.clone().into_statement();
571            self.bind_statement(&mut statement, outer, &visible)?;
572            cte.body = statement.try_into()?;
573            if let Some(cycle) = &mut cte.cycle {
574                self.bind_expr(&mut cycle.mark_value, outer, &visible)?;
575                self.bind_expr(&mut cycle.mark_default, outer, &visible)?;
576            }
577            let mut columns = cte_output_names(&cte.body);
578            apply_positional_aliases(&mut columns, &cte.columns);
579            if let Some(search) = &cte.search {
580                columns.push(search.sequence_column.clone());
581            }
582            if let Some(cycle) = &cte.cycle {
583                columns.push(cycle.mark_column.clone());
584                columns.push(cycle.path_column.clone());
585            }
586            visible.ctes.insert(cte.name.to_ascii_lowercase(), columns);
587        }
588        Ok(visible)
589    }
590
591    fn bind_select(
592        &mut self,
593        select: &mut SelectStmt,
594        outer: &[ColumnScope],
595        context: &ColumnBindingContext,
596    ) -> Result<(), SQLError> {
597        let context = self.bind_ctes(&mut select.with, outer, context)?;
598        let local = select
599            .from
600            .as_mut()
601            .map(|source| self.bind_from(source, outer, &context))
602            .transpose()?;
603        let mut scopes = local.iter().cloned().collect::<Vec<_>>();
604        scopes.extend_from_slice(outer);
605        self.bind_projections(&mut select.projections, local.as_ref(), &scopes, &context)?;
606        for expression in select.values.iter_mut().flatten() {
607            self.bind_expr(expression, &scopes, &context)?;
608        }
609        if let Some(expression) = &mut select.r#where {
610            self.bind_expr(expression, &scopes, &context)?;
611        }
612        for expression in &mut select.group_by {
613            self.bind_expr(expression, &scopes, &context)?;
614        }
615        for expression in select.grouping_sets.iter_mut().flatten() {
616            self.bind_expr(expression, &scopes, &context)?;
617        }
618        if let Some(expression) = &mut select.having {
619            self.bind_expr(expression, &scopes, &context)?;
620        }
621        let output_names = select_output_names(select);
622        for order in &mut select.order_by {
623            if !is_output_alias(&order.expr, &output_names) {
624                self.bind_expr(&mut order.expr, &scopes, &context)?;
625            }
626        }
627        if let Some(expression) = &mut select.limit {
628            self.bind_expr(expression, &scopes, &context)?;
629        }
630        if let Some(expression) = &mut select.offset {
631            self.bind_expr(expression, &scopes, &context)?;
632        }
633        for expression in &mut select.distinct_on {
634            self.bind_expr(expression, &scopes, &context)?;
635        }
636        if let Some(set) = &mut select.set_op {
637            if let Some(left) = &mut set.left {
638                self.bind_select(left, outer, &context)?;
639            }
640            self.bind_select(&mut set.right, outer, &context)?;
641            let set_output = set
642                .left
643                .as_deref()
644                .map_or_else(|| output_names.clone(), select_output_names);
645            for order in &mut set.combined_order_by {
646                if !is_output_alias(&order.expr, &set_output) {
647                    self.bind_expr(&mut order.expr, outer, &context)?;
648                }
649            }
650            if let Some(expression) = &mut set.combined_limit {
651                self.bind_expr(expression, outer, &context)?;
652            }
653            if let Some(expression) = &mut set.combined_offset {
654                self.bind_expr(expression, outer, &context)?;
655            }
656        }
657        Ok(())
658    }
659
660    fn bind_from(
661        &mut self,
662        source: &mut FromClause,
663        outer: &[ColumnScope],
664        context: &ColumnBindingContext,
665    ) -> Result<ColumnScope, SQLError> {
666        match source {
667            source @ FromClause::Table { .. } => self.bind_table_source(source, context),
668            source @ FromClause::Join { .. } => self.bind_join(source, outer, context),
669            FromClause::Values {
670                rows,
671                alias,
672                column_aliases,
673                ..
674            } => {
675                for expression in rows.iter_mut().flatten() {
676                    self.bind_expr(expression, outer, context)?;
677                }
678                let mut columns = if column_aliases.is_empty() {
679                    (1..=rows.first().map_or(0, Vec::len))
680                        .map(|position| format!("column{position}"))
681                        .collect::<Vec<_>>()
682                } else {
683                    column_aliases.clone()
684                };
685                apply_positional_aliases(&mut columns, column_aliases);
686                Ok(opaque_scope(&columns, alias.as_deref()))
687            }
688            FromClause::Function {
689                output_name,
690                args,
691                alias,
692                column_aliases,
693                ordinality,
694                ..
695            } => {
696                for expression in args {
697                    self.bind_expr(expression, outer, context)?;
698                }
699                let mut columns = vec![output_name.clone()];
700                apply_positional_aliases(&mut columns, column_aliases);
701                if *ordinality {
702                    columns.push("ordinality".into());
703                }
704                Ok(opaque_scope(
705                    &columns,
706                    Some(alias.as_deref().unwrap_or(output_name)),
707                ))
708            }
709            FromClause::FunctionGroup {
710                functions,
711                alias,
712                column_aliases,
713                ordinality,
714            } => {
715                for function in functions.iter_mut() {
716                    for expression in &mut function.args {
717                        self.bind_expr(expression, outer, context)?;
718                    }
719                }
720                let mut columns = functions
721                    .iter()
722                    .flat_map(|function| {
723                        if function.column_aliases.is_empty() {
724                            vec![function.output_name.clone()]
725                        } else {
726                            function.column_aliases.clone()
727                        }
728                    })
729                    .collect::<Vec<_>>();
730                apply_positional_aliases(&mut columns, column_aliases);
731                if *ordinality {
732                    columns.push("ordinality".into());
733                }
734                Ok(opaque_scope(&columns, alias.as_deref()))
735            }
736            FromClause::Subquery {
737                body,
738                alias,
739                column_aliases,
740            } => {
741                self.bind_select(body, outer, context)?;
742                let mut columns = select_output_names(body);
743                apply_positional_aliases(&mut columns, column_aliases);
744                Ok(opaque_scope(&columns, alias.as_deref()))
745            }
746        }
747    }
748
749    fn bind_join(
750        &mut self,
751        source: &mut FromClause,
752        outer: &[ColumnScope],
753        context: &ColumnBindingContext,
754    ) -> Result<ColumnScope, SQLError> {
755        let FromClause::Join {
756            left,
757            right,
758            on,
759            using,
760            natural,
761            alias,
762            column_aliases,
763            lateral,
764            ..
765        } = source
766        else {
767            unreachable!("join column binding requires a join source")
768        };
769        if let ColumnBindingMode::Rename { relation, from, to } = self.mode {
770            if *natural
771                || using.as_ref().is_some_and(|using| {
772                    using
773                        .columns
774                        .iter()
775                        .any(|column| same_identifier(column, from))
776                })
777            {
778                preserve_table_column_name(self.catalog, left, relation, from, to)?;
779                preserve_table_column_name(self.catalog, right, relation, from, to)?;
780            }
781        }
782        let left_scope = self.bind_from(left, outer, context)?;
783        let mut right_outer = Vec::new();
784        if *lateral {
785            right_outer.push(left_scope.clone());
786        }
787        right_outer.extend_from_slice(outer);
788        let right_scope = self.bind_from(right, &right_outer, context)?;
789        let input_scope = ColumnScope::combined(&left_scope, &right_scope);
790        let mut on_scopes = vec![input_scope];
791        on_scopes.extend_from_slice(outer);
792        if let Some(expression) = on {
793            self.bind_expr(expression, &on_scopes, context)?;
794        }
795        if *natural {
796            let columns = left_scope
797                .output
798                .iter()
799                .filter(|left| {
800                    right_scope
801                        .output
802                        .iter()
803                        .any(|right| same_identifier(&left.name, &right.name))
804                })
805                .map(|column| column.name.clone())
806                .collect();
807            *using = Some(crate::ast::JoinUsing {
808                columns,
809                alias: None,
810            });
811            *natural = false;
812        }
813        let scope = self.join_scope(
814            left_scope,
815            right_scope,
816            using.as_mut(),
817            alias.as_deref(),
818            column_aliases,
819        )?;
820        self.remove_dropped_column_aliases(column_aliases, &scope);
821        Ok(scope)
822    }
823
824    fn join_scope(
825        &mut self,
826        left: ColumnScope,
827        right: ColumnScope,
828        using: Option<&mut crate::ast::JoinUsing>,
829        alias: Option<&str>,
830        column_aliases: &[String],
831    ) -> Result<ColumnScope, SQLError> {
832        let using_alias = using.as_ref().and_then(|using| using.alias.clone());
833        let mut merged = Vec::new();
834        if let Some(using) = using {
835            for name in &mut using.columns {
836                let left_matches = left.unqualified(name);
837                let right_matches = right.unqualified(name);
838                for column in left_matches.iter().chain(&right_matches) {
839                    self.dependencies
840                        .extend(column.dependencies.iter().cloned());
841                }
842                let left_current = unique_current_name(&left_matches);
843                let right_current = unique_current_name(&right_matches);
844                if left_current.is_some()
845                    && right_current.is_some()
846                    && left_current != right_current
847                {
848                    return Err(SQLError::Internal(format!(
849                        "rule JOIN USING column \"{name}\" resolved to different visible names after column rebinding"
850                    )));
851                }
852                let current_name = left_current
853                    .or(right_current)
854                    .unwrap_or_else(|| name.clone());
855                let mut dependencies = BTreeSet::new();
856                for column in left_matches.iter().chain(&right_matches) {
857                    dependencies.extend(column.dependencies.iter().cloned());
858                }
859                let stored_name = name.clone();
860                name.clone_from(&current_name);
861                merged.push(ScopeColumn {
862                    name: stored_name,
863                    current_name: current_name.clone(),
864                    reference: Expr::Column(current_name),
865                    dependencies,
866                });
867            }
868        }
869        let merged_names = merged
870            .iter()
871            .map(|column| column.name.clone())
872            .collect::<Vec<_>>();
873        let mut output = merged;
874        output.extend(
875            left.output
876                .iter()
877                .chain(&right.output)
878                .filter(|column| {
879                    !merged_names
880                        .iter()
881                        .any(|name| same_identifier(name, &column.name))
882                })
883                .cloned(),
884        );
885        if let Some(alias) = alias {
886            for (position, column) in output.iter_mut().enumerate() {
887                if let Some(name) = column_aliases.get(position) {
888                    column.name.clone_from(name);
889                    column.current_name.clone_from(name);
890                }
891                column.reference = Expr::qualified_column(alias, &column.current_name);
892            }
893            let mut scope = ColumnScope {
894                output: output.clone(),
895                ..ColumnScope::default()
896            };
897            scope.insert_qualifier(alias, &output);
898            return Ok(scope);
899        }
900        let mut qualifiers = left.qualifiers;
901        qualifiers.extend(right.qualifiers);
902        let mut scope = ColumnScope { output, qualifiers };
903        if let Some(using_alias) = using_alias.as_deref() {
904            let merged = scope
905                .output
906                .iter()
907                .filter(|column| {
908                    merged_names
909                        .iter()
910                        .any(|name| same_identifier(name, &column.name))
911                })
912                .cloned()
913                .collect::<Vec<_>>();
914            scope.insert_qualifier(using_alias, &merged);
915        }
916        Ok(scope)
917    }
918
919    fn bind_projections(
920        &mut self,
921        projections: &mut Vec<Projection>,
922        local: Option<&ColumnScope>,
923        scopes: &[ColumnScope],
924        context: &ColumnBindingContext,
925    ) -> Result<(), SQLError> {
926        let mut bound = Vec::with_capacity(projections.len());
927        for mut projection in projections.drain(..) {
928            let expanded = match &projection.expr {
929                Expr::Star => local.map(|scope| scope.output.as_slice()),
930                Expr::QualifiedStar(qualifier) => {
931                    scopes.iter().find_map(|scope| scope.qualified(qualifier))
932                }
933                _ => None,
934            };
935            if let Some(columns) = expanded {
936                for column in columns {
937                    self.dependencies
938                        .extend(column.dependencies.iter().cloned());
939                    bound.push(Projection {
940                        expr: column.reference.clone(),
941                        alias: Some(column.name.clone()),
942                    });
943                }
944                continue;
945            }
946            let implicit_name = match &projection.expr {
947                Expr::Column(name) | Expr::QualifiedColumn { column: name, .. } => {
948                    Some(name.clone())
949                }
950                _ => None,
951            };
952            self.bind_expr(&mut projection.expr, scopes, context)?;
953            if projection.alias.is_none() && self.mode.is_rename() {
954                let current_name = match &projection.expr {
955                    Expr::Column(name) | Expr::QualifiedColumn { column: name, .. } => Some(name),
956                    _ => None,
957                };
958                if implicit_name
959                    .as_ref()
960                    .zip(current_name)
961                    .is_some_and(|(stored, current)| !same_identifier(stored, current))
962                {
963                    projection.alias = implicit_name;
964                }
965            }
966            bound.push(projection);
967        }
968        *projections = bound;
969        Ok(())
970    }
971}