Skip to main content

uqa_sql/expr/call_dispatch/
production.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Evaluated arguments for validated generated built-ins keep their existing payload owner through SQL dispatch.
8
9use super::named::builtin_named_args;
10use crate::{
11    ast::FunctionBinding,
12    error::{Result, SQLError},
13};
14use uqa_core::{
15    memory::{MemoryReservation, Produced, ProductionControl, ProductionVec},
16    Value,
17};
18
19/// Execute a built-in selected by generated-column validation. This boundary has no session, host-function or query callbacks; general runtime dispatch remains with `eval_function_call`. Argument values arrive already admitted by their producers.
20pub fn eval_generated_function_call_with_control(
21    name: &str,
22    binding: Option<&FunctionBinding>,
23    arguments: Produced<Vec<(Option<String>, Value)>>,
24    control: &ProductionControl<'_>,
25) -> Result<Produced<Value>> {
26    if let Some(error) = binding.and_then(|binding| binding.resolution_error.as_ref()) {
27        return Err(error.sql_error());
28    }
29    control.check()?;
30    if binding.is_some_and(|binding| !binding.builtin) {
31        return Err(SQLError::Unsupported(
32            "bound user function requires a logical engine session".into(),
33        ));
34    }
35    if let Some((binding, dispatch)) =
36        binding.and_then(|binding| binding.dispatch.map(|dispatch| (binding, dispatch)))
37    {
38        if arguments.iter().any(|(name, _)| name.is_some()) {
39            return Err(SQLError::Internal(format!(
40                "bound {} expression retained a named argument",
41                dispatch.label(),
42            )));
43        }
44        let evaluated = MovedArguments::new(arguments, control)?;
45        return super::super::builtin::eval_dispatched_builtin_with_control(
46            binding,
47            dispatch,
48            &evaluated.values,
49            control,
50        );
51    }
52    let name = binding.map_or(name, |binding| binding.name.as_str());
53    let normalized =
54        super::super::call_arguments::normalized_function_name_with_control(name, control)?;
55    let name = normalized.as_ref();
56    let value = if arguments.iter().any(|(name, _)| name.is_some()) {
57        let Some(positional) = builtin_named_args(name, &arguments, control)? else {
58            return Err(super::super::diagnostics::unknown_function_error(
59                name, &arguments,
60            ));
61        };
62        super::super::scalar_dispatch::eval_generated_scalar_function(name, &positional, control)?
63    } else {
64        let evaluated = MovedArguments::new(arguments, control)?;
65        super::super::scalar_dispatch::eval_generated_scalar_function(
66            name,
67            &evaluated.values,
68            control,
69        )?
70    };
71    if matches!(*value, Value::Int(_) | Value::Float(_)) {
72        if let Some(binding) = binding {
73            if let Some(ty) = crate::fixed_builtin_return_type_with_control(binding, control)? {
74                if matches!(
75                    *ty,
76                    crate::ColumnType::SmallInteger
77                        | crate::ColumnType::Integer
78                        | crate::ColumnType::Real
79                ) {
80                    let name = ty.sql_name_with_control(control)?;
81                    return super::super::cast_value_from_with_control(
82                        &value, &name, None, control,
83                    );
84                }
85            }
86        }
87    }
88    Ok(value)
89}
90
91/// Moving positional values must not make a second payload copy. The new vector's complete capacity is admitted by Core before movement; the original argument buffer and payload lease remain with this guard until dispatch finishes. Both vectors precede the shared lease on every failure path.
92struct MovedArguments {
93    values: Vec<Value>,
94    remaining: std::vec::IntoIter<(Option<String>, Value)>,
95    _memory: Option<MemoryReservation>,
96}
97
98impl MovedArguments {
99    fn new(
100        arguments: Produced<Vec<(Option<String>, Value)>>,
101        control: &ProductionControl<'_>,
102    ) -> Result<Self> {
103        let mut values = ProductionVec::new(*control);
104        values.reserve(arguments.len())?;
105        let (values, values_memory) = values.finish()?.into_parts();
106        let (arguments, argument_memory) = arguments.into_parts();
107        let mut output = Self {
108            values,
109            remaining: arguments.into_iter(),
110            _memory: control.combine(values_memory, argument_memory),
111        };
112        for (_, value) in output.remaining.by_ref() {
113            control.check()?;
114            output.values.push(value);
115        }
116        control.check()?;
117        Ok(output)
118    }
119}
120
121#[cfg(test)]
122mod tests;