1use uqa_core::{ArrayValue, Value};
10
11use crate::ast::{Expr, FunctionResolutionError};
12use crate::error::{Result, SQLError};
13use crate::params::SQLParam;
14
15use super::binary::{compare_nullable, eval_binary, truthy, values_equal, values_equal_nullable};
16use super::builtin::eval_bound_builtin_function_call;
17use super::call_arguments::evaluate_call_args;
18use super::call_dispatch::eval_function_call;
19use super::casting::negate_value;
20use super::context::{cast_value_with_type_resolution, EvalContext};
21
22#[expect(
24 clippy::too_many_lines,
25 reason = "builtin dispatch preserves arity, NULL, and error precedence"
26)]
27pub fn eval(expr: &Expr, ctx: &EvalContext<'_>) -> Result<Value> {
28 match expr {
29 Expr::Default => Err(SQLError::Internal(
30 "DEFAULT reached scalar expression evaluation without a mutation target".into(),
31 )),
32 Expr::Literal(v) | Expr::TypedLiteral { value: v, .. } => Ok(v.clone()),
33 Expr::Param(i) => match i.checked_sub(1).and_then(|index| ctx.params.get(index)) {
34 Some(SQLParam::Scalar(v) | SQLParam::TypedScalar { value: v, .. }) => Ok(v.clone()),
35 Some(SQLParam::Vector(v)) => Ok(Value::List(
36 v.iter().map(|x| Value::Float(f64::from(*x))).collect(),
37 )),
38 Some(SQLParam::Tensor(vectors)) => Ok(Value::List(
39 vectors
40 .iter()
41 .map(|vector| {
42 Value::List(vector.iter().map(|x| Value::Float(f64::from(*x))).collect())
43 })
44 .collect(),
45 )),
46 None => Err(SQLError::MissingParam(*i)),
47 },
48 Expr::Column(name) => {
49 if ctx.row_lookup()?.column_is_ambiguous(name) {
53 return Err(SQLError::AmbiguousColumn(name.clone()));
54 }
55 Ok(ctx
56 .row_lookup()?
57 .column(name)
58 .cloned()
59 .unwrap_or(Value::Null))
60 }
61 Expr::QualifiedColumn { qualifier, column } => {
62 if ctx
63 .row_lookup()?
64 .qualified_column_is_ambiguous(qualifier, column)
65 {
66 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
67 }
68 Ok(ctx
69 .row_lookup()?
70 .qualified_column(qualifier, column)
71 .cloned()
72 .unwrap_or(Value::Null))
73 }
74 Expr::InternalColumn(column) => ctx
75 .row_lookup()?
76 .internal_column(*column)
77 .cloned()
78 .ok_or_else(|| {
79 SQLError::Internal(format!(
80 "internal relation attribute {column:?} is unavailable"
81 ))
82 }),
83 Expr::Array(elements) => {
84 let mut out = Vec::with_capacity(elements.len());
85 for e in elements {
86 out.push(eval(e, ctx)?);
87 }
88 ArrayValue::try_new(out).map(Value::Array).ok_or_else(|| {
89 SQLError::TypeMismatch(
90 "multidimensional arrays must have matching dimensions".into(),
91 )
92 })
93 }
94 Expr::Row(elements) => {
95 let mut out = Vec::with_capacity(elements.len());
96 for element in elements {
97 out.push(eval(element, ctx)?);
98 }
99 Ok(Value::Row(out))
100 }
101 Expr::Star | Expr::QualifiedStar(_) => {
102 Err(SQLError::Internal("`*` cannot be evaluated".into()))
103 }
104 Expr::Func {
105 name,
106 binding,
107 args,
108 ..
109 } => {
110 if name.eq_ignore_ascii_case("coalesce")
111 && binding.as_ref().is_none_or(|binding| binding.builtin)
112 {
113 for argument in args {
114 let value = eval(argument, ctx)?;
115 if !matches!(value, Value::Null) {
116 return Ok(value);
117 }
118 }
119 return Ok(Value::Null);
120 }
121 let call_args = evaluate_call_args(args, ctx)?;
122 if let Some(binding) = binding {
123 if let Some(FunctionResolutionError::UndefinedFunction { signature }) =
124 binding.resolution_error.as_ref()
125 {
126 return Err(SQLError::Routine {
127 sqlstate: "42883".into(),
128 message: format!("function {signature} does not exist"),
129 });
130 }
131 if binding.builtin {
132 return eval_bound_builtin_function_call(binding, call_args, ctx);
133 }
134 let engine = ctx.engine.ok_or_else(|| {
135 SQLError::Unsupported(
136 "bound user function requires a logical engine session".into(),
137 )
138 })?;
139 engine
140 .call_bound_user_function(binding, &call_args)
141 .unwrap_or_else(|| Err(SQLError::UnknownFunction(binding.name.clone())))
142 } else {
143 eval_function_call(name, call_args, ctx)
144 }
145 }
146 Expr::WindowCall { name, .. } => Err(SQLError::Unsupported(format!(
147 "window function `{name}` must be evaluated by the window-aware executor"
148 ))),
149 Expr::Case {
150 base,
151 when,
152 else_branch,
153 } => {
154 let base_value = match base {
155 Some(b) => Some(eval(b, ctx)?),
156 None => None,
157 };
158 for (cond, result) in when {
159 let matched = match &base_value {
160 Some(bv) => values_equal(bv, &eval(cond, ctx)?),
161 None => truthy(&eval(cond, ctx)?),
162 };
163 if matched {
164 return eval(result, ctx);
165 }
166 }
167 match else_branch {
168 Some(e) => eval(e, ctx),
169 None => Ok(Value::Null),
170 }
171 }
172 Expr::Cast { expr, ty } => {
173 let source_ty = explicit_expr_type(expr);
174 let v = eval(expr, ctx)?;
175 cast_value_with_type_resolution(&v, source_ty, ty, ctx.engine)
176 }
177 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
178 Err(SQLError::Unsupported(
179 "query-valued expressions must be lowered to physical ScalarExpr/QueryPlan slots"
180 .into(),
181 ))
182 }
183 Expr::Binary { op, lhs, rhs } => eval_binary(*op, lhs, rhs, ctx),
184 Expr::UnaryMinus(inner) => {
185 let source_ty = explicit_expr_type(inner);
186 let value = eval(inner, ctx)?;
187 negate_value(&value, source_ty)
188 }
189 Expr::Not(inner) => {
190 let v = eval(inner, ctx)?;
192 if matches!(v, Value::Null) {
193 return Ok(Value::Null);
194 }
195 Ok(Value::Bool(!truthy(&v)))
196 }
197 Expr::And(items) => {
198 let mut saw_null = false;
200 for item in items {
201 let v = eval(item, ctx)?;
202 if matches!(v, Value::Null) {
203 saw_null = true;
204 } else if !truthy(&v) {
205 return Ok(Value::Bool(false));
206 }
207 }
208 if saw_null {
209 return Ok(Value::Null);
210 }
211 Ok(Value::Bool(true))
212 }
213 Expr::Or(items) => {
214 let mut saw_null = false;
216 for item in items {
217 let v = eval(item, ctx)?;
218 if matches!(v, Value::Null) {
219 saw_null = true;
220 } else if truthy(&v) {
221 return Ok(Value::Bool(true));
222 }
223 }
224 if saw_null {
225 return Ok(Value::Null);
226 }
227 Ok(Value::Bool(false))
228 }
229 Expr::IsNull { expr, negated } => {
230 let v = eval(expr, ctx)?;
231 let is_null = matches!(v, Value::Null);
232 Ok(Value::Bool(if *negated { !is_null } else { is_null }))
233 }
234 Expr::Between { expr, low, high } => {
235 let v = eval(expr, ctx)?;
236 let lo = eval(low, ctx)?;
237 let hi = eval(high, ctx)?;
238 eval_between(&v, &lo, &hi)
239 }
240 Expr::InList {
241 expr,
242 list,
243 negated,
244 } => {
245 let v = eval(expr, ctx)?;
248 let mut saw_null = matches!(v, Value::Null);
249 for item in list {
250 let candidate = eval(item, ctx)?;
251 match values_equal_nullable(&v, &candidate) {
252 Some(true) => return Ok(Value::Bool(!*negated)),
253 Some(false) => {}
254 None => saw_null = true,
255 }
256 }
257 if saw_null {
258 return Ok(Value::Null);
259 }
260 Ok(Value::Bool(*negated))
261 }
262 }
263}
264
265fn explicit_expr_type(expr: &Expr) -> Option<&str> {
266 match expr {
267 Expr::Cast { ty, .. } | Expr::TypedLiteral { ty, .. } => Some(ty),
268 Expr::Literal(Value::Int(value)) if i32::try_from(*value).is_ok() => Some("integer"),
269 Expr::Literal(Value::Int(_)) => Some("bigint"),
270 Expr::Literal(Value::Bytes(_)) => Some("bytea"),
271 _ => None,
272 }
273}
274
275pub(super) fn eval_between(v: &Value, lo: &Value, hi: &Value) -> Result<Value> {
278 let ge = compare_nullable(v, lo)?.map(|ord| ord.is_ge());
279 let le = compare_nullable(v, hi)?.map(|ord| ord.is_le());
280 Ok(match (ge, le) {
281 (Some(false), _) | (_, Some(false)) => Value::Bool(false),
282 (Some(true), Some(true)) => Value::Bool(true),
283 _ => Value::Null,
284 })
285}