1use uqa_core::{ArrayValue, Value};
10
11use crate::ast::{BinaryOp, Expr};
12use crate::error::{Result, SQLError};
13
14use super::binary::{eval_binary, eval_comparison_truth_with_engine, truthy};
15use super::builtin::eval_bound_builtin_function_call;
16use super::call_arguments::evaluate_call_args;
17use super::call_dispatch::eval_function_call;
18use super::casting::negate_value;
19use super::context::{cast_value_with_type_resolution, EvalContext};
20
21#[expect(
23 clippy::too_many_lines,
24 reason = "builtin dispatch preserves arity, NULL, and error precedence"
25)]
26pub fn eval(expr: &Expr, ctx: &EvalContext<'_>) -> Result<Value> {
27 match expr {
28 Expr::Default => Err(SQLError::Internal(
29 "DEFAULT reached scalar expression evaluation without a mutation target".into(),
30 )),
31 Expr::Literal(v) => Ok(v.clone()),
32 Expr::TypedLiteral { value, ty, .. } => {
33 Ok(super::composites::literal::evaluate_with_control(
34 value,
35 ty,
36 ctx.engine,
37 &uqa_core::memory::ProductionControl::uncontrolled(),
38 )?
39 .into_uncontrolled()
40 .expect("ordinary constant result"))
41 }
42 Expr::Param(i) => match i.checked_sub(1).and_then(|index| ctx.params.get(index)) {
43 Some(parameter) => parameter.to_value(),
44 None => Err(SQLError::MissingParam(*i)),
45 },
46 Expr::Column(name) => {
47 if ctx.row_lookup()?.column_is_ambiguous(name) {
51 return Err(SQLError::AmbiguousColumn(name.clone()));
52 }
53 Ok(ctx
54 .row_lookup()?
55 .column(name)
56 .cloned()
57 .unwrap_or(Value::Null))
58 }
59 Expr::QualifiedColumn { qualifier, column } => {
60 if ctx
61 .row_lookup()?
62 .qualified_column_is_ambiguous(qualifier, column)
63 {
64 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
65 }
66 Ok(ctx
67 .row_lookup()?
68 .qualified_column(qualifier, column)
69 .cloned()
70 .unwrap_or(Value::Null))
71 }
72 Expr::InternalColumn(column) => ctx
73 .row_lookup()?
74 .internal_column(*column)
75 .cloned()
76 .ok_or_else(|| {
77 SQLError::Internal(format!(
78 "internal relation attribute {column:?} is unavailable"
79 ))
80 }),
81 Expr::Array(elements) => {
82 let scalar = crate::plan::ExpressionPlan::lower(Expr::Array(elements.clone())).scalar;
83 let crate::ScalarExpr::Array(items) = scalar else {
84 unreachable!("array lowering")
85 };
86 let element_oid = crate::type_resolution::scalar_array_element_oid_with_control(
87 &items,
88 &crate::RowSchema::default(),
89 ctx.params,
90 ctx.engine,
91 &uqa_core::memory::ProductionControl::uncontrolled(),
92 )?;
93 let mut out = Vec::with_capacity(elements.len());
94 for e in elements {
95 out.push(eval(e, ctx)?);
96 }
97 ArrayValue::try_new(out)
98 .map(|array| Value::Array(array.with_element_type_oid(element_oid)))
99 .ok_or_else(|| {
100 SQLError::TypeMismatch(
101 "multidimensional arrays must have matching dimensions".into(),
102 )
103 })
104 }
105 Expr::CompositeRow { items, binding } => {
106 let control = uqa_core::memory::ProductionControl::uncontrolled();
107 Ok(super::composites::constructor::evaluate_with_control(
108 binding,
109 items.len(),
110 ctx.engine,
111 &control,
112 |index| {
113 control
114 .finish(eval(&items[index], ctx)?, None)
115 .map_err(Into::into)
116 },
117 )?
118 .into_uncontrolled()
119 .expect("ordinary composite constructor"))
120 }
121 Expr::Row(elements) => {
122 let mut out = Vec::with_capacity(elements.len());
123 let mut fields = Vec::with_capacity(elements.len());
124 for element in elements {
125 let scalar = crate::plan::ExpressionPlan::lower(element.clone()).scalar;
126 if let Some(field) = crate::type_resolution::scalar_record_field_type_with_control(
127 &scalar,
128 &crate::RowSchema::default(),
129 ctx.params,
130 ctx.engine,
131 &uqa_core::memory::ProductionControl::uncontrolled(),
132 )? {
133 fields.push(field);
134 }
135 out.push(eval(element, ctx)?);
136 }
137 let row = if fields.len() == out.len() {
138 uqa_core::RowValue::typed(out, fields)?
139 } else {
140 uqa_core::RowValue::new(out)
141 };
142 Ok(Value::Row(row))
143 }
144 Expr::Star | Expr::QualifiedStar(_) => {
145 Err(SQLError::Internal("`*` cannot be evaluated".into()))
146 }
147 Expr::Func {
148 name,
149 binding,
150 args,
151 ..
152 } => {
153 if let Some(binding) = binding {
154 if let Some(error) = &binding.resolution_error {
155 return Err(error.sql_error());
156 }
157 if let Some(clause) = crate::catalog::collations::expression::ast_clause(expr)? {
158 return eval(clause.argument, ctx);
160 }
161 if let Some(hook) = ctx.engine {
162 hook.require_builtin_execute(binding)?;
163 }
164 if let Some(crate::ast::FunctionDispatch::NumericOperator(operator)) =
165 binding.dispatch
166 {
167 return super::numeric_operator::eval_ast_operator(
168 operator, binding, args, ctx,
169 );
170 }
171 }
172 if name.eq_ignore_ascii_case("coalesce")
173 && binding.as_ref().is_none_or(|binding| binding.builtin)
174 {
175 for argument in args {
176 let value = eval(argument, ctx)?;
177 if !matches!(value, Value::Null) {
178 return Ok(value);
179 }
180 }
181 return Ok(Value::Null);
182 }
183 if binding.as_ref().is_some_and(|binding| {
184 binding.builtin
185 && binding.dispatch == Some(crate::ast::FunctionDispatch::BetweenSymmetric)
186 }) {
187 let [value, low, high] = args.as_slice() else {
188 return Err(SQLError::TypeMismatch(
189 "BETWEEN SYMMETRIC takes 3 args".into(),
190 ));
191 };
192 let forward = eval_between(value, low, high, ctx, 0)?;
193 if forward == Value::Bool(true) {
194 return Ok(forward);
195 }
196 let backward = eval_between(value, high, low, ctx, 2)?;
197 return Ok(match (forward, backward) {
198 (_, Value::Bool(true)) => Value::Bool(true),
199 (Value::Null, _) | (_, Value::Null) => Value::Null,
200 _ => Value::Bool(false),
201 });
202 }
203 if let Some(access) = binding
204 .as_ref()
205 .and_then(|binding| binding.dispatch)
206 .and_then(|dispatch| {
207 crate::ast::ArrayAccess::from_dispatch(dispatch, args.len(), |index| {
208 matches!(args[index], Expr::Literal(Value::Null))
209 })
210 })
211 {
212 let control = uqa_core::memory::ProductionControl::uncontrolled();
213 return Ok(
214 super::eval_array_access_with_control(access?, &control, |index| {
215 Ok(control.finish(eval(&args[index], ctx)?, None)?)
216 })?
217 .into_uncontrolled()
218 .expect("ordinary array access"),
219 );
220 }
221 let call_args = evaluate_call_args(args, ctx)?;
222 if let Some(binding) = binding {
223 if binding.builtin {
224 return eval_bound_builtin_function_call(binding, call_args, ctx);
225 }
226 let engine = ctx.engine.ok_or_else(|| {
227 SQLError::Unsupported(
228 "bound user function requires a logical engine session".into(),
229 )
230 })?;
231 engine
232 .call_bound_user_function(binding, &call_args)
233 .unwrap_or_else(|| Err(SQLError::UnknownFunction(binding.name.clone())))
234 } else {
235 eval_function_call(name, call_args, ctx)
236 }
237 }
238 Expr::WindowCall { name, .. } => Err(SQLError::Unsupported(format!(
239 "window function `{name}` must be evaluated by the window-aware executor"
240 ))),
241 Expr::Case {
242 base,
243 when,
244 else_branch,
245 } => {
246 let base_value = match base {
247 Some(b) => Some(eval(b, ctx)?),
248 None => None,
249 };
250 for (cond, result) in when {
251 let matched = match &base_value {
252 Some(bv) => {
253 super::eval_value_comparison_truth_with_engine(
254 BinaryOp::Equal,
255 bv,
256 &eval(cond, ctx)?,
257 &uqa_core::memory::ProductionControl::uncontrolled(),
258 ctx.engine,
259 ctx.enum_comparison_state_at(0),
260 )? == Some(true)
261 }
262 None => truthy(&eval(cond, ctx)?),
263 };
264 if matched {
265 return eval(result, ctx);
266 }
267 }
268 match else_branch {
269 Some(e) => eval(e, ctx),
270 None => Ok(Value::Null),
271 }
272 }
273 Expr::Cast { expr, ty, .. } => {
274 let source_ty = explicit_expr_type(expr);
275 let v = eval(expr, ctx)?;
276 cast_value_with_type_resolution(&v, source_ty, ty, ctx.engine)
277 }
278 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
279 Err(SQLError::Unsupported(
280 "query-valued expressions must be lowered to physical ScalarExpr/QueryPlan slots"
281 .into(),
282 ))
283 }
284 Expr::Binary { op, lhs, rhs } => eval_binary(*op, lhs, rhs, ctx),
285 Expr::UnaryMinus(inner) => {
286 let source_ty = explicit_expr_type(inner);
287 let value = eval(inner, ctx)?;
288 negate_value(&value, source_ty)
289 }
290 Expr::Not(inner) => {
291 let v = eval(inner, ctx)?;
293 if matches!(v, Value::Null) {
294 return Ok(Value::Null);
295 }
296 Ok(Value::Bool(!truthy(&v)))
297 }
298 Expr::And(items) => {
299 let mut saw_null = false;
301 for item in items {
302 let v = eval(item, ctx)?;
303 if matches!(v, Value::Null) {
304 saw_null = true;
305 } else if !truthy(&v) {
306 return Ok(Value::Bool(false));
307 }
308 }
309 if saw_null {
310 return Ok(Value::Null);
311 }
312 Ok(Value::Bool(true))
313 }
314 Expr::Or(items) => {
315 let mut saw_null = false;
317 for item in items {
318 let v = eval(item, ctx)?;
319 if matches!(v, Value::Null) {
320 saw_null = true;
321 } else if truthy(&v) {
322 return Ok(Value::Bool(true));
323 }
324 }
325 if saw_null {
326 return Ok(Value::Null);
327 }
328 Ok(Value::Bool(false))
329 }
330 Expr::IsNull { expr, negated } => {
331 let v = eval(expr, ctx)?;
332 Ok(Value::Bool(uqa_core::sql_null_test(Some(&v), *negated)))
333 }
334 Expr::Between { expr, low, high } => eval_between(expr, low, high, ctx, 0),
335 Expr::InList {
336 expr,
337 list,
338 negated,
339 } => {
340 let v = eval(expr, ctx)?;
343 let mut saw_null = matches!(v, Value::Null);
344 for item in list {
345 let candidate = eval(item, ctx)?;
346 match compare(BinaryOp::Equal, &v, &candidate, ctx, 0)? {
347 Some(true) => return Ok(Value::Bool(!*negated)),
348 Some(false) => {}
349 None => saw_null = true,
350 }
351 }
352 if saw_null {
353 return Ok(Value::Null);
354 }
355 Ok(Value::Bool(*negated))
356 }
357 }
358}
359
360fn explicit_expr_type(expr: &Expr) -> Option<&str> {
361 match expr {
362 Expr::Cast { ty, .. }
363 | Expr::TypedLiteral { ty, .. }
364 | Expr::CompositeRow {
365 binding: crate::ast::CompositeRowBinding { ty, .. },
366 ..
367 } => Some(ty),
368 Expr::Literal(Value::Int(value)) if i32::try_from(*value).is_ok() => Some("integer"),
369 Expr::Literal(Value::Int(_)) => Some("bigint"),
370 Expr::Literal(Value::Bytes(_)) => Some("bytea"),
371 _ => None,
372 }
373}
374
375fn eval_between(
378 expression: &Expr,
379 low: &Expr,
380 high: &Expr,
381 context: &EvalContext<'_>,
382 slot: usize,
383) -> Result<Value> {
384 let ge = compare(
385 BinaryOp::GreaterEqual,
386 &eval(expression, context)?,
387 &eval(low, context)?,
388 context,
389 slot,
390 )?;
391 if ge == Some(false) {
392 return Ok(Value::Bool(false));
393 }
394 let le = compare(
395 BinaryOp::LessEqual,
396 &eval(expression, context)?,
397 &eval(high, context)?,
398 context,
399 slot + 1,
400 )?;
401 Ok(match (ge, le) {
402 (_, Some(false)) => Value::Bool(false),
403 (Some(true), Some(true)) => Value::Bool(true),
404 _ => Value::Null,
405 })
406}
407
408fn compare(
409 op: BinaryOp,
410 left: &Value,
411 right: &Value,
412 context: &EvalContext<'_>,
413 slot: usize,
414) -> Result<Option<bool>> {
415 eval_comparison_truth_with_engine(
416 op,
417 left,
418 right,
419 &uqa_core::memory::ProductionControl::uncontrolled(),
420 context.engine,
421 context.enum_comparison_state_at(slot),
422 )
423}