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,
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    alias_shape_changed: bool,
188}
189
190impl<'a> StoredColumnBinder<'a> {
191    fn new(catalog: StoredColumnBindingContext<'a>, mode: ColumnBindingMode<'a>) -> Self {
192        Self {
193            catalog,
194            mode,
195            dependencies: BTreeSet::new(),
196            alias_shape_changed: false,
197        }
198    }
199
200    fn finish(self) -> BTreeSet<RuleColumnDependency> {
201        self.dependencies
202    }
203
204    const fn alias_shape_changed(&self) -> bool {
205        self.alias_shape_changed
206    }
207
208    fn remove_dropped_column_aliases(
209        &mut self,
210        column_aliases: &mut Vec<String>,
211        scope: &ColumnScope,
212    ) {
213        let Some(dependencies) = self.mode.dropped_dependencies() else {
214            return;
215        };
216        let positions = scope
217            .output
218            .iter()
219            .enumerate()
220            .filter_map(|(position, column)| {
221                (!column.dependencies.is_disjoint(dependencies)).then_some(position)
222            })
223            .collect::<Vec<_>>();
224        for position in positions.into_iter().rev() {
225            if position < column_aliases.len() {
226                column_aliases.remove(position);
227                self.alias_shape_changed = true;
228            }
229        }
230    }
231
232    fn table_scope(
233        &self,
234        name: &str,
235        qualifier: &str,
236        alias: Option<&str>,
237        column_aliases: &[String],
238        bound_columns: Option<&[String]>,
239        context: &ColumnBindingContext,
240    ) -> Result<ColumnScope, SQLError> {
241        if let Some(columns) = context.ctes.get(&name.to_ascii_lowercase()) {
242            if column_aliases.len() > columns.len() {
243                return Err(table_alias_count_error(
244                    alias.unwrap_or(qualifier),
245                    columns.len(),
246                    column_aliases.len(),
247                ));
248            }
249            let mut columns = columns.clone();
250            apply_positional_aliases(&mut columns, column_aliases);
251            return Ok(opaque_scope(&columns, Some(alias.unwrap_or(qualifier))));
252        }
253        let columns = match bound_columns {
254            Some(columns) => columns.to_vec(),
255            None => self
256                .catalog
257                .sources
258                .stored_relation_column_names(name)?
259                .ok_or_else(|| SQLError::UnknownTable(name.to_string()))?,
260        };
261        if column_aliases.len() > columns.len() {
262            return Err(table_alias_count_error(
263                alias.unwrap_or(qualifier),
264                columns.len(),
265                column_aliases.len(),
266            ));
267        }
268        let relation = RelationIdentity::from_legacy_name(name).map_err(|error| {
269            SQLError::Internal(format!("decode bound rule relation `{name}`: {error}"))
270        })?;
271        let visible_qualifier = alias.unwrap_or(qualifier);
272        let output = columns
273            .into_iter()
274            .enumerate()
275            .map(|(position, column)| {
276                let (stored_physical, current_physical) =
277                    self.mode.column_names(&relation, &column);
278                let name = column_aliases
279                    .get(position)
280                    .cloned()
281                    .unwrap_or_else(|| stored_physical.clone());
282                let current_name = column_aliases
283                    .get(position)
284                    .cloned()
285                    .unwrap_or(current_physical);
286                ScopeColumn {
287                    reference: Expr::qualified_column(visible_qualifier, &current_name),
288                    dependencies: BTreeSet::from([RuleColumnDependency {
289                        relation: relation.clone(),
290                        column: stored_physical,
291                    }]),
292                    name,
293                    current_name,
294                }
295            })
296            .collect::<Vec<_>>();
297        let mut scope = ColumnScope {
298            output: output.clone(),
299            ..ColumnScope::default()
300        };
301        if let Some(alias) = alias {
302            scope.insert_qualifier(alias, &output);
303        } else {
304            scope.insert_qualifier(qualifier, &output);
305            scope.insert_qualifier(name, &output);
306            if let Some((_, local)) = name.rsplit_once('.') {
307                scope.insert_qualifier(local.trim_matches('"'), &output);
308            }
309        }
310        Ok(scope)
311    }
312
313    fn bind_statement(
314        &mut self,
315        statement: &mut Statement,
316        outer: &[ColumnScope],
317        context: &ColumnBindingContext,
318    ) -> Result<(), SQLError> {
319        match statement {
320            Statement::Select(select) => self.bind_select(select, outer, context),
321            Statement::Insert(insert) => self.bind_insert(insert, outer, context),
322            Statement::Update(update) => self.bind_update(update, outer, context),
323            Statement::Delete(delete) => self.bind_delete(delete, outer, context),
324            Statement::Merge(merge) => self.bind_merge(merge, outer, context),
325            Statement::Notify { .. } => Ok(()),
326            _ => Err(SQLError::Internal(
327                "validated rewrite-rule action has an unsupported statement kind".into(),
328            )),
329        }
330    }
331
332    fn bind_insert(
333        &mut self,
334        insert: &mut InsertStmt,
335        outer: &[ColumnScope],
336        context: &ColumnBindingContext,
337    ) -> Result<(), SQLError> {
338        let context = self.bind_ctes(&mut insert.with, outer, context)?;
339        for expression in insert.rows.iter_mut().flatten() {
340            self.bind_expr(expression, outer, &context)?;
341        }
342        if let Some(select) = insert.select_source.as_deref_mut() {
343            self.bind_select(select, outer, &context)?;
344        }
345        let target = self.table_scope(
346            &insert.table,
347            &insert.target_qualifier,
348            Some(&insert.target_qualifier),
349            &[],
350            None,
351            &ColumnBindingContext::default(),
352        )?;
353        if insert.columns.is_empty() && !is_default_values_insert(&insert.rows) {
354            let width = insert.rows.first().map(Vec::len).or_else(|| {
355                insert
356                    .select_source
357                    .as_deref()
358                    .map(select_output_names)
359                    .map(|columns| columns.len())
360            });
361            if let Some(width) = width {
362                insert.columns = target
363                    .output
364                    .iter()
365                    .take(width)
366                    .map(|column| column.current_name.clone())
367                    .collect();
368            }
369        }
370        self.bind_target_names(&mut insert.columns, &target);
371        let mut conflict_scope = target.clone();
372        conflict_scope.insert_qualifier("excluded", &target.output);
373        let mut target_scopes = vec![conflict_scope];
374        target_scopes.extend_from_slice(outer);
375        if let Some(conflict) = &mut insert.on_conflict {
376            self.bind_target_names(&mut conflict.conflict_columns, &target);
377            for expression in &mut conflict.expressions {
378                self.bind_expr(expression, &target_scopes, &context)?;
379            }
380            if let Some(predicate) = conflict.predicate.as_deref_mut() {
381                self.bind_expr(predicate, &target_scopes, &context)?;
382            }
383            if let OnConflictAction::Update {
384                assignments,
385                r#where,
386            } = &mut conflict.action
387            {
388                for (column, expression) in assignments {
389                    self.bind_target_name(column, &target);
390                    self.bind_expr(expression, &target_scopes, &context)?;
391                }
392                if let Some(expression) = r#where {
393                    self.bind_expr(expression, &target_scopes, &context)?;
394                }
395            }
396        }
397        let returning = action_returning_scope(
398            &target,
399            &target,
400            crate::ast::RuleEvent::Insert,
401            &insert.returning_aliases,
402        );
403        let mut returning_scopes = vec![returning.clone()];
404        returning_scopes.extend_from_slice(outer);
405        self.bind_projections(
406            &mut insert.returning,
407            Some(&returning),
408            &returning_scopes,
409            &context,
410        )
411    }
412
413    fn bind_update(
414        &mut self,
415        update: &mut UpdateStmt,
416        outer: &[ColumnScope],
417        context: &ColumnBindingContext,
418    ) -> Result<(), SQLError> {
419        let context = self.bind_ctes(&mut update.with, outer, context)?;
420        let target = self.table_scope(
421            &update.table,
422            &update.target_qualifier,
423            Some(&update.target_qualifier),
424            &[],
425            None,
426            &ColumnBindingContext::default(),
427        )?;
428        let (local, scopes) =
429            self.bind_dml_source(update.from.as_mut(), &target, outer, &context)?;
430        for (column, expression) in &mut update.assignments {
431            self.bind_target_name(column, &target);
432            self.bind_expr(expression, &scopes, &context)?;
433        }
434        if let Some(expression) = &mut update.r#where {
435            self.bind_expr(expression, &scopes, &context)?;
436        }
437        let returning = action_returning_scope(
438            &local,
439            &target,
440            crate::ast::RuleEvent::Update,
441            &update.returning_aliases,
442        );
443        let mut returning_scopes = vec![returning.clone()];
444        returning_scopes.extend_from_slice(outer);
445        self.bind_projections(
446            &mut update.returning,
447            Some(&returning),
448            &returning_scopes,
449            &context,
450        )
451    }
452
453    fn bind_delete(
454        &mut self,
455        delete: &mut DeleteStmt,
456        outer: &[ColumnScope],
457        context: &ColumnBindingContext,
458    ) -> Result<(), SQLError> {
459        let context = self.bind_ctes(&mut delete.with, outer, context)?;
460        let target = self.table_scope(
461            &delete.table,
462            &delete.target_qualifier,
463            Some(&delete.target_qualifier),
464            &[],
465            None,
466            &ColumnBindingContext::default(),
467        )?;
468        let (local, scopes) =
469            self.bind_dml_source(delete.using.as_mut(), &target, outer, &context)?;
470        if let Some(expression) = &mut delete.r#where {
471            self.bind_expr(expression, &scopes, &context)?;
472        }
473        let returning = action_returning_scope(
474            &local,
475            &target,
476            crate::ast::RuleEvent::Delete,
477            &delete.returning_aliases,
478        );
479        let mut returning_scopes = vec![returning.clone()];
480        returning_scopes.extend_from_slice(outer);
481        self.bind_projections(
482            &mut delete.returning,
483            Some(&returning),
484            &returning_scopes,
485            &context,
486        )
487    }
488
489    fn bind_dml_source(
490        &mut self,
491        source: Option<&mut FromClause>,
492        target: &ColumnScope,
493        outer: &[ColumnScope],
494        context: &ColumnBindingContext,
495    ) -> Result<(ColumnScope, Vec<ColumnScope>), SQLError> {
496        let mut source_outer = vec![target.clone()];
497        source_outer.extend_from_slice(outer);
498        let source = source
499            .map(|source| self.bind_from(source, &source_outer, context))
500            .transpose()?;
501        let local = source.as_ref().map_or_else(
502            || target.clone(),
503            |source| ColumnScope::combined(target, source),
504        );
505        let mut scopes = vec![local.clone()];
506        scopes.extend_from_slice(outer);
507        Ok((local, scopes))
508    }
509
510    fn bind_target_names(&mut self, names: &mut [String], target: &ColumnScope) {
511        for name in names {
512            self.bind_target_name(name, target);
513        }
514    }
515
516    fn bind_target_name(&mut self, name: &mut String, target: &ColumnScope) {
517        let Some(column) = target
518            .output
519            .iter()
520            .find(|column| same_identifier(&column.name, name))
521        else {
522            return;
523        };
524        self.dependencies
525            .extend(column.dependencies.iter().cloned());
526        name.clone_from(&column.current_name);
527    }
528
529    fn bind_ctes(
530        &mut self,
531        ctes: &mut [CTE],
532        outer: &[ColumnScope],
533        context: &ColumnBindingContext,
534    ) -> Result<ColumnBindingContext, SQLError> {
535        let mut visible = context.clone();
536        let recursive_names = ctes
537            .iter()
538            .filter(|cte| cte.recursive)
539            .map(|cte| {
540                let columns = if cte.columns.is_empty() {
541                    cte_output_names(&cte.body)
542                } else {
543                    cte.columns.clone()
544                };
545                (cte.name.to_ascii_lowercase(), columns)
546            })
547            .collect::<Vec<_>>();
548        for (name, columns) in recursive_names {
549            visible.ctes.entry(name).or_insert(columns);
550        }
551        for cte in ctes {
552            let mut statement = cte.body.clone().into_statement();
553            self.bind_statement(&mut statement, outer, &visible)?;
554            cte.body = statement.try_into()?;
555            if let Some(cycle) = &mut cte.cycle {
556                self.bind_expr(&mut cycle.mark_value, outer, &visible)?;
557                self.bind_expr(&mut cycle.mark_default, outer, &visible)?;
558            }
559            let mut columns = cte_output_names(&cte.body);
560            apply_positional_aliases(&mut columns, &cte.columns);
561            if let Some(search) = &cte.search {
562                columns.push(search.sequence_column.clone());
563            }
564            if let Some(cycle) = &cte.cycle {
565                columns.push(cycle.mark_column.clone());
566                columns.push(cycle.path_column.clone());
567            }
568            visible.ctes.insert(cte.name.to_ascii_lowercase(), columns);
569        }
570        Ok(visible)
571    }
572
573    fn bind_select(
574        &mut self,
575        select: &mut SelectStmt,
576        outer: &[ColumnScope],
577        context: &ColumnBindingContext,
578    ) -> Result<(), SQLError> {
579        let context = self.bind_ctes(&mut select.with, outer, context)?;
580        let local = select
581            .from
582            .as_mut()
583            .map(|source| self.bind_from(source, outer, &context))
584            .transpose()?;
585        let mut scopes = local.iter().cloned().collect::<Vec<_>>();
586        scopes.extend_from_slice(outer);
587        self.bind_projections(&mut select.projections, local.as_ref(), &scopes, &context)?;
588        for expression in select.values.iter_mut().flatten() {
589            self.bind_expr(expression, &scopes, &context)?;
590        }
591        if let Some(expression) = &mut select.r#where {
592            self.bind_expr(expression, &scopes, &context)?;
593        }
594        for expression in &mut select.group_by {
595            self.bind_expr(expression, &scopes, &context)?;
596        }
597        for expression in select.grouping_sets.iter_mut().flatten() {
598            self.bind_expr(expression, &scopes, &context)?;
599        }
600        if let Some(expression) = &mut select.having {
601            self.bind_expr(expression, &scopes, &context)?;
602        }
603        let output_names = select_output_names(select);
604        for order in &mut select.order_by {
605            if !is_output_alias(&order.expr, &output_names) {
606                self.bind_expr(&mut order.expr, &scopes, &context)?;
607            }
608        }
609        if let Some(expression) = &mut select.limit {
610            self.bind_expr(expression, &scopes, &context)?;
611        }
612        if let Some(expression) = &mut select.offset {
613            self.bind_expr(expression, &scopes, &context)?;
614        }
615        for expression in &mut select.distinct_on {
616            self.bind_expr(expression, &scopes, &context)?;
617        }
618        if let Some(set) = &mut select.set_op {
619            if let Some(left) = &mut set.left {
620                self.bind_select(left, outer, &context)?;
621            }
622            self.bind_select(&mut set.right, outer, &context)?;
623            let set_output = set
624                .left
625                .as_deref()
626                .map_or_else(|| output_names.clone(), select_output_names);
627            for order in &mut set.combined_order_by {
628                if !is_output_alias(&order.expr, &set_output) {
629                    self.bind_expr(&mut order.expr, outer, &context)?;
630                }
631            }
632            if let Some(expression) = &mut set.combined_limit {
633                self.bind_expr(expression, outer, &context)?;
634            }
635            if let Some(expression) = &mut set.combined_offset {
636                self.bind_expr(expression, outer, &context)?;
637            }
638        }
639        Ok(())
640    }
641
642    fn bind_from(
643        &mut self,
644        source: &mut FromClause,
645        outer: &[ColumnScope],
646        context: &ColumnBindingContext,
647    ) -> Result<ColumnScope, SQLError> {
648        match source {
649            source @ FromClause::Table { .. } => self.bind_table_source(source, context),
650            source @ FromClause::Join { .. } => self.bind_join(source, outer, context),
651            FromClause::Values {
652                rows,
653                alias,
654                column_aliases,
655                ..
656            } => {
657                for expression in rows.iter_mut().flatten() {
658                    self.bind_expr(expression, outer, context)?;
659                }
660                let mut columns = if column_aliases.is_empty() {
661                    (1..=rows.first().map_or(0, Vec::len))
662                        .map(|position| format!("column{position}"))
663                        .collect::<Vec<_>>()
664                } else {
665                    column_aliases.clone()
666                };
667                apply_positional_aliases(&mut columns, column_aliases);
668                Ok(opaque_scope(&columns, alias.as_deref()))
669            }
670            FromClause::Function {
671                output_name,
672                args,
673                alias,
674                column_aliases,
675                ordinality,
676                ..
677            } => {
678                for expression in args {
679                    self.bind_expr(expression, outer, context)?;
680                }
681                let mut columns = vec![output_name.clone()];
682                apply_positional_aliases(&mut columns, column_aliases);
683                if *ordinality {
684                    columns.push("ordinality".into());
685                }
686                Ok(opaque_scope(
687                    &columns,
688                    Some(alias.as_deref().unwrap_or(output_name)),
689                ))
690            }
691            FromClause::FunctionGroup {
692                functions,
693                alias,
694                column_aliases,
695                ordinality,
696            } => {
697                for function in functions.iter_mut() {
698                    for expression in &mut function.args {
699                        self.bind_expr(expression, outer, context)?;
700                    }
701                }
702                let mut columns = functions
703                    .iter()
704                    .flat_map(|function| {
705                        if function.column_aliases.is_empty() {
706                            vec![function.output_name.clone()]
707                        } else {
708                            function.column_aliases.clone()
709                        }
710                    })
711                    .collect::<Vec<_>>();
712                apply_positional_aliases(&mut columns, column_aliases);
713                if *ordinality {
714                    columns.push("ordinality".into());
715                }
716                Ok(opaque_scope(&columns, alias.as_deref()))
717            }
718            FromClause::Subquery {
719                body,
720                alias,
721                column_aliases,
722            } => {
723                self.bind_select(body, outer, context)?;
724                let mut columns = select_output_names(body);
725                apply_positional_aliases(&mut columns, column_aliases);
726                Ok(opaque_scope(&columns, alias.as_deref()))
727            }
728        }
729    }
730
731    fn bind_join(
732        &mut self,
733        source: &mut FromClause,
734        outer: &[ColumnScope],
735        context: &ColumnBindingContext,
736    ) -> Result<ColumnScope, SQLError> {
737        let FromClause::Join {
738            left,
739            right,
740            on,
741            using,
742            natural,
743            alias,
744            column_aliases,
745            lateral,
746            ..
747        } = source
748        else {
749            unreachable!("join column binding requires a join source")
750        };
751        if let ColumnBindingMode::Rename { relation, from, to } = self.mode {
752            if *natural
753                || using.as_ref().is_some_and(|using| {
754                    using
755                        .columns
756                        .iter()
757                        .any(|column| same_identifier(column, from))
758                })
759            {
760                preserve_table_column_name(self.catalog, left, relation, from, to)?;
761                preserve_table_column_name(self.catalog, right, relation, from, to)?;
762            }
763        }
764        let left_scope = self.bind_from(left, outer, context)?;
765        let mut right_outer = Vec::new();
766        if *lateral {
767            right_outer.push(left_scope.clone());
768        }
769        right_outer.extend_from_slice(outer);
770        let right_scope = self.bind_from(right, &right_outer, context)?;
771        let input_scope = ColumnScope::combined(&left_scope, &right_scope);
772        let mut on_scopes = vec![input_scope];
773        on_scopes.extend_from_slice(outer);
774        if let Some(expression) = on {
775            self.bind_expr(expression, &on_scopes, context)?;
776        }
777        if *natural {
778            let columns = left_scope
779                .output
780                .iter()
781                .filter(|left| {
782                    right_scope
783                        .output
784                        .iter()
785                        .any(|right| same_identifier(&left.name, &right.name))
786                })
787                .map(|column| column.name.clone())
788                .collect();
789            *using = Some(crate::ast::JoinUsing {
790                columns,
791                alias: None,
792            });
793            *natural = false;
794        }
795        let scope = self.join_scope(
796            left_scope,
797            right_scope,
798            using.as_mut(),
799            alias.as_deref(),
800            column_aliases,
801        )?;
802        self.remove_dropped_column_aliases(column_aliases, &scope);
803        Ok(scope)
804    }
805
806    fn join_scope(
807        &mut self,
808        left: ColumnScope,
809        right: ColumnScope,
810        using: Option<&mut crate::ast::JoinUsing>,
811        alias: Option<&str>,
812        column_aliases: &[String],
813    ) -> Result<ColumnScope, SQLError> {
814        let using_alias = using.as_ref().and_then(|using| using.alias.clone());
815        let mut merged = Vec::new();
816        if let Some(using) = using {
817            for name in &mut using.columns {
818                let left_matches = left.unqualified(name);
819                let right_matches = right.unqualified(name);
820                for column in left_matches.iter().chain(&right_matches) {
821                    self.dependencies
822                        .extend(column.dependencies.iter().cloned());
823                }
824                let left_current = unique_current_name(&left_matches);
825                let right_current = unique_current_name(&right_matches);
826                if left_current.is_some()
827                    && right_current.is_some()
828                    && left_current != right_current
829                {
830                    return Err(SQLError::Internal(format!(
831                        "rule JOIN USING column \"{name}\" resolved to different visible names after column rebinding"
832                    )));
833                }
834                let current_name = left_current
835                    .or(right_current)
836                    .unwrap_or_else(|| name.clone());
837                let mut dependencies = BTreeSet::new();
838                for column in left_matches.iter().chain(&right_matches) {
839                    dependencies.extend(column.dependencies.iter().cloned());
840                }
841                let stored_name = name.clone();
842                name.clone_from(&current_name);
843                merged.push(ScopeColumn {
844                    name: stored_name,
845                    current_name: current_name.clone(),
846                    reference: Expr::Column(current_name),
847                    dependencies,
848                });
849            }
850        }
851        let merged_names = merged
852            .iter()
853            .map(|column| column.name.clone())
854            .collect::<Vec<_>>();
855        let mut output = merged;
856        output.extend(
857            left.output
858                .iter()
859                .chain(&right.output)
860                .filter(|column| {
861                    !merged_names
862                        .iter()
863                        .any(|name| same_identifier(name, &column.name))
864                })
865                .cloned(),
866        );
867        if let Some(alias) = alias {
868            for (position, column) in output.iter_mut().enumerate() {
869                if let Some(name) = column_aliases.get(position) {
870                    column.name.clone_from(name);
871                    column.current_name.clone_from(name);
872                }
873                column.reference = Expr::qualified_column(alias, &column.current_name);
874            }
875            let mut scope = ColumnScope {
876                output: output.clone(),
877                ..ColumnScope::default()
878            };
879            scope.insert_qualifier(alias, &output);
880            return Ok(scope);
881        }
882        let mut qualifiers = left.qualifiers;
883        qualifiers.extend(right.qualifiers);
884        let mut scope = ColumnScope { output, qualifiers };
885        if let Some(using_alias) = using_alias.as_deref() {
886            let merged = scope
887                .output
888                .iter()
889                .filter(|column| {
890                    merged_names
891                        .iter()
892                        .any(|name| same_identifier(name, &column.name))
893                })
894                .cloned()
895                .collect::<Vec<_>>();
896            scope.insert_qualifier(using_alias, &merged);
897        }
898        Ok(scope)
899    }
900
901    fn bind_projections(
902        &mut self,
903        projections: &mut Vec<Projection>,
904        local: Option<&ColumnScope>,
905        scopes: &[ColumnScope],
906        context: &ColumnBindingContext,
907    ) -> Result<(), SQLError> {
908        let mut bound = Vec::with_capacity(projections.len());
909        for mut projection in projections.drain(..) {
910            let expanded = match &projection.expr {
911                Expr::Star => local.map(|scope| scope.output.as_slice()),
912                Expr::QualifiedStar(qualifier) => {
913                    scopes.iter().find_map(|scope| scope.qualified(qualifier))
914                }
915                _ => None,
916            };
917            if let Some(columns) = expanded {
918                for column in columns {
919                    self.dependencies
920                        .extend(column.dependencies.iter().cloned());
921                    bound.push(Projection {
922                        expr: column.reference.clone(),
923                        alias: Some(column.name.clone()),
924                    });
925                }
926                continue;
927            }
928            let implicit_name = match &projection.expr {
929                Expr::Column(name) | Expr::QualifiedColumn { column: name, .. } => {
930                    Some(name.clone())
931                }
932                _ => None,
933            };
934            self.bind_expr(&mut projection.expr, scopes, context)?;
935            if projection.alias.is_none() && self.mode.is_rename() {
936                let current_name = match &projection.expr {
937                    Expr::Column(name) | Expr::QualifiedColumn { column: name, .. } => Some(name),
938                    _ => None,
939                };
940                if implicit_name
941                    .as_ref()
942                    .zip(current_name)
943                    .is_some_and(|(stored, current)| !same_identifier(stored, current))
944                {
945                    projection.alias = implicit_name;
946                }
947            }
948            bound.push(projection);
949        }
950        *projections = bound;
951        Ok(())
952    }
953}