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 collation_oid: None,
160 composite_source: None,
161 value: uqa_core::Value::Null,
162 ty: layout.declared_type.catalog_name(),
163 bound_type: Some(layout.declared_type),
164 parameter_index: None,
165 }));
166 }
167 if definition.volatility == crate::ast::FunctionVolatility::Immutable
168 && arguments.iter().all(is_constant)
169 {
170 let call = ScalarExpr::Func {
171 name: binding.name.clone(),
172 binding: Some(binding.clone()),
173 args: arguments_for_evaluation.to_vec(),
174 distinct: false,
175 order_by: Vec::new(),
176 order_syntax: crate::ast::FunctionCallSyntax::Ordinary,
177 filter: None,
178 };
179 let value = self.expressions.evaluate_constant_routine(&call)?;
180 return Ok(Some(ScalarExpr::TypedLiteral {
181 collation_oid: None,
182 composite_source: None,
183 value,
184 ty: layout.declared_type.catalog_name(),
185 bound_type: Some(layout.declared_type),
186 parameter_index: None,
187 }));
188 }
189 }
190 Ok(None)
191 }
192
193 fn analyze_body(
194 &self,
195 function: &super::SQLUserFunction,
196 definition: &CreateFunction,
197 ) -> Result<Option<(ScalarExpr, Vec<crate::SQLParam>)>, SQLError> {
198 let Some(mut plan) = self.body_plan(function)? else {
199 return Ok(None);
200 };
201 let parameters = routine_parameter_values(self.types, definition);
202 let names = matches!(definition.body, FunctionBody::Source(_))
203 .then(|| sql_body_parameter_scope(definition, ¶meters))
204 .transpose()?;
205 let snapshot = self.catalog.binding_snapshot()?;
206 let result = prepare_routine_body_inputs(
207 self.routines,
208 &mut plan,
209 ¶meters,
210 &snapshot.context(),
211 self.catalog,
212 names.as_ref(),
213 )?;
214 if let Some(names) = &names {
215 bind_routine_parameter_references(
216 self.routines,
217 &mut plan,
218 ¶meters,
219 &snapshot.context(),
220 names,
221 )?;
222 }
223 let UnifiedPlan::Query(query) = plan else {
224 return Ok(None);
225 };
226 if !query.ctes.is_empty() {
227 return Ok(None);
228 }
229 let RelationalPlan::QueryBlock(mut block) = query.root else {
230 return Ok(None);
231 };
232 if !simple_select(&block) {
233 return Ok(None);
234 }
235 let layout = sql_function_result_layout(self.types, definition, Some(&result))?;
236 if layout.kind == SQLFunctionResultKind::Tuple {
237 return Ok(None);
238 }
239 let mut expression = block.projections.remove(0).expr;
240 let actual = result
241 .column_types()
242 .and_then(|types| types.first())
243 .and_then(Option::as_ref);
244 if layout.kind == SQLFunctionResultKind::Void && actual != Some(&ColumnType::Void) {
245 return Ok(None);
246 }
247 if actual != Some(&layout.declared_type) {
248 expression = ScalarExpr::Cast {
249 implicit: true,
250 expr: Box::new(expression),
251 ty: layout.declared_type.catalog_name(),
252 };
253 }
254 Ok(Some((expression, parameters)))
255 }
256
257 pub fn materialize_call(
261 &self,
262 binding: &FunctionBinding,
263 arguments: &[ScalarExpr],
264 ) -> Result<Option<(FunctionBinding, Vec<ScalarExpr>)>, SQLError> {
265 let Some(function) = self.selected_function(binding) else {
266 return Ok(None);
267 };
268 let Some(invocation) = binding.invocation.as_deref() else {
269 return Ok(None);
270 };
271 let specialized = super::invocation::specialized_definition(&function.def, invocation)?;
272 let definition = specialized.as_ref().unwrap_or(&function.def);
273 let Some(mut arguments) = arguments::prepare(self, definition, invocation, arguments)?
274 else {
275 return Ok(None);
276 };
277 let mut selected = binding.clone();
278 let invocation = selected.invocation.as_mut().expect("selected invocation");
279 invocation.argument_positions = definition
280 .params
281 .iter()
282 .enumerate()
283 .filter_map(|(index, parameter)| {
284 super::body_parameters::is_sql_body_parameter(parameter).then_some(index)
285 })
286 .collect();
287 invocation.argument_targets = invocation
288 .argument_positions
289 .iter()
290 .map(|index| invocation.parameter_types[*index].clone())
291 .collect();
292 invocation.argument_sources = invocation
293 .argument_targets
294 .iter()
295 .cloned()
296 .map(Some)
297 .collect();
298 if let crate::ast::RoutineVariadicMode::Expanded { parameter_index } =
299 invocation.variadic_mode
300 {
301 invocation.variadic_mode =
302 crate::ast::RoutineVariadicMode::Explicit { parameter_index };
303 }
304 if let crate::ast::RoutineVariadicMode::Explicit { parameter_index } =
305 invocation.variadic_mode
306 {
307 let position = invocation
308 .argument_positions
309 .iter()
310 .position(|index| *index == parameter_index)
311 .ok_or_else(|| SQLError::Internal("variadic call lost its argument".into()))?;
312 let value = std::mem::replace(
313 &mut arguments[position],
314 ScalarExpr::Literal(uqa_core::Value::Null),
315 );
316 let marker =
317 FunctionBinding::dispatched(crate::ast::FunctionDispatch::VariadicArgument);
318 arguments[position] = ScalarExpr::Func {
319 name: marker.name.clone(),
320 binding: Some(marker),
321 args: vec![value],
322 distinct: false,
323 order_by: Vec::new(),
324 filter: None,
325 order_syntax: crate::ast::FunctionCallSyntax::Ordinary,
326 };
327 }
328 Ok(Some((selected, arguments)))
329 }
330
331 fn selected_function(
332 &self,
333 binding: &FunctionBinding,
334 ) -> Option<std::sync::Arc<super::SQLUserFunction>> {
335 if binding.builtin || binding.resolution_error.is_some() {
336 return None;
337 }
338 let identity = binding.object_id?;
339 self.routines
340 .lookup_bound_sql_functions_by_binding(binding)?
341 .into_iter()
342 .find(|function| {
343 !function.def.is_procedure
344 && function.def.object_id == Some(identity)
345 && super::routine_signature_types(&function.def) == binding.argument_types
346 })
347 }
348
349 pub fn requires_replanning(&self, plan: &UnifiedPlan) -> bool {
352 let mut required = false;
353 plan.visit_scalar_expressions(&mut |root| {
354 root.visit(&mut |expression| {
355 let ScalarExpr::Func {
356 binding: Some(binding),
357 ..
358 } = expression
359 else {
360 return;
361 };
362 if binding.builtin {
363 return;
364 }
365 required |= self
366 .routines
367 .lookup_bound_sql_functions_by_binding(binding)
368 .is_some_and(|functions| {
369 functions.iter().any(|function| {
370 function.def.object_id == binding.object_id
371 && super::routine_signature_types(&function.def)
372 == binding.argument_types
373 && (eligible_definition(&function.def)
374 || function.def.volatility
375 == crate::ast::FunctionVolatility::Immutable)
376 })
377 });
378 });
379 });
380 required
381 }
382
383 fn body_plan(
384 &self,
385 function: &super::SQLUserFunction,
386 ) -> Result<Option<UnifiedPlan>, SQLError> {
387 match &function.body {
388 RoutineBody::Bound(body) => match body.as_ref() {
389 CompiledFunctionBody::SQL(plans) if plans.len() == 1 => Ok(Some(plans[0].clone())),
390 _ => Ok(None),
391 },
392 RoutineBody::Source => {
393 let FunctionBody::Source(source) = &function.def.body else {
394 return Err(SQLError::Internal(
395 "source routine has no SQL source".into(),
396 ));
397 };
398 super::compilation::with_parser_context(self.parsers, || {
402 let statements = crate::parse_statements(source)?;
403 let [statement] = statements.as_slice() else {
404 return Ok(None);
405 };
406 Ok(Some(UnifiedPlan::lower_with(
407 statement.compile()?,
408 &|name: &str| self.catalog.has_registered_aggregate_function(name),
409 )))
410 })
411 }
412 }
413 }
414}
415
416fn eligible_definition(definition: &CreateFunction) -> bool {
417 definition.language == "sql"
418 && !definition.is_procedure
419 && !definition.security.security_definer
420 && !definition.returns_set()
421 && definition.config.is_empty()
422 && definition.output_params().len() <= 1
423 && !matches!(&definition.returns, FunctionReturns::Scalar { type_name }
424 if crate::type_resolution::canonical_routine_type_name(type_name) == "record")
425}
426
427fn simple_select(block: &QueryBlockPlan) -> bool {
428 matches!(block.compute, ComputePlan::Project)
429 && block.from.is_none()
430 && block.r#where.is_none()
431 && block.group_by.is_empty()
432 && block.grouping_sets.is_empty()
433 && block.having.is_none()
434 && block.windows.is_empty()
435 && !block.distinct
436 && block.distinct_on.is_empty()
437 && block.order_by.is_empty()
438 && block.limit.is_none()
439 && block.offset.is_none()
440 && block.locking.is_empty()
441 && block.subqueries.is_empty()
442 && block.projections.len() == 1
443}
444
445fn is_constant(expression: &ScalarExpr) -> bool {
446 matches!(
447 expression,
448 ScalarExpr::Literal(_) | ScalarExpr::TypedLiteral { .. }
449 )
450}
451
452fn is_null_constant(expression: &ScalarExpr) -> bool {
453 matches!(
454 expression,
455 ScalarExpr::Literal(uqa_core::Value::Null)
456 | ScalarExpr::TypedLiteral {
457 value: uqa_core::Value::Null,
458 ..
459 }
460 )
461}