1mod arguments;
11mod operators;
12mod properties;
13#[cfg(test)]
14mod tests;
15
16use super::{
17 body_parameters::sql_body_parameter_scope,
18 body_validation::routine_parameter_values,
19 compilation::{RoutineCompilationCatalog, RoutineParserCatalog},
20 declaration::RoutineTypeCatalog,
21 result_check::{sql_function_result_layout, SQLFunctionResultKind},
22 security::{ensure_routine_execute_privilege, RoutineExecutionAuthority},
23 CompiledFunctionBody, RoutineBody, RoutineResolution,
24};
25use crate::{
26 ast::{CreateFunction, FunctionBinding, FunctionBody, FunctionReturns},
27 binding::{bind_routine_parameter_references, prepare_routine_body_inputs},
28 plan::{ComputePlan, QueryBlockPlan, RelationalPlan, UnifiedPlan},
29 semantics::volatility::VolatilityCatalog,
30 ColumnType, SQLError, ScalarExpr,
31};
32
33pub trait RoutinePlanExpressions {
35 fn evaluate_constant_routine(
38 &self,
39 expression: &ScalarExpr,
40 ) -> Result<uqa_core::Value, SQLError>;
41}
42
43#[derive(Clone, Copy)]
44pub struct RoutineInliningContext<'a> {
45 pub routines: &'a dyn RoutineResolution,
46 pub types: &'a dyn RoutineTypeCatalog,
47 pub parsers: &'a dyn RoutineParserCatalog,
48 pub catalog: &'a dyn RoutineCompilationCatalog,
49 pub authority: &'a dyn RoutineExecutionAuthority,
50 pub volatility: &'a dyn VolatilityCatalog,
51 pub expressions: &'a dyn RoutinePlanExpressions,
52}
53
54impl std::fmt::Debug for RoutineInliningContext<'_> {
55 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 formatter
57 .debug_struct("RoutineInliningContext")
58 .finish_non_exhaustive()
59 }
60}
61
62pub struct InlineRoutineExpression {
65 pub identity: [u8; 16],
66 pub expression: ScalarExpr,
67}
68
69impl RoutineInliningContext<'_> {
70 pub fn expression_volatility(
72 &self,
73 expression: &ScalarExpr,
74 schema: &crate::RowSchema,
75 ) -> Result<crate::ast::FunctionVolatility, SQLError> {
76 properties::volatility(self, expression, schema)
77 }
78
79 pub fn prepare(
82 &self,
83 binding: &FunctionBinding,
84 arguments: &[ScalarExpr],
85 active: &[[u8; 16]],
86 ) -> Result<Option<InlineRoutineExpression>, SQLError> {
87 if binding.builtin || binding.resolution_error.is_some() {
88 return Ok(None);
89 }
90 let Some(identity) = binding.object_id else {
91 return Ok(None);
92 };
93 let Some(function) = self.selected_function(binding) else {
94 return Ok(None);
95 };
96 let Some(invocation) = binding.invocation.as_deref() else {
97 return Ok(None);
98 };
99 let specialized = super::invocation::specialized_definition(&function.def, invocation)?;
100 let definition = specialized.as_ref().unwrap_or(&function.def);
101 let arguments_for_evaluation = arguments;
102 let Some(arguments) = arguments::prepare(self, definition, invocation, arguments)? else {
103 return Ok(None);
104 };
105 if let Some(expression) =
106 self.constant_call(definition, binding, &arguments, arguments_for_evaluation)?
107 {
108 return Ok(Some(InlineRoutineExpression {
109 identity,
110 expression,
111 }));
112 }
113 if !eligible_definition(definition) || active.contains(&identity) {
114 return Ok(None);
115 }
116 match ensure_routine_execute_privilege(self.authority, definition) {
117 Ok(()) => {}
118 Err(error) if error.sqlstate() == Some("42501") => return Ok(None),
119 Err(error) => return Err(error),
120 }
121 let Some((mut expression, parameters)) = self.analyze_body(&function, definition)? else {
122 return Ok(None);
123 };
124 let properties = properties::inspect(self, &expression, ¶meters)?;
125 if !properties.permits(definition) {
126 return Ok(None);
127 }
128 let uses = arguments::use_counts(&expression, arguments.len())?;
129 for (argument, count) in arguments.iter().zip(uses) {
130 if count == 0 && definition.strict {
131 return Ok(None);
132 }
133 if count > 1 && !properties::can_duplicate(self, argument)? {
134 return Ok(None);
135 }
136 }
137 arguments::substitute(&mut expression, &arguments);
138 Ok(Some(InlineRoutineExpression {
139 identity,
140 expression,
141 }))
142 }
143
144 fn constant_call(
145 &self,
146 definition: &CreateFunction,
147 binding: &FunctionBinding,
148 arguments: &[ScalarExpr],
149 arguments_for_evaluation: &[ScalarExpr],
150 ) -> Result<Option<ScalarExpr>, SQLError> {
151 if !definition.is_procedure
152 && !definition.returns_set()
153 && definition.output_params().len() <= 1
154 && !super::routine_returns_anonymous_record(definition)
155 {
156 let layout = super::result_check::declared_sql_function_result(self.types, definition)?;
157 if definition.strict && arguments.iter().any(is_null_constant) {
158 return Ok(Some(ScalarExpr::TypedLiteral {
159 composite_source: None,
160 value: uqa_core::Value::Null,
161 ty: layout.declared_type.catalog_name(),
162 bound_type: Some(layout.declared_type),
163 parameter_index: None,
164 }));
165 }
166 if definition.volatility == crate::ast::FunctionVolatility::Immutable
167 && arguments.iter().all(is_constant)
168 {
169 let call = ScalarExpr::Func {
170 name: binding.name.clone(),
171 binding: Some(binding.clone()),
172 args: arguments_for_evaluation.to_vec(),
173 distinct: false,
174 order_by: Vec::new(),
175 order_syntax: crate::ast::FunctionCallSyntax::Ordinary,
176 filter: None,
177 };
178 let value = self.expressions.evaluate_constant_routine(&call)?;
179 return Ok(Some(ScalarExpr::TypedLiteral {
180 composite_source: None,
181 value,
182 ty: layout.declared_type.catalog_name(),
183 bound_type: Some(layout.declared_type),
184 parameter_index: None,
185 }));
186 }
187 }
188 Ok(None)
189 }
190
191 fn analyze_body(
192 &self,
193 function: &super::SQLUserFunction,
194 definition: &CreateFunction,
195 ) -> Result<Option<(ScalarExpr, Vec<crate::SQLParam>)>, SQLError> {
196 let Some(mut plan) = self.body_plan(function)? else {
197 return Ok(None);
198 };
199 let parameters = routine_parameter_values(self.types, definition);
200 let names = matches!(definition.body, FunctionBody::Source(_))
201 .then(|| sql_body_parameter_scope(definition, ¶meters))
202 .transpose()?;
203 let snapshot = self.catalog.binding_snapshot()?;
204 let result = prepare_routine_body_inputs(
205 self.routines,
206 &mut plan,
207 ¶meters,
208 &snapshot.context(),
209 self.catalog,
210 names.as_ref(),
211 )?;
212 if let Some(names) = &names {
213 bind_routine_parameter_references(
214 self.routines,
215 &mut plan,
216 ¶meters,
217 &snapshot.context(),
218 names,
219 )?;
220 }
221 let UnifiedPlan::Query(query) = plan else {
222 return Ok(None);
223 };
224 if !query.ctes.is_empty() {
225 return Ok(None);
226 }
227 let RelationalPlan::QueryBlock(mut block) = query.root else {
228 return Ok(None);
229 };
230 if !simple_select(&block) {
231 return Ok(None);
232 }
233 let layout = sql_function_result_layout(self.types, definition, Some(&result))?;
234 if layout.kind == SQLFunctionResultKind::Tuple {
235 return Ok(None);
236 }
237 let mut expression = block.projections.remove(0).expr;
238 let actual = result
239 .column_types()
240 .and_then(|types| types.first())
241 .and_then(Option::as_ref);
242 if layout.kind == SQLFunctionResultKind::Void && actual != Some(&ColumnType::Void) {
243 return Ok(None);
244 }
245 if actual != Some(&layout.declared_type) {
246 expression = ScalarExpr::Cast {
247 implicit: true,
248 expr: Box::new(expression),
249 ty: layout.declared_type.catalog_name(),
250 };
251 }
252 Ok(Some((expression, parameters)))
253 }
254
255 pub fn materialize_call(
259 &self,
260 binding: &FunctionBinding,
261 arguments: &[ScalarExpr],
262 ) -> Result<Option<(FunctionBinding, Vec<ScalarExpr>)>, SQLError> {
263 let Some(function) = self.selected_function(binding) else {
264 return Ok(None);
265 };
266 let Some(invocation) = binding.invocation.as_deref() else {
267 return Ok(None);
268 };
269 let specialized = super::invocation::specialized_definition(&function.def, invocation)?;
270 let definition = specialized.as_ref().unwrap_or(&function.def);
271 let Some(mut arguments) = arguments::prepare(self, definition, invocation, arguments)?
272 else {
273 return Ok(None);
274 };
275 let mut selected = binding.clone();
276 let invocation = selected.invocation.as_mut().expect("selected invocation");
277 invocation.argument_positions = definition
278 .params
279 .iter()
280 .enumerate()
281 .filter_map(|(index, parameter)| {
282 super::body_parameters::is_sql_body_parameter(parameter).then_some(index)
283 })
284 .collect();
285 invocation.argument_targets = invocation
286 .argument_positions
287 .iter()
288 .map(|index| invocation.parameter_types[*index].clone())
289 .collect();
290 invocation.argument_sources = invocation
291 .argument_targets
292 .iter()
293 .cloned()
294 .map(Some)
295 .collect();
296 if let crate::ast::RoutineVariadicMode::Expanded { parameter_index } =
297 invocation.variadic_mode
298 {
299 invocation.variadic_mode =
300 crate::ast::RoutineVariadicMode::Explicit { parameter_index };
301 }
302 if let crate::ast::RoutineVariadicMode::Explicit { parameter_index } =
303 invocation.variadic_mode
304 {
305 let position = invocation
306 .argument_positions
307 .iter()
308 .position(|index| *index == parameter_index)
309 .ok_or_else(|| SQLError::Internal("variadic call lost its argument".into()))?;
310 let value = std::mem::replace(
311 &mut arguments[position],
312 ScalarExpr::Literal(uqa_core::Value::Null),
313 );
314 let marker =
315 FunctionBinding::dispatched(crate::ast::FunctionDispatch::VariadicArgument);
316 arguments[position] = ScalarExpr::Func {
317 name: marker.name.clone(),
318 binding: Some(marker),
319 args: vec![value],
320 distinct: false,
321 order_by: Vec::new(),
322 filter: None,
323 order_syntax: crate::ast::FunctionCallSyntax::Ordinary,
324 };
325 }
326 Ok(Some((selected, arguments)))
327 }
328
329 fn selected_function(
330 &self,
331 binding: &FunctionBinding,
332 ) -> Option<std::sync::Arc<super::SQLUserFunction>> {
333 if binding.builtin || binding.resolution_error.is_some() {
334 return None;
335 }
336 let identity = binding.object_id?;
337 self.routines
338 .lookup_bound_sql_functions_by_binding(binding)?
339 .into_iter()
340 .find(|function| {
341 !function.def.is_procedure
342 && function.def.object_id == Some(identity)
343 && super::routine_signature_types(&function.def) == binding.argument_types
344 })
345 }
346
347 pub fn requires_replanning(&self, plan: &UnifiedPlan) -> bool {
350 let mut required = false;
351 plan.visit_scalar_expressions(&mut |root| {
352 root.visit(&mut |expression| {
353 let ScalarExpr::Func {
354 binding: Some(binding),
355 ..
356 } = expression
357 else {
358 return;
359 };
360 if binding.builtin {
361 return;
362 }
363 required |= self
364 .routines
365 .lookup_bound_sql_functions_by_binding(binding)
366 .is_some_and(|functions| {
367 functions.iter().any(|function| {
368 function.def.object_id == binding.object_id
369 && super::routine_signature_types(&function.def)
370 == binding.argument_types
371 && (eligible_definition(&function.def)
372 || function.def.volatility
373 == crate::ast::FunctionVolatility::Immutable)
374 })
375 });
376 });
377 });
378 required
379 }
380
381 fn body_plan(
382 &self,
383 function: &super::SQLUserFunction,
384 ) -> Result<Option<UnifiedPlan>, SQLError> {
385 match &function.body {
386 RoutineBody::Bound(body) => match body.as_ref() {
387 CompiledFunctionBody::SQL(plans) if plans.len() == 1 => Ok(Some(plans[0].clone())),
388 _ => Ok(None),
389 },
390 RoutineBody::Source => {
391 let FunctionBody::Source(source) = &function.def.body else {
392 return Err(SQLError::Internal(
393 "source routine has no SQL source".into(),
394 ));
395 };
396 super::compilation::with_parser_context(self.parsers, || {
400 let statements = crate::parse_statements(source)?;
401 let [statement] = statements.as_slice() else {
402 return Ok(None);
403 };
404 Ok(Some(UnifiedPlan::lower_with(
405 statement.compile()?,
406 &|name: &str| self.catalog.has_registered_aggregate_function(name),
407 )))
408 })
409 }
410 }
411 }
412}
413
414fn eligible_definition(definition: &CreateFunction) -> bool {
415 definition.language == "sql"
416 && !definition.is_procedure
417 && !definition.security.security_definer
418 && !definition.returns_set()
419 && definition.config.is_empty()
420 && definition.output_params().len() <= 1
421 && !matches!(&definition.returns, FunctionReturns::Scalar { type_name }
422 if crate::type_resolution::canonical_routine_type_name(type_name) == "record")
423}
424
425fn simple_select(block: &QueryBlockPlan) -> bool {
426 matches!(block.compute, ComputePlan::Project)
427 && block.from.is_none()
428 && block.r#where.is_none()
429 && block.group_by.is_empty()
430 && block.grouping_sets.is_empty()
431 && block.having.is_none()
432 && block.windows.is_empty()
433 && !block.distinct
434 && block.distinct_on.is_empty()
435 && block.order_by.is_empty()
436 && block.limit.is_none()
437 && block.offset.is_none()
438 && block.locking.is_empty()
439 && block.subqueries.is_empty()
440 && block.projections.len() == 1
441}
442
443fn is_constant(expression: &ScalarExpr) -> bool {
444 matches!(
445 expression,
446 ScalarExpr::Literal(_) | ScalarExpr::TypedLiteral { .. }
447 )
448}
449
450fn is_null_constant(expression: &ScalarExpr) -> bool {
451 matches!(
452 expression,
453 ScalarExpr::Literal(uqa_core::Value::Null)
454 | ScalarExpr::TypedLiteral {
455 value: uqa_core::Value::Null,
456 ..
457 }
458 )
459}