Skip to main content

uqa_sql/binding/
sources.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Source row-type adapters and join binding inputs.
8
9use crate::ast::{JoinKind, JoinUsing};
10use crate::plan::{QueryPlan, SourcePlan, TableFunctionPlan};
11use crate::RowSchema;
12use crate::{SQLError, SQLParam};
13
14use super::{analysis, projection::rename_schema, BindingContext, ScalarExpr, SchemaScope};
15use crate::routines::RoutineResolution;
16
17pub(super) fn alias_table_schema(
18    schema: &RowSchema,
19    qualifier: &str,
20    column_aliases: &[String],
21) -> Result<RowSchema, SQLError> {
22    if column_aliases.len() > schema.len() {
23        return Err(SQLError::Routine {
24            sqlstate: "42P10".into(),
25            message: format!(
26                "table \"{qualifier}\" has {} columns available but {} columns specified",
27                schema.len(),
28                column_aliases.len()
29            ),
30        });
31    }
32    Ok(rename_schema(schema, column_aliases, Some(qualifier)))
33}
34
35pub(super) struct JoinSchemaBinding<'a> {
36    pub(super) routines: &'a dyn RoutineResolution,
37    pub(super) kind: JoinKind,
38    pub(super) on: Option<&'a ScalarExpr>,
39    pub(super) using: Option<&'a JoinUsing>,
40    pub(super) natural: bool,
41    pub(super) alias: Option<&'a str>,
42    pub(super) column_aliases: &'a [String],
43    pub(super) left: &'a RowSchema,
44    pub(super) right: &'a RowSchema,
45    pub(super) subqueries: &'a [QueryPlan],
46    pub(super) params: &'a [SQLParam],
47    pub(super) outer: Option<&'a RowSchema>,
48}
49
50pub(super) fn table_function_member_source(function: &TableFunctionPlan) -> SourcePlan {
51    SourcePlan::Function {
52        name: function.name.clone(),
53        binding: function.binding.clone(),
54        output_name: function.output_name.clone(),
55        relations: function.relations.clone(),
56        args: function.args.clone(),
57        alias: None,
58        column_aliases: function.column_aliases.clone(),
59        ordinality: false,
60        column_types: function.column_types.clone(),
61    }
62}
63
64/// Derive the exact row type of one FROM source without executing it.
65pub fn bind_source_plan_schema(
66    routines: &dyn RoutineResolution,
67    source: &SourcePlan,
68    params: &[SQLParam],
69    ctes: &BindingContext,
70    outer: Option<&RowSchema>,
71) -> Result<RowSchema, SQLError> {
72    SchemaScope::from_context(ctes)?.bind_source(
73        routines,
74        source,
75        ctes.scalar_subqueries,
76        params,
77        outer,
78    )
79}
80
81/// Add query-block pseudo columns after the complete source scope is known, so `_meta` is exposed only for one unambiguous local-table source and never shadows a real relation alias.
82pub fn with_query_table_pseudo_columns(schema: &RowSchema) -> RowSchema {
83    analysis::with_unqualified_table_pseudo_columns(schema)
84}
85
86/// Derive and validate one FROM source's exact row type without executing it.
87pub fn analyze_source_plan_schema(
88    routines: &dyn RoutineResolution,
89    source: &SourcePlan,
90    params: &[SQLParam],
91    ctes: &BindingContext,
92    outer: Option<&RowSchema>,
93) -> Result<RowSchema, SQLError> {
94    SchemaScope::for_analysis(ctes)?.bind_source(
95        routines,
96        source,
97        ctes.scalar_subqueries,
98        params,
99        outer,
100    )
101}
102
103/// Bind every table-function source in one execution-owned source plan to its exact routine identity and return the schema derived from those same bindings.
104pub fn bind_source_plan_schema_for_execution(
105    routines: &dyn RoutineResolution,
106    source: &mut SourcePlan,
107    params: &[SQLParam],
108    ctes: &BindingContext,
109    outer: Option<&RowSchema>,
110) -> Result<RowSchema, SQLError> {
111    SchemaScope::from_context(ctes)?.bind_source_for_execution(
112        routines,
113        source,
114        ctes.scalar_subqueries,
115        params,
116        outer,
117    )
118}
119
120impl SchemaScope {
121    pub(super) fn bind_source(
122        &mut self,
123        routines: &dyn RoutineResolution,
124        source: &SourcePlan,
125        subqueries: &[QueryPlan],
126        params: &[SQLParam],
127        outer: Option<&RowSchema>,
128    ) -> Result<RowSchema, SQLError> {
129        let bound = matches!(
130            source,
131            SourcePlan::Table {
132                bound_columns: Some(_),
133                ..
134            }
135        );
136        let previous = bound.then(|| {
137            self.resolution
138                .set_lookup_mode(crate::catalog::resolution::RelationLookupMode::Bound)
139        });
140        let result = self.bind_source_inner(routines, source, subqueries, params, outer);
141        if let Some(previous) = previous {
142            self.resolution.set_lookup_mode(previous);
143        }
144        result
145    }
146}