uqa_sql/binding/
variable_sites.rs1use super::{
10 projection_columns, BindingContext, QueryBlockPlan, RowSchema, SQLError, SQLParam, ScalarExpr,
11 SchemaScope,
12};
13use crate::plan::{ProjectionPlan, UnifiedPlan};
14use crate::routines::RoutineResolution;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum VariableSiteResolution {
19 Variable,
21 Column,
23 Output,
25}
26
27pub(super) struct VariableSites {
29 names: Vec<ScalarExpr>,
31 resolutions: Vec<VariableSiteResolution>,
32}
33
34impl VariableSites {
35 fn site(&self, expression: &ScalarExpr) -> Option<usize> {
37 match expression {
38 ScalarExpr::Param(number) => number
39 .checked_sub(1)
40 .filter(|site| *site < self.names.len()),
41 _ => None,
42 }
43 }
44
45 fn label(&self, site: usize) -> Option<&str> {
47 match &self.names[site] {
48 ScalarExpr::Column(name) | ScalarExpr::QualifiedColumn { column: name, .. } => {
49 Some(name)
50 }
51 _ => None,
52 }
53 }
54
55 fn bare_name(&self, site: usize) -> Option<&str> {
57 match &self.names[site] {
58 ScalarExpr::Column(name) => Some(name),
59 _ => None,
60 }
61 }
62}
63
64fn takes(schema: &RowSchema, name: &ScalarExpr) -> bool {
66 match name {
67 ScalarExpr::Column(name) => {
68 schema.has_unqualified_column(name)
69 || schema.column_is_ambiguous(name)
70 || schema.has_qualifier(name)
71 }
72 ScalarExpr::QualifiedColumn { qualifier, column } => {
73 schema.has_qualified_column(qualifier, column)
74 || schema.qualified_column_is_ambiguous(qualifier, column)
75 }
76 _ => false,
77 }
78}
79
80impl SchemaScope {
81 pub(super) fn resolve_variable_sites(
83 &mut self,
84 expression: &mut ScalarExpr,
85 schema: &RowSchema,
86 ) {
87 let Some(sites) = self.variable_sites.as_mut() else {
88 return;
89 };
90 crate::plan::rewrite_scalar_expression(expression, &mut |node| {
91 if let Some(site) = sites.site(node) {
92 if sites.resolutions[site] == VariableSiteResolution::Variable
93 && takes(schema, &sites.names[site])
94 {
95 sites.resolutions[site] = VariableSiteResolution::Column;
96 node.clone_from(&sites.names[site]);
97 }
98 }
99 });
100 }
101
102 pub(super) fn output_names(&self, projections: &[ProjectionPlan]) -> Vec<String> {
104 let mut names = projection_columns(projections);
105 if let Some(sites) = self.variable_sites.as_ref() {
106 for (name, projection) in names.iter_mut().zip(projections) {
107 if projection.alias.is_some() {
108 continue;
109 }
110 if let Some(label) = sites
111 .site(&projection.expr)
112 .and_then(|site| sites.label(site))
113 {
114 label.clone_into(name);
115 }
116 }
117 }
118 names
119 }
120
121 pub(super) fn output_takes_variable_site(
123 &mut self,
124 expression: &ScalarExpr,
125 output_names: &[String],
126 ) -> bool {
127 let Some(sites) = self.variable_sites.as_mut() else {
128 return false;
129 };
130 let Some(site) = sites.site(expression) else {
131 return false;
132 };
133 if !sites
134 .bare_name(site)
135 .is_some_and(|name| output_names.iter().any(|output| output == name))
136 {
137 return false;
138 }
139 sites.resolutions[site] = VariableSiteResolution::Output;
140 true
141 }
142
143 pub(super) fn bind_grouping_variable_sites(
145 &mut self,
146 block: &mut QueryBlockPlan,
147 source: &RowSchema,
148 ) {
149 let output_names = self.output_names(&block.projections);
150 let Some(sites) = self.variable_sites.as_mut() else {
151 return;
152 };
153 for item in block
154 .group_by
155 .iter_mut()
156 .chain(block.grouping_sets.iter_mut().flatten())
157 {
158 let Some(site) = sites.site(item) else {
159 continue;
160 };
161 let Some(name) = sites.bare_name(site) else {
162 continue;
163 };
164 if source.has_unqualified_column(name) || source.column_is_ambiguous(name) {
165 continue;
166 }
167 let Some(position) = output_names.iter().position(|output| output == name) else {
168 continue;
169 };
170 sites.resolutions[site] = VariableSiteResolution::Output;
171 item.clone_from(&block.projections[position].expr);
172 }
173 }
174}
175
176pub fn resolve_variable_sites(
178 routines: &dyn RoutineResolution,
179 plan: &mut UnifiedPlan,
180 params: &[SQLParam],
181 ctes: &BindingContext,
182 names: Vec<ScalarExpr>,
183) -> Result<Vec<VariableSiteResolution>, SQLError> {
184 let mut scope = SchemaScope::for_analysis(ctes)?;
185 scope.binds_routine_identities = false;
186 scope.variable_sites = Some(VariableSites {
187 resolutions: vec![VariableSiteResolution::Variable; names.len()],
188 names,
189 });
190 let walked = scope.bind_statement_parameters(routines, plan, params, None);
191 let resolutions = scope
192 .variable_sites
193 .take()
194 .map(|sites| sites.resolutions)
195 .unwrap_or_default();
196 match walked {
197 Ok(()) => Ok(resolutions),
198 Err(_) if resolutions.contains(&VariableSiteResolution::Column) => Ok(resolutions),
200 Err(error) => Err(error),
201 }
202}