Skip to main content

uqa_sql/expr/
numeric_operator.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Scalar numeric operators share typed SQL overloads and existing arithmetic kernels.
8
9use crate::ast::{ColumnType, Expr, FunctionBinding, NumericOperator};
10use crate::type_resolution::numeric_operator_types_with_control;
11use crate::{RowSchema, SQLError};
12use uqa_core::{
13    memory::{Produced, ProductionControl, ProductionVec},
14    Value,
15};
16
17use super::{eval, EvalContext, Result};
18
19#[cfg(test)]
20mod tests;
21
22pub fn eval_numeric_operator(
23    operator: NumericOperator,
24    arguments: &[Value],
25    types: &[Option<ColumnType>],
26) -> Result<Value> {
27    eval_numeric_operator_with_control(
28        operator,
29        arguments,
30        types,
31        &ProductionControl::uncontrolled(),
32    )
33    .map(|value| {
34        value
35            .into_uncontrolled()
36            .expect("ordinary numeric result has no reservation")
37    })
38}
39
40/// Evaluate the existing selected numeric overload while casts, type names and intermediate values remain owned by the original allowance.
41pub fn eval_numeric_operator_with_control(
42    operator: NumericOperator,
43    arguments: &[Value],
44    types: &[Option<ColumnType>],
45    control: &ProductionControl<'_>,
46) -> Result<Produced<Value>> {
47    control.check()?;
48    if arguments.len() != types.len() {
49        return Err(SQLError::Internal(
50            "operator operand/type count differs".into(),
51        ));
52    }
53    let selected = numeric_operator_types_with_control(operator, types, control)?;
54    let mut converted = ProductionVec::new(*control);
55    converted.reserve(arguments.len())?;
56    for ((value, source), target) in arguments.iter().zip(types).zip(&selected.arguments) {
57        let target = target.sql_name_with_control(control)?;
58        let source = source
59            .as_ref()
60            .map(|ty| ty.sql_name_with_control(control))
61            .transpose()?;
62        converted.push_produced(super::cast_value_from_with_control(
63            value,
64            &target,
65            source.as_deref().map(String::as_str),
66            control,
67        )?)?;
68    }
69    let arguments = converted.finish()?;
70    if arguments.iter().any(|value| matches!(value, Value::Null)) {
71        return Ok(control.finish(Value::Null, control.empty_reservation())?);
72    }
73    let value = if operator == NumericOperator::Plus {
74        control.copy_value(&arguments[0])?
75    } else {
76        let name = match operator {
77            NumericOperator::Modulo => "mod",
78            NumericOperator::Power => "power",
79            NumericOperator::SquareRoot => "sqrt",
80            NumericOperator::CubeRoot => "cbrt",
81            NumericOperator::Absolute => "abs",
82            NumericOperator::Plus => unreachable!("unary plus copied its selected operand"),
83        };
84        super::scalar_core::eval_core_functions_with_control(name, &arguments, control)
85            .or_else(|| {
86                super::scalar_math::eval_math_functions_with_control(name, &arguments, control)
87            })
88            .expect("numeric syntax selects an existing numeric function")?
89    };
90    // The shared carrier does not retain int2/int4 width; the selected result cast enforces absolute-value overflow and real width.
91    let target = selected.result.sql_name_with_control(control)?;
92    super::cast_value_from_with_control(&value, &target, None, control)
93}
94
95pub(super) fn eval_ast_operator(
96    operator: NumericOperator,
97    binding: &FunctionBinding,
98    arguments: &[Expr],
99    context: &EvalContext<'_>,
100) -> Result<Value> {
101    let values = arguments
102        .iter()
103        .map(|arg| eval(arg, context))
104        .collect::<Result<Vec<_>>>()?;
105    let types = if binding.argument_types.is_empty() {
106        arguments
107            .iter()
108            .zip(&values)
109            .map(|(arg, value)| {
110                let scalar = crate::plan::ExpressionPlan::lower(arg.clone()).scalar;
111                let ty = crate::common_context_expression_type(
112                    &scalar,
113                    &RowSchema::default(),
114                    context.params,
115                    None,
116                )?;
117                Ok(ty.or_else(|| {
118                    (!matches!(arg, Expr::Literal(Value::Str(_) | Value::Null)))
119                        .then(|| crate::type_resolution::value_type(value))
120                        .flatten()
121                }))
122            })
123            .collect::<Result<Vec<_>>>()?
124    } else {
125        binding
126            .argument_types
127            .iter()
128            .map(|name| ColumnType::from_sql_name(name).map(Some))
129            .collect::<Result<Vec<_>>>()?
130    };
131    eval_numeric_operator(operator, &values, &types)
132}
133
134pub(super) fn eval_bound_operator_with_control(
135    operator: NumericOperator,
136    binding: &FunctionBinding,
137    arguments: &[Value],
138    control: &ProductionControl<'_>,
139) -> Result<Produced<Value>> {
140    control.check()?;
141    let mut types = ProductionVec::new(*control);
142    if binding.argument_types.is_empty() {
143        types.reserve(arguments.len())?;
144        for value in arguments {
145            let ty = crate::type_resolution::value_type_with_control(value, control)?;
146            let (ty, memory) = ty.map_or_else(
147                || (None, control.empty_reservation()),
148                |ty| {
149                    let (ty, memory) = ty.into_parts();
150                    (Some(ty), memory)
151                },
152            );
153            types.push_produced(control.finish(ty, memory)?)?;
154        }
155    } else {
156        types.reserve(binding.argument_types.len())?;
157        for name in &binding.argument_types {
158            let (ty, memory) = ColumnType::from_sql_name_with_control(name, control)?.into_parts();
159            types.push_produced(control.finish(Some(ty), memory)?)?;
160        }
161    }
162    let types = types.finish()?;
163    eval_numeric_operator_with_control(operator, arguments, &types, control)
164}
165
166#[cfg(test)]
167mod production_tests;