1use super::{BindingContext, QueryPlan, RowSchema, SQLError, SQLParam, ScalarExpr, SchemaScope};
10use crate::ast::{ColumnType, InternalRelationId};
11use crate::catalog::resolution::RelationLookupMode;
12use crate::plan::{CommandPlan, ExpressionPlan, ProjectionPlan, UnifiedPlan};
13use crate::routines::RoutineResolution;
14use std::sync::LazyLock;
15
16static ROUTINE_PARAMETERS: LazyLock<InternalRelationId> =
18 LazyLock::new(InternalRelationId::allocate);
19
20#[derive(Clone)]
22pub struct RoutineParameterScope {
23 function: String,
25 names: Vec<String>,
27 schema: RowSchema,
28}
29
30impl RoutineParameterScope {
31 #[must_use]
33 pub fn new(function: &str, names: Vec<String>, types: Vec<Option<ColumnType>>) -> Self {
34 let visible = RowSchema::with_qualified_types(function, names.clone(), types.clone());
35 let marked = RowSchema::with_internal_relation_types(*ROUTINE_PARAMETERS, types);
36 Self {
37 function: function.to_string(),
38 names,
39 schema: RowSchema::with_trailing_internal_aliases(&visible, &marked),
40 }
41 }
42
43 #[must_use]
45 pub const fn schema(&self) -> &RowSchema {
46 &self.schema
47 }
48
49 fn position_at(schema: &RowSchema, slot: usize) -> Option<usize> {
51 schema
52 .unique_internal_column_for_slot(slot)
53 .filter(|column| column.relation() == *ROUTINE_PARAMETERS)
54 .map(crate::ast::InternalColumnRef::attribute)
55 }
56
57 fn reaches(schema: &RowSchema) -> bool {
59 schema.internal_slot(ROUTINE_PARAMETERS.column(0)).is_some()
60 }
61
62 fn parameter(&self, expression: &ScalarExpr, schema: &RowSchema) -> Option<usize> {
64 match expression {
65 ScalarExpr::Column(name) => {
66 let position = Self::position_at(schema, schema.column_slot(name)?)?;
67 (!self.names_relation(schema, name)).then_some(position)
69 }
70 ScalarExpr::QualifiedColumn { qualifier, column } => {
71 match schema.qualified_slot(qualifier, column) {
72 Some(slot) => Self::position_at(schema, slot),
73 None if *qualifier == self.function && Self::reaches(schema) => self
75 .names
76 .iter()
77 .position(|name| !name.is_empty() && name == column),
78 None => None,
79 }
80 }
81 _ => None,
82 }
83 }
84
85 fn names_relation(&self, schema: &RowSchema, name: &str) -> bool {
87 schema.has_qualifier(name)
88 && (name != self.function
89 || !self.names.iter().any(|parameter| {
90 schema
91 .qualified_slot(name, parameter)
92 .and_then(|slot| Self::position_at(schema, slot))
93 .is_some()
94 }))
95 }
96}
97
98pub(super) fn column_labels(projections: &[ProjectionPlan]) -> Vec<Option<String>> {
100 projections
101 .iter()
102 .map(|projection| match &projection.expr {
103 ScalarExpr::Column(name) | ScalarExpr::QualifiedColumn { column: name, .. }
104 if projection.alias.is_none() =>
105 {
106 Some(name.clone())
107 }
108 _ => None,
109 })
110 .collect()
111}
112
113pub(super) fn keep_column_labels(projections: &mut [ProjectionPlan], labels: Vec<Option<String>>) {
115 for (projection, label) in projections.iter_mut().zip(labels) {
116 if let Some(label) = label {
117 if matches!(projection.expr, ScalarExpr::Param(_)) {
118 projection.alias = Some(label);
119 }
120 }
121 }
122}
123
124impl SchemaScope {
125 pub(super) fn routine_parameter_reference(
127 &self,
128 expression: &ScalarExpr,
129 schema: &RowSchema,
130 ) -> Option<usize> {
131 self.routine_parameters
132 .as_ref()
133 .and_then(|parameters| parameters.parameter(expression, schema))
134 .map(|position| position + 1)
135 }
136
137 pub(super) fn canonicalize_routine_parameters(
139 &self,
140 expression: &mut ScalarExpr,
141 schema: &RowSchema,
142 ) {
143 let Some(parameters) = self.routine_parameters.as_ref() else {
144 return;
145 };
146 crate::plan::rewrite_scalar_expression(expression, &mut |node| {
147 if let Some(position) = parameters.parameter(node, schema) {
148 *node = ScalarExpr::Param(position + 1);
149 }
150 });
151 }
152
153 pub(super) fn bind_statement_parameters(
155 &mut self,
156 routines: &dyn RoutineResolution,
157 plan: &mut UnifiedPlan,
158 params: &[SQLParam],
159 outer: Option<&RowSchema>,
160 ) -> Result<(), SQLError> {
161 let command = match plan {
162 UnifiedPlan::Query(query) => {
163 return self.bind_query_parameters(routines, query, params, outer);
164 }
165 UnifiedPlan::Command(command) => command.as_mut(),
166 };
167 match command {
168 CommandPlan::Explain { body, .. } => {
169 self.bind_statement_parameters(routines, body, params, outer)
170 }
171 CommandPlan::CreateTableAs { query, .. }
172 | CommandPlan::CreateMaterializedView { query, .. }
173 | CommandPlan::DeclareCursor { query, .. } => {
174 self.bind_query_parameters(routines, query, params, outer)
175 }
176 CommandPlan::Call { args, .. } => {
177 for argument in args {
178 self.bind_expression_parameters(routines, argument, params, outer)?;
179 }
180 Ok(())
181 }
182 command if command.mutation_target().is_some() => {
183 self.set_command_lookup_mode(command);
184 self.bind_command_routines_for_storage(routines, command, params, outer)
185 }
186 _ => Ok(()),
188 }
189 }
190
191 fn bind_query_parameters(
193 &mut self,
194 routines: &dyn RoutineResolution,
195 query: &mut QueryPlan,
196 params: &[SQLParam],
197 outer: Option<&RowSchema>,
198 ) -> Result<(), SQLError> {
199 let previous = self.resolution.set_lookup_mode(if query.relations_bound {
200 RelationLookupMode::Bound
201 } else {
202 RelationLookupMode::Dynamic
203 });
204 let result = self
205 .bind_query_routines_for_storage(routines, query, params, outer)
206 .map(|_| ());
207 self.resolution.set_lookup_mode(previous);
208 result
209 }
210
211 fn bind_expression_parameters(
212 &mut self,
213 routines: &dyn RoutineResolution,
214 expression: &mut ExpressionPlan,
215 params: &[SQLParam],
216 outer: Option<&RowSchema>,
217 ) -> Result<(), SQLError> {
218 for subquery in &mut expression.subqueries {
219 self.bind_query_parameters(routines, subquery, params, outer)?;
220 }
221 let schema = outer.cloned().unwrap_or_default();
222 self.canonicalize_routine_parameters(&mut expression.scalar, &schema);
223 self.resolve_variable_sites(&mut expression.scalar, &schema);
224 Ok(())
225 }
226}
227
228pub fn bind_routine_parameter_references(
230 routines: &dyn RoutineResolution,
231 plan: &mut UnifiedPlan,
232 params: &[SQLParam],
233 ctes: &BindingContext,
234 parameters: &RoutineParameterScope,
235) -> Result<(), SQLError> {
236 let mut scope = SchemaScope::for_analysis(ctes)?;
237 scope.routine_parameters = Some(parameters.clone());
238 scope.binds_routine_identities = false;
239 scope.preserve_syntax_shape = true;
241 scope.bind_statement_parameters(routines, plan, params, Some(parameters.schema()))
242}