Skip to main content

uqa_sql/binding/
scope.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Outer-scope composition and common row-value typing.
8
9use crate::ast::ColumnType;
10use crate::plan::{ExpressionPlan, QueryPlan};
11use crate::RowSchema;
12use crate::{SQLError, SQLParam};
13
14use super::{BindingContext, ScalarExpr, SchemaScope};
15use crate::routines::RoutineResolution;
16
17/// Derive the exact output row type of a query plan without executing it.
18pub fn bind_query_plan_schema(
19    routines: &dyn RoutineResolution,
20    plan: &QueryPlan,
21    params: &[SQLParam],
22    ctes: &BindingContext,
23    outer: Option<&RowSchema>,
24) -> Result<RowSchema, SQLError> {
25    SchemaScope::from_context(ctes)?.bind_query(routines, plan, params, outer)
26}
27
28/// Derive the declared SQL type of a standalone expression plan without executing it. The plan-owned subquery arena participates in type resolution so scalar subqueries retain their projected type at command boundaries such as `CALL`.
29pub fn bind_expression_plan_type(
30    routines: &dyn RoutineResolution,
31    plan: &ExpressionPlan,
32    params: &[SQLParam],
33    ctes: &BindingContext,
34) -> Result<Option<ColumnType>, SQLError> {
35    SchemaScope::from_context(ctes)?.bind_expression_type(
36        routines,
37        &plan.scalar,
38        &RowSchema::default(),
39        &plan.subqueries,
40        params,
41        None,
42    )
43}
44
45/// Validate a command argument's names and types without running its expressions.
46pub fn analyze_expression_plan_type(
47    routines: &dyn RoutineResolution,
48    plan: &ExpressionPlan,
49    params: &[SQLParam],
50    ctes: &BindingContext,
51) -> Result<Option<ColumnType>, SQLError> {
52    SchemaScope::for_analysis(ctes)?.bind_expression_type(
53        routines,
54        &plan.scalar,
55        &RowSchema::default(),
56        &plan.subqueries,
57        params,
58        None,
59    )
60}
61
62/// Analyze every catalog and scalar reference and derive the exact output row type without executing the query.
63pub fn analyze_query_plan_schema(
64    routines: &dyn RoutineResolution,
65    plan: &QueryPlan,
66    params: &[SQLParam],
67    ctes: &BindingContext,
68    outer: Option<&RowSchema>,
69) -> Result<RowSchema, SQLError> {
70    SchemaScope::for_analysis(ctes)?.bind_query(routines, plan, params, outer)
71}
72
73pub fn analyze_query_plan_schema_with_catalog(
74    routines: &dyn RoutineResolution,
75    plan: &QueryPlan,
76    params: &[SQLParam],
77    catalog: crate::catalog::analysis::CatalogReadView,
78    resolution: crate::catalog::resolution::RelationNameResolution,
79) -> Result<RowSchema, SQLError> {
80    SchemaScope::for_catalog_analysis(catalog, resolution).bind_query(routines, plan, params, None)
81}
82
83pub fn overlay_outer_schema(current: &RowSchema, outer: Option<&RowSchema>) -> RowSchema {
84    outer.map_or_else(
85        || current.clone(),
86        |outer| RowSchema::with_outer_schema(current, outer),
87    )
88}
89
90pub fn values_types_in_scope(
91    routines: &dyn RoutineResolution,
92    rows: &[Vec<ScalarExpr>],
93    subqueries: &[QueryPlan],
94    schema: Option<&RowSchema>,
95    params: &[SQLParam],
96    ctes: &BindingContext,
97) -> Result<Vec<Option<ColumnType>>, SQLError> {
98    SchemaScope::from_context(ctes)?
99        .bind_values_types(routines, rows, subqueries, schema, params, schema)
100}
101
102pub(super) fn merge_types(
103    left: Option<&ColumnType>,
104    right: Option<&ColumnType>,
105) -> Result<Option<ColumnType>, SQLError> {
106    match (left, right) {
107        (None, None) => Ok(None),
108        (Some(ty), None) | (None, Some(ty)) => Ok(Some(ty.clone())),
109        (Some(left), Some(right)) => crate::common_type(left, right).map(Some),
110    }
111}