Skip to main content

uqa_sql/binding/
mod.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Static relational row-type binding.
8//!
9//! This module derives source and query output schemas from plans, catalog
10//! declarations, and already-bound CTE schemas. It never executes a query or
11//! samples a result row, so empty, spilled, and correlated relations retain
12//! the same declared `PostgreSQL` type identities as non-empty relations.
13
14mod analysis;
15pub mod catalog_sources;
16mod command_scopes;
17mod commands;
18mod cte_controls;
19mod ctes;
20mod dependencies;
21mod merge_scopes;
22mod ordered_calls;
23mod preparation;
24mod projection;
25mod record_fields;
26mod routine_binding;
27mod routine_parameters;
28mod scope;
29mod source_schemas;
30mod sources;
31pub mod statements;
32mod type_resolution;
33mod variable_sites;
34
35pub use commands::analyze_prepared_command_schema;
36pub use preparation::{analyze_column_type_transform, infer_prepared_parameter_types};
37pub(crate) use preparation::{
38    analyze_default_inputs, analyze_routine_body_argument, analyze_routine_body_inputs,
39    prepare_routine_body_inputs,
40};
41pub(crate) use preparation::{
42    analyze_domain_check, analyze_stored_expression_inputs, read_prepared_inputs,
43};
44
45#[cfg(test)]
46mod tests;
47
48pub use cte_controls::{analyze_recursive_control_step, extend_cte_generated_schema};
49pub use projection::{
50    analyze_projection_output_schema, bind_projection_output_schema,
51    validate_query_block_expression_types, validate_query_block_references,
52};
53pub use routine_binding::{
54    bind_expression_plan_routines_for_storage, bind_query_plan_routines_for_storage,
55    bind_syntax_query_plan_routines,
56};
57pub use routine_parameters::{bind_routine_parameter_references, RoutineParameterScope};
58pub use scope::{
59    analyze_expression_plan_type, analyze_query_plan_schema,
60    analyze_query_plan_schema_with_catalog, bind_expression_plan_type, bind_query_plan_schema,
61};
62pub use scope::{overlay_outer_schema, values_types_in_scope};
63pub use sources::{
64    analyze_source_plan_schema, bind_source_plan_schema, bind_source_plan_schema_for_execution,
65    with_query_table_pseudo_columns,
66};
67pub use variable_sites::{resolve_variable_sites, VariableSiteResolution};
68
69use catalog_sources::operator_join_relation_schemas;
70use cte_controls::extend_recursive_cte_binding_schema;
71pub use cte_controls::hide_recursive_generated_schema;
72use projection::{projection_star_columns, rename_schema};
73use sources::{alias_table_schema, table_function_member_source, JoinSchemaBinding};
74use type_resolution::{set_operation_output_schema, QueryFunctionTypeResolver};
75
76use crate::plan::{QueryBlockPlan, QueryPlan, RelationalPlan, SourcePlan};
77use crate::semantics::{cte_references_own_name, expr_contains_subquery, projection_columns};
78use crate::{SQLError, SQLParam, ScalarExpr};
79use catalog_sources::user_function_output_columns;
80pub use context::BindingContext;
81use uqa_core::Value;
82
83pub mod context;
84use crate::ast::ColumnType;
85use crate::catalog::analysis::CatalogReadView;
86use crate::catalog::resolution::RelationNameResolution;
87use crate::routines::RoutineResolution;
88use crate::semantics::{
89    apply_table_function_aliases, table_function_column_types, table_function_empty_schema,
90    validate_table_function_alias_count, validate_table_function_column_definition,
91    TableFunctionTypeRequest,
92};
93use crate::RowSchema;
94use std::collections::{BTreeMap, BTreeSet};
95
96struct SchemaScope {
97    catalog: CatalogReadView,
98    resolution: RelationNameResolution,
99    ctes: BTreeMap<String, RowSchema>,
100    deferred_ctes: BTreeMap<String, crate::plan::CtePlan>,
101    non_returning_ctes: BTreeSet<String>,
102    visiting_views: BTreeSet<String>,
103    validate_references: bool,
104    stored_expression_outer: Option<RowSchema>,
105    /// The parameters of the SQL routine whose body is bound, as the outermost scope: a reference that resolves into them, because no column of any query level takes its name, becomes the positional parameter it names.
106    routine_parameters: Option<RoutineParameterScope>,
107    /// Whether binding a stored expression also fixes the routines it calls; a pass that only resolves routine parameters leaves calls to analysis.
108    binds_routine_identities: bool,
109    /// The names of a `PL/pgSQL` statement that the function's variables take, checked against what the statement can see.
110    variable_sites: Option<variable_sites::VariableSites>,
111    /// Keep `*` projections and `GROUP BY` output-name references as written, so a bound copy of stored syntax still corresponds to that syntax node for node.
112    preserve_syntax_shape: bool,
113    prepared_dependencies: Option<crate::prepared::dependencies::PreparedAnalysisDependencies>,
114}
115
116fn non_returning_cte_error(name: &str) -> SQLError {
117    SQLError::Unsupported(format!(
118        "WITH query \"{name}\" does not have a RETURNING clause"
119    ))
120}
121
122impl SchemaScope {
123    fn from_context(ctes: &BindingContext) -> Result<Self, SQLError> {
124        Ok(Self {
125            catalog: ctes.catalog.clone(),
126            resolution: ctes.resolution.clone(),
127            ctes: ctes.ctes.clone(),
128            deferred_ctes: ctes.deferred_ctes.clone(),
129            non_returning_ctes: ctes.non_returning_ctes.clone(),
130            visiting_views: BTreeSet::new(),
131            validate_references: false,
132            stored_expression_outer: None,
133            routine_parameters: None,
134            binds_routine_identities: true,
135            variable_sites: None,
136            preserve_syntax_shape: false,
137            prepared_dependencies: None,
138        })
139    }
140
141    fn for_analysis(ctes: &BindingContext) -> Result<Self, SQLError> {
142        let mut scope = Self::from_context(ctes)?;
143        scope.validate_references = true;
144        Ok(scope)
145    }
146
147    fn for_catalog_analysis(catalog: CatalogReadView, resolution: RelationNameResolution) -> Self {
148        Self {
149            catalog,
150            resolution,
151            ctes: BTreeMap::new(),
152            deferred_ctes: BTreeMap::new(),
153            non_returning_ctes: BTreeSet::new(),
154            visiting_views: BTreeSet::new(),
155            validate_references: true,
156            stored_expression_outer: None,
157            routine_parameters: None,
158            binds_routine_identities: true,
159            variable_sites: None,
160            preserve_syntax_shape: false,
161            prepared_dependencies: None,
162        }
163    }
164
165    fn bind_query(
166        &mut self,
167        routines: &dyn RoutineResolution,
168        plan: &QueryPlan,
169        params: &[SQLParam],
170        outer: Option<&RowSchema>,
171    ) -> Result<RowSchema, SQLError> {
172        self.bind_query_mode(routines, plan, params, outer, false)
173    }
174
175    fn bind_set_operand(
176        &mut self,
177        routines: &dyn RoutineResolution,
178        plan: &QueryPlan,
179        params: &[SQLParam],
180        outer: Option<&RowSchema>,
181    ) -> Result<RowSchema, SQLError> {
182        self.bind_query_mode(routines, plan, params, outer, true)
183    }
184
185    fn bind_query_mode(
186        &mut self,
187        routines: &dyn RoutineResolution,
188        plan: &QueryPlan,
189        params: &[SQLParam],
190        outer: Option<&RowSchema>,
191        preserve_top_level_unknown: bool,
192    ) -> Result<RowSchema, SQLError> {
193        let lookup_mode = if plan.relations_bound {
194            crate::catalog::resolution::RelationLookupMode::Bound
195        } else {
196            crate::catalog::resolution::RelationLookupMode::Dynamic
197        };
198        let previous = self.resolution.set_lookup_mode(lookup_mode);
199        let result =
200            self.bind_query_mode_inner(routines, plan, params, outer, preserve_top_level_unknown);
201        self.resolution.set_lookup_mode(previous);
202        result
203    }
204
205    fn bind_query_mode_inner(
206        &mut self,
207        routines: &dyn RoutineResolution,
208        plan: &QueryPlan,
209        params: &[SQLParam],
210        outer: Option<&RowSchema>,
211        preserve_top_level_unknown: bool,
212    ) -> Result<RowSchema, SQLError> {
213        let previous = self.bind_cte_schemas(routines, &plan.ctes, params, outer)?;
214
215        let result = self.bind_root(
216            routines,
217            &plan.root,
218            params,
219            outer,
220            preserve_top_level_unknown,
221        );
222        self.restore_cte_schemas(previous);
223        result
224    }
225
226    fn bind_recursive_seed(
227        &mut self,
228        routines: &dyn RoutineResolution,
229        plan: &QueryPlan,
230        params: &[SQLParam],
231        outer: Option<&RowSchema>,
232    ) -> Result<RowSchema, SQLError> {
233        match &plan.root {
234            RelationalPlan::SetOp { left, .. } => self.bind_query(routines, left, params, outer),
235            _ => self.bind_root(routines, &plan.root, params, outer, false),
236        }
237    }
238
239    fn bind_root(
240        &mut self,
241        routines: &dyn RoutineResolution,
242        root: &RelationalPlan,
243        params: &[SQLParam],
244        outer: Option<&RowSchema>,
245        preserve_top_level_unknown: bool,
246    ) -> Result<RowSchema, SQLError> {
247        match root {
248            RelationalPlan::QueryBlock(block) => {
249                self.bind_query_block(routines, block, params, outer, preserve_top_level_unknown)
250            }
251            RelationalPlan::SetOp {
252                kind,
253                all,
254                left,
255                right,
256                order_by,
257                limit,
258                offset,
259                subqueries,
260                ..
261            } => {
262                let left = self.bind_set_operand(routines, left, params, outer)?;
263                let right = self.bind_set_operand(routines, right, params, outer)?;
264                if left.len() != right.len() {
265                    return Err(SQLError::TypeMismatch(format!(
266                        "set operation has {} columns on the left and {} on the right",
267                        left.len(),
268                        right.len()
269                    )));
270                }
271                let output = set_operation_output_schema(&left, &right, *kind, *all)?;
272                if self.validate_references {
273                    self.validate_set_operation_clauses(
274                        routines,
275                        analysis::SetOperationClauses {
276                            order_by,
277                            limit: limit.as_deref(),
278                            offset: offset.as_deref(),
279                            subqueries,
280                            output: &output,
281                            outer,
282                        },
283                        params,
284                    )?;
285                }
286                Ok(output)
287            }
288            RelationalPlan::Values { rows, subqueries } => {
289                let columns = rows.first().map_or_else(Vec::new, |row| {
290                    (1..=row.len())
291                        .map(|index| format!("column{index}"))
292                        .collect()
293                });
294                let types = self.bind_values_types(routines, rows, subqueries, outer, params)?;
295                Ok(RowSchema::with_types(columns, types))
296            }
297        }
298    }
299
300    fn bind_query_block(
301        &mut self,
302        routines: &dyn RoutineResolution,
303        block: &QueryBlockPlan,
304        params: &[SQLParam],
305        outer: Option<&RowSchema>,
306        preserve_top_level_unknown: bool,
307    ) -> Result<RowSchema, SQLError> {
308        let source = block.from.as_ref().map_or_else(
309            || Ok(RowSchema::default()),
310            |source| self.bind_source(routines, source, &block.subqueries, params, outer),
311        )?;
312        let source = if self.validate_references {
313            analysis::with_query_source_columns(&source, block)
314        } else {
315            source
316        };
317        let expression_schema = overlay_outer_schema(&source, outer);
318        let labels = projection_columns(&block.projections);
319        let mut columns = Vec::new();
320        let mut types = Vec::new();
321        let mut records = Vec::new();
322        for (position, projection) in block.projections.iter().enumerate() {
323            if let Some(star_columns) = projection_star_columns(&projection.expr, &source)? {
324                records.extend(
325                    record_fields::star_fields(&projection.expr, &source).unwrap_or_default(),
326                );
327                for (column, ty) in star_columns {
328                    columns.push(column);
329                    types.push(ty);
330                }
331                continue;
332            }
333            columns.push(labels[position].clone());
334            types.push(
335                if preserve_top_level_unknown
336                    && matches!(
337                        &projection.expr,
338                        ScalarExpr::Literal(Value::Str(_) | Value::Null)
339                    )
340                {
341                    None
342                } else if matches!(&projection.expr, ScalarExpr::Literal(Value::Null)) {
343                    Some(ColumnType::Text)
344                } else {
345                    self.bind_expression_type(
346                        routines,
347                        &projection.expr,
348                        &expression_schema,
349                        &block.subqueries,
350                        params,
351                    )?
352                },
353            );
354            records.push(
355                if types.last().is_some_and(|ty| {
356                    matches!(ty, Some(ColumnType::Record | ColumnType::Composite(_)))
357                }) {
358                    self.bind_record_fields(
359                        routines,
360                        &projection.expr,
361                        &expression_schema,
362                        &block.subqueries,
363                        params,
364                    )?
365                } else {
366                    None
367                },
368            );
369        }
370        let output = RowSchema::with_types(columns, types).with_record_fields(
371            records
372                .into_iter()
373                .enumerate()
374                .filter_map(|(index, fields)| fields.map(|fields| (index, fields))),
375        );
376        let output = analysis::with_projected_open_columns(&output, &block.projections, &source);
377        if self.validate_references {
378            self.validate_query_block_clauses(
379                routines,
380                block,
381                &expression_schema,
382                &output,
383                params,
384                outer,
385            )?;
386        }
387        Ok(output)
388    }
389
390    fn bind_expression_type(
391        &mut self,
392        routines: &dyn RoutineResolution,
393        expression: &ScalarExpr,
394        schema: &RowSchema,
395        subqueries: &[QueryPlan],
396        params: &[SQLParam],
397    ) -> Result<Option<ColumnType>, SQLError> {
398        if let ScalarExpr::ScalarSubquery(index) = expression {
399            let plan = subqueries.get(*index).ok_or_else(|| {
400                SQLError::Internal(format!("scalar subquery slot {index} is out of bounds"))
401            })?;
402            // Result typing and validation share the complete lexical row scope.
403            let subquery_outer = Some(schema);
404            let output = self.bind_query(routines, plan, params, subquery_outer)?;
405            return Ok(output.column_type(0).cloned());
406        }
407        let schema = self.with_stored_outer_internal_aliases(schema);
408        let schema = &schema;
409        let resolver =
410            self.query_function_type_resolver(routines, expression, schema, subqueries, params)?;
411        if self.validate_references {
412            Self::validate_expression_references_with_resolver(
413                routines, expression, schema, None, params, &resolver,
414            )?;
415        }
416        crate::type_resolution::validate_catalog_literals(expression, schema, params, &resolver)?;
417        crate::scalar_type_with_resolver(expression, schema, params, &resolver)
418    }
419
420    #[expect(
421        clippy::too_many_lines,
422        reason = "preserves SELECT schema and row identity"
423    )]
424    fn bind_source_inner(
425        &mut self,
426        routines: &dyn RoutineResolution,
427        source: &SourcePlan,
428        subqueries: &[QueryPlan],
429        params: &[SQLParam],
430        outer: Option<&RowSchema>,
431    ) -> Result<RowSchema, SQLError> {
432        match source {
433            SourcePlan::Table {
434                name,
435                qualifier,
436                alias,
437                column_aliases,
438                bound_columns,
439                ..
440            } => {
441                let qualifier = alias.as_deref().unwrap_or(qualifier);
442                let cte_name = crate::semantics::cte_reference_name(name);
443                if let Some(schema) = cte_name.as_ref().and_then(|name| self.ctes.get(name)) {
444                    if let Some(name) = cte_name
445                        .as_ref()
446                        .filter(|name| self.non_returning_ctes.contains(*name))
447                    {
448                        return Err(non_returning_cte_error(name));
449                    }
450                    return alias_table_schema(schema, qualifier, column_aliases);
451                }
452                if let Some(plan) = cte_name
453                    .as_ref()
454                    .and_then(|name| self.deferred_ctes.get(name))
455                {
456                    if !plan.body.returns_rows() {
457                        return Err(non_returning_cte_error(&plan.name));
458                    }
459                }
460                if let Some(plan) = cte_name.and_then(|name| self.deferred_ctes.remove(&name)) {
461                    let result = self
462                        .bind_cte_body(routines, &plan.body, params, outer)
463                        .and_then(|schema| {
464                            let schema = rename_schema(&schema, &plan.columns, Some(qualifier));
465                            alias_table_schema(&schema, qualifier, column_aliases)
466                        });
467                    self.deferred_ctes.insert(plan.name.clone(), plan);
468                    return result;
469                }
470                if self.catalog.sequence_exists(&self.resolution, name)? {
471                    self.record_relation_dependency(name)?;
472                    let schema = RowSchema::with_qualified_types(
473                        qualifier,
474                        vec!["last_value".into(), "log_cnt".into(), "is_called".into()],
475                        vec![
476                            Some(ColumnType::BigInteger),
477                            Some(ColumnType::BigInteger),
478                            Some(ColumnType::Boolean),
479                        ],
480                    );
481                    return alias_table_schema(&schema, qualifier, column_aliases);
482                }
483                let view = self.catalog.view_resolved(&self.resolution, name)?;
484                if let Some(view) = view {
485                    self.record_relation_dependency(name)?;
486                    if view.materialized {
487                        let columns = view.output_columns.unwrap_or_default();
488                        if columns.len() != view.materialized_column_types.len() {
489                            return Err(SQLError::Internal(format!(
490                                "materialized view `{name}` has {} columns but {} stored column types",
491                                columns.len(),
492                                view.materialized_column_types.len()
493                            )));
494                        }
495                        let schema = RowSchema::with_qualified_types(
496                            qualifier,
497                            columns,
498                            view.materialized_column_types,
499                        );
500                        return alias_table_schema(&schema, qualifier, column_aliases);
501                    }
502                    let key = name.to_ascii_lowercase();
503                    if !self.visiting_views.insert(key.clone()) {
504                        return Err(SQLError::Internal(format!(
505                            "view `{name}` has a recursive schema dependency"
506                        )));
507                    }
508                    self.record_stored_query_dependencies(&view.query);
509                    let result = self
510                        .bind_query(routines, &view.query, params, outer)
511                        .and_then(|schema| {
512                            let schema = rename_schema(
513                                &schema,
514                                view.output_columns.as_deref().unwrap_or(&[]),
515                                Some(qualifier),
516                            );
517                            alias_table_schema(&schema, qualifier, column_aliases)
518                        });
519                    self.visiting_views.remove(&key);
520                    return result;
521                }
522                let table = self.catalog.table_resolved(&self.resolution, name)?;
523                if let Some(table) = table {
524                    self.record_relation_dependency(name)?;
525                    let columns = table
526                        .columns
527                        .iter()
528                        .map(|column| column.name.clone())
529                        .collect();
530                    let types = table
531                        .columns
532                        .iter()
533                        .map(|column| Some(column.ty.clone()))
534                        .collect();
535                    let schema = RowSchema::with_qualified_types(qualifier, columns, types);
536                    let schema =
537                        crate::semantics::bound_source_schema(&schema, bound_columns.as_deref())?;
538                    let schema = alias_table_schema(&schema, qualifier, column_aliases)?;
539                    let schema = if table.columns.is_empty() && !table.columns_declared {
540                        RowSchema::with_open_columns(&schema, Some(qualifier))
541                    } else {
542                        schema
543                    };
544                    return Ok(analysis::with_table_pseudo_columns(&schema, qualifier));
545                }
546                let foreign_table = self
547                    .catalog
548                    .foreign_table_resolved(&self.resolution, name)?;
549                if let Some(foreign_table) = foreign_table {
550                    self.record_relation_dependency(name)?;
551                    let typed_columns = foreign_table
552                        .columns
553                        .iter()
554                        .map(|column| (column.name.clone(), column.ty.clone()))
555                        .collect::<Vec<_>>();
556                    let columns = typed_columns
557                        .iter()
558                        .map(|(column, _)| column.clone())
559                        .collect();
560                    let types = typed_columns.into_iter().map(|(_, ty)| Some(ty)).collect();
561                    let schema = RowSchema::with_qualified_types(qualifier, columns, types);
562                    let schema =
563                        crate::semantics::bound_source_schema(&schema, bound_columns.as_deref())?;
564                    return alias_table_schema(&schema, qualifier, column_aliases);
565                }
566                if let Some(schema) = self
567                    .catalog
568                    .virtual_relation_schema(&self.resolution, name)?
569                {
570                    self.record_relation_dependency(name)?;
571                    let (columns, types): (Vec<_>, Vec<_>) = schema
572                        .into_iter()
573                        .map(|(column, ty)| (column, Some(ty)))
574                        .unzip();
575                    let schema = RowSchema::with_qualified_types(qualifier, columns, types);
576                    return alias_table_schema(&schema, qualifier, column_aliases);
577                }
578                if let Some(relation) = self.catalog.unopenable_relation(&self.resolution, name)? {
579                    return Err(relation.error());
580                }
581                Err(SQLError::UnknownTable(name.clone()))
582            }
583            SourcePlan::Values {
584                rows,
585                alias,
586                column_aliases,
587                internal_relation,
588                internal_column_types,
589            } => {
590                if let Some(relation) = internal_relation {
591                    return Ok(RowSchema::with_internal_relation_types(
592                        *relation,
593                        internal_column_types.clone(),
594                    ));
595                }
596                let columns = if column_aliases.is_empty() {
597                    (1..=rows.first().map_or(0, Vec::len))
598                        .map(|index| format!("column{index}"))
599                        .collect::<Vec<_>>()
600                } else {
601                    column_aliases.clone()
602                };
603                let binding_schema = outer.cloned().unwrap_or_default();
604                let types = self.bind_values_types(
605                    routines,
606                    rows,
607                    subqueries,
608                    Some(&binding_schema),
609                    params,
610                )?;
611                Ok(match alias.as_deref() {
612                    Some(qualifier) => RowSchema::with_qualified_types(qualifier, columns, types),
613                    None => RowSchema::with_types(columns, types),
614                })
615            }
616            SourcePlan::Function {
617                name,
618                binding,
619                output_name,
620                relations,
621                args,
622                alias,
623                column_aliases,
624                ordinality,
625                column_types,
626                ..
627            } => {
628                let lower = crate::semantics::builtin_function_dispatch_name(name);
629                let operator_join = crate::registry::is_operator_join_table_function(&lower);
630                let operator_inputs = operator_join
631                    .then(|| {
632                        operator_join_relation_schemas(
633                            &self.catalog,
634                            &self.resolution,
635                            relations.as_ref(),
636                        )
637                    })
638                    .transpose()?;
639                let input = outer.cloned().unwrap_or_default();
640                let type_resolver = self.query_function_type_resolver_for_subqueries(
641                    routines, args, &input, subqueries, params,
642                )?;
643                let user_function = if let Some((left, right)) = operator_inputs.as_ref() {
644                    if self.validate_references {
645                        let constant = RowSchema::default();
646                        for (position, argument) in args.iter().enumerate() {
647                            let schema = match position {
648                                0 => left,
649                                1 => right,
650                                _ => &constant,
651                            };
652                            self.validate_expression_references(
653                                routines, argument, schema, None, subqueries, params,
654                            )?;
655                        }
656                    }
657                    None
658                } else if self.validate_references {
659                    self.validate_table_function_source(
660                        routines,
661                        analysis::TableFunctionSourceValidation {
662                            name,
663                            binding: binding.as_ref(),
664                            args,
665                            subqueries,
666                            input: &input,
667                            params,
668                        },
669                    )?
670                } else {
671                    crate::semantics::resolve_user_table_function(
672                        routines,
673                        name,
674                        binding.as_ref(),
675                        args,
676                        &input,
677                        params,
678                        &type_resolver,
679                    )?
680                };
681                validate_table_function_column_definition(
682                    name,
683                    binding.as_ref(),
684                    user_function
685                        .as_ref()
686                        .map(|resolved| resolved.function.as_ref()),
687                    column_types,
688                )?;
689                if let Some(function) = &user_function {
690                    self.record_routine_dependency(&function.binding);
691                }
692                let catalog_columns = if !self.validate_references && user_function.is_none() {
693                    user_function_output_columns(self.catalog.as_ref(), &self.resolution, name)?
694                } else {
695                    None
696                };
697                let columns = user_function
698                    .as_ref()
699                    .map(|resolved| {
700                        crate::semantics::user_function_composite_result(
701                            routines,
702                            &resolved.function,
703                            resolved.binding.invocation.as_deref(),
704                        )
705                        .map(|descriptor| {
706                            descriptor
707                                .map(|descriptor| {
708                                    descriptor
709                                        .attributes
710                                        .iter()
711                                        .map(|attribute| attribute.name.clone())
712                                        .collect()
713                                })
714                                .or_else(|| {
715                                    crate::semantics::user_function_output_columns_for(
716                                        &resolved.function,
717                                    )
718                                })
719                        })
720                    })
721                    .transpose()?
722                    .flatten()
723                    .or(catalog_columns)
724                    .map_or_else(
725                        || {
726                            table_function_empty_schema(
727                                name,
728                                output_name,
729                                alias.as_deref(),
730                                column_aliases,
731                                args.len(),
732                                *ordinality,
733                            )
734                        },
735                        |columns| {
736                            apply_table_function_aliases(columns, column_aliases, *ordinality)
737                        },
738                    );
739                validate_table_function_alias_count(
740                    alias.as_deref().unwrap_or(output_name),
741                    columns.len(),
742                    column_aliases.len(),
743                )?;
744                let types = table_function_column_types(
745                    routines,
746                    TableFunctionTypeRequest {
747                        name,
748                        binding: binding.as_ref(),
749                        args,
750                        user_function: user_function
751                            .as_ref()
752                            .map(|resolved| resolved.function.as_ref()),
753                        user_invocation: user_function
754                            .as_ref()
755                            .and_then(|resolved| resolved.binding.invocation.as_deref()),
756                        declared_types: column_types,
757                        columns: &columns,
758                        ordinality: *ordinality,
759                    },
760                    &input,
761                    params,
762                    &type_resolver,
763                );
764                let qualifier = alias.as_deref().unwrap_or(output_name);
765                let schema = RowSchema::with_qualified_types(qualifier, columns, types);
766                Ok(
767                    if user_function.is_none()
768                        && column_types.is_empty()
769                        && routines.has_registered_table_function(name)
770                    {
771                        RowSchema::with_open_columns(&schema, Some(qualifier))
772                    } else {
773                        schema
774                    },
775                )
776            }
777            SourcePlan::FunctionGroup {
778                functions,
779                alias,
780                column_aliases,
781                ordinality,
782            } => {
783                let first = functions
784                    .first()
785                    .ok_or_else(|| SQLError::Internal("ROWS FROM group has no functions".into()))?;
786                let mut columns = Vec::new();
787                let mut types = Vec::new();
788                let mut open = false;
789                for function in functions {
790                    let member = table_function_member_source(function);
791                    let schema = self.bind_source(routines, &member, subqueries, params, outer)?;
792                    open |= schema.columns_are_open(None);
793                    columns.extend(schema.iter().enumerate().map(|(position, column)| {
794                        schema.public_name(position).unwrap_or(column).to_string()
795                    }));
796                    types.extend(schema.column_types().iter().cloned());
797                }
798                if *ordinality {
799                    columns.push("ordinality".into());
800                    types.push(Some(ColumnType::BigInteger));
801                }
802                let qualifier = alias.as_deref().unwrap_or(&first.output_name);
803                validate_table_function_alias_count(
804                    qualifier,
805                    columns.len(),
806                    column_aliases.len(),
807                )?;
808                for (column, alias) in columns.iter_mut().zip(column_aliases) {
809                    column.clone_from(alias);
810                }
811                let schema = RowSchema::with_qualified_types(qualifier, columns, types);
812                Ok(if open {
813                    RowSchema::with_open_columns(&schema, Some(qualifier))
814                } else {
815                    schema
816                })
817            }
818            SourcePlan::Subquery {
819                body,
820                alias,
821                column_aliases,
822            } => {
823                let schema = self.bind_query(routines, body, params, outer)?;
824                Ok(rename_schema(&schema, column_aliases, alias.as_deref()))
825            }
826            SourcePlan::Join {
827                left,
828                right,
829                kind,
830                on,
831                using,
832                natural,
833                alias,
834                column_aliases,
835                lateral,
836                ..
837            } => {
838                let left_schema = self.bind_source(routines, left, subqueries, params, outer)?;
839                let implicit_lateral_function = matches!(
840                    right.as_ref(),
841                    SourcePlan::Function { .. } | SourcePlan::FunctionGroup { .. }
842                );
843                let right_scope = (*lateral || implicit_lateral_function)
844                    .then(|| overlay_outer_schema(&left_schema, outer));
845                let right_schema = self.bind_source(
846                    routines,
847                    right,
848                    subqueries,
849                    params,
850                    right_scope.as_ref().or(outer),
851                )?;
852                self.bind_join_output_schema(JoinSchemaBinding {
853                    routines,
854                    kind: *kind,
855                    on: on.as_ref(),
856                    using: using.as_ref(),
857                    natural: *natural,
858                    alias: alias.as_deref(),
859                    column_aliases,
860                    left: &left_schema,
861                    right: &right_schema,
862                    subqueries,
863                    params,
864                    outer,
865                })
866            }
867        }
868    }
869}
870
871#[cfg(test)]
872pub(crate) mod fixture;
873
874pub mod correlation;
875
876pub mod stored_columns;
877pub mod stored_relations;
878pub mod stored_routines;
879pub mod stored_types;
880pub mod syntax_sites;
881
882pub mod scoped_types;
883pub mod snapshot;
884
885pub mod view_dependencies;
886
887pub mod portals;