1use crate::ast::{JoinKind, JoinUsing};
10use crate::plan::{QueryPlan, SourcePlan, TableFunctionPlan};
11use crate::RowSchema;
12use crate::{SQLError, SQLParam};
13
14use super::{
15 analysis, operator_join_relation_schemas, overlay_outer_schema, projection::rename_schema,
16 BindingContext, ScalarExpr, SchemaScope,
17};
18use crate::routines::RoutineResolution;
19
20pub(super) fn alias_table_schema(
21 schema: &RowSchema,
22 qualifier: &str,
23 column_aliases: &[String],
24) -> Result<RowSchema, SQLError> {
25 if column_aliases.len() > schema.len() {
26 return Err(SQLError::Routine {
27 sqlstate: "42P10".into(),
28 message: format!(
29 "table \"{qualifier}\" has {} columns available but {} columns specified",
30 schema.len(),
31 column_aliases.len()
32 ),
33 });
34 }
35 Ok(rename_schema(schema, column_aliases, Some(qualifier)))
36}
37
38pub(super) struct JoinSchemaBinding<'a> {
39 pub(super) routines: &'a dyn RoutineResolution,
40 pub(super) kind: JoinKind,
41 pub(super) on: Option<&'a ScalarExpr>,
42 pub(super) using: Option<&'a JoinUsing>,
43 pub(super) natural: bool,
44 pub(super) alias: Option<&'a str>,
45 pub(super) column_aliases: &'a [String],
46 pub(super) left: &'a RowSchema,
47 pub(super) right: &'a RowSchema,
48 pub(super) subqueries: &'a [QueryPlan],
49 pub(super) params: &'a [SQLParam],
50 pub(super) outer: Option<&'a RowSchema>,
51}
52
53pub(super) fn table_function_member_source(function: &TableFunctionPlan) -> SourcePlan {
54 SourcePlan::Function {
55 name: function.name.clone(),
56 binding: function.binding.clone(),
57 output_name: function.output_name.clone(),
58 relations: function.relations.clone(),
59 args: function.args.clone(),
60 alias: None,
61 column_aliases: function.column_aliases.clone(),
62 ordinality: false,
63 column_types: function.column_types.clone(),
64 }
65}
66
67pub fn bind_source_plan_schema(
69 routines: &dyn RoutineResolution,
70 source: &SourcePlan,
71 params: &[SQLParam],
72 ctes: &BindingContext,
73 outer: Option<&RowSchema>,
74) -> Result<RowSchema, SQLError> {
75 SchemaScope::from_context(ctes)?.bind_source(
76 routines,
77 source,
78 ctes.scalar_subqueries,
79 params,
80 outer,
81 )
82}
83
84pub fn with_query_table_pseudo_columns(schema: &RowSchema) -> RowSchema {
86 analysis::with_unqualified_table_pseudo_columns(schema)
87}
88
89pub fn analyze_source_plan_schema(
91 routines: &dyn RoutineResolution,
92 source: &SourcePlan,
93 params: &[SQLParam],
94 ctes: &BindingContext,
95 outer: Option<&RowSchema>,
96) -> Result<RowSchema, SQLError> {
97 SchemaScope::for_analysis(ctes)?.bind_source(
98 routines,
99 source,
100 ctes.scalar_subqueries,
101 params,
102 outer,
103 )
104}
105
106pub fn bind_source_plan_schema_for_execution(
108 routines: &dyn RoutineResolution,
109 source: &mut SourcePlan,
110 params: &[SQLParam],
111 ctes: &BindingContext,
112 outer: Option<&RowSchema>,
113) -> Result<RowSchema, SQLError> {
114 SchemaScope::from_context(ctes)?.bind_source_for_execution(
115 routines,
116 source,
117 ctes.scalar_subqueries,
118 params,
119 outer,
120 )
121}
122
123impl SchemaScope {
124 pub(super) fn bind_source(
125 &mut self,
126 routines: &dyn RoutineResolution,
127 source: &SourcePlan,
128 subqueries: &[QueryPlan],
129 params: &[SQLParam],
130 outer: Option<&RowSchema>,
131 ) -> Result<RowSchema, SQLError> {
132 let bound = matches!(
133 source,
134 SourcePlan::Table {
135 bound_columns: Some(_),
136 ..
137 }
138 );
139 let previous = bound.then(|| {
140 self.resolution
141 .set_lookup_mode(crate::catalog::resolution::RelationLookupMode::Bound)
142 });
143 let result = self.bind_source_inner(routines, source, subqueries, params, outer);
144 if let Some(previous) = previous {
145 self.resolution.set_lookup_mode(previous);
146 }
147 result
148 }
149}
150
151impl SchemaScope {
152 pub(super) fn bind_source_for_execution(
153 &mut self,
154 routines: &dyn RoutineResolution,
155 source: &mut SourcePlan,
156 subqueries: &[QueryPlan],
157 params: &[SQLParam],
158 outer: Option<&RowSchema>,
159 ) -> Result<RowSchema, SQLError> {
160 match source {
161 SourcePlan::Join {
162 left,
163 right,
164 kind,
165 on,
166 using,
167 natural,
168 alias,
169 column_aliases,
170 lateral,
171 ..
172 } => {
173 let left_schema =
174 self.bind_source_for_execution(routines, left, subqueries, params, outer)?;
175 let implicit_lateral_function = matches!(
176 right.as_ref(),
177 SourcePlan::Function { .. } | SourcePlan::FunctionGroup { .. }
178 );
179 let right_scope = (*lateral || implicit_lateral_function)
180 .then(|| overlay_outer_schema(&left_schema, outer));
181 let right_schema = self.bind_source_for_execution(
182 routines,
183 right,
184 subqueries,
185 params,
186 right_scope.as_ref().or(outer),
187 )?;
188 return self.bind_join_output_schema(JoinSchemaBinding {
189 routines,
190 kind: *kind,
191 on: on.as_ref(),
192 using: using.as_ref(),
193 natural: *natural,
194 alias: alias.as_deref(),
195 column_aliases,
196 left: &left_schema,
197 right: &right_schema,
198 subqueries,
199 params,
200 outer,
201 });
202 }
203 SourcePlan::Function {
204 name,
205 binding,
206 relations,
207 args,
208 ..
209 } => {
210 let lower = crate::semantics::builtin_function_dispatch_name(name);
211 if crate::registry::is_operator_join_table_function(&lower) {
212 operator_join_relation_schemas(
213 &self.catalog,
214 &self.resolution,
215 relations.as_ref(),
216 )?;
217 return self.bind_source(routines, source, subqueries, params, outer);
218 }
219 let input = outer.cloned().unwrap_or_default();
220 let resolver = self.query_function_type_resolver_for_subqueries(
221 routines, args, &input, subqueries, params,
222 )?;
223 if let Some(selected) = crate::semantics::resolve_table_function_binding(
224 routines,
225 name,
226 binding.as_ref(),
227 args,
228 &input,
229 params,
230 &resolver,
231 )? {
232 *binding = Some(selected);
233 }
234 }
235 SourcePlan::FunctionGroup { functions, .. } => {
236 for function in functions {
237 let mut member = table_function_member_source(function);
238 self.bind_source_for_execution(
239 routines,
240 &mut member,
241 subqueries,
242 params,
243 outer,
244 )?;
245 let SourcePlan::Function { binding, .. } = member else {
246 unreachable!("table-function member changed source kind during binding")
247 };
248 function.binding = binding;
249 }
250 }
251 SourcePlan::Table { .. } | SourcePlan::Values { .. } | SourcePlan::Subquery { .. } => {}
252 }
253 self.bind_source(routines, source, subqueries, params, outer)
254 }
255}