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 mut out = Vec::with_capacity(elements.len());
83 for e in elements {
84 out.push(eval(e, ctx)?);
85 }
86 ArrayValue::try_new(out).map(Value::Array).ok_or_else(|| {
87 SQLError::TypeMismatch(
88 "multidimensional arrays must have matching dimensions".into(),
89 )
90 })
91 }
92 Expr::CompositeRow { items, binding } => {
93 let control = uqa_core::memory::ProductionControl::uncontrolled();
94 Ok(super::composites::constructor::evaluate_with_control(
95 binding,
96 items.len(),
97 ctx.engine,
98 &control,
99 |index| {
100 control
101 .finish(eval(&items[index], ctx)?, None)
102 .map_err(Into::into)
103 },
104 )?
105 .into_uncontrolled()
106 .expect("ordinary composite constructor"))
107 }
108 Expr::Row(elements) => {
109 let mut out = Vec::with_capacity(elements.len());
110 let mut fields = Vec::with_capacity(elements.len());
111 for element in elements {
112 let scalar = crate::plan::ExpressionPlan::lower(element.clone()).scalar;
113 if let Some(field) = crate::type_resolution::scalar_record_field_type_with_control(
114 &scalar,
115 &crate::RowSchema::default(),
116 ctx.params,
117 ctx.engine,
118 &uqa_core::memory::ProductionControl::uncontrolled(),
119 )? {
120 fields.push(field);
121 }
122 out.push(eval(element, ctx)?);
123 }
124 let row = if fields.len() == out.len() {
125 uqa_core::RowValue::typed(out, fields)?
126 } else {
127 uqa_core::RowValue::new(out)
128 };
129 Ok(Value::Row(row))
130 }
131 Expr::Star | Expr::QualifiedStar(_) => {
132 Err(SQLError::Internal("`*` cannot be evaluated".into()))
133 }
134 Expr::Func {
135 name,
136 binding,
137 args,
138 ..
139 } => {
140 if let Some(binding) = binding {
141 if let Some(error) = &binding.resolution_error {
142 return Err(error.sql_error());
143 }
144 if let Some(hook) = ctx.engine {
145 hook.require_builtin_execute(binding)?;
146 }
147 if let Some(crate::ast::FunctionDispatch::NumericOperator(operator)) =
148 binding.dispatch
149 {
150 return super::numeric_operator::eval_ast_operator(
151 operator, binding, args, ctx,
152 );
153 }
154 }
155 if name.eq_ignore_ascii_case("coalesce")
156 && binding.as_ref().is_none_or(|binding| binding.builtin)
157 {
158 for argument in args {
159 let value = eval(argument, ctx)?;
160 if !matches!(value, Value::Null) {
161 return Ok(value);
162 }
163 }
164 return Ok(Value::Null);
165 }
166 if binding.as_ref().is_some_and(|binding| {
167 binding.builtin
168 && binding.dispatch == Some(crate::ast::FunctionDispatch::BetweenSymmetric)
169 }) {
170 let [value, low, high] = args.as_slice() else {
171 return Err(SQLError::TypeMismatch(
172 "BETWEEN SYMMETRIC takes 3 args".into(),
173 ));
174 };
175 let forward = eval_between(value, low, high, ctx, 0)?;
176 if forward == Value::Bool(true) {
177 return Ok(forward);
178 }
179 let backward = eval_between(value, high, low, ctx, 2)?;
180 return Ok(match (forward, backward) {
181 (_, Value::Bool(true)) => Value::Bool(true),
182 (Value::Null, _) | (_, Value::Null) => Value::Null,
183 _ => Value::Bool(false),
184 });
185 }
186 let call_args = evaluate_call_args(args, ctx)?;
187 if let Some(binding) = binding {
188 if binding.builtin {
189 return eval_bound_builtin_function_call(binding, call_args, ctx);
190 }
191 let engine = ctx.engine.ok_or_else(|| {
192 SQLError::Unsupported(
193 "bound user function requires a logical engine session".into(),
194 )
195 })?;
196 engine
197 .call_bound_user_function(binding, &call_args)
198 .unwrap_or_else(|| Err(SQLError::UnknownFunction(binding.name.clone())))
199 } else {
200 eval_function_call(name, call_args, ctx)
201 }
202 }
203 Expr::WindowCall { name, .. } => Err(SQLError::Unsupported(format!(
204 "window function `{name}` must be evaluated by the window-aware executor"
205 ))),
206 Expr::Case {
207 base,
208 when,
209 else_branch,
210 } => {
211 let base_value = match base {
212 Some(b) => Some(eval(b, ctx)?),
213 None => None,
214 };
215 for (cond, result) in when {
216 let matched = match &base_value {
217 Some(bv) => {
218 compare(BinaryOp::Equal, bv, &eval(cond, ctx)?, ctx, 0)? == Some(true)
219 }
220 None => truthy(&eval(cond, ctx)?),
221 };
222 if matched {
223 return eval(result, ctx);
224 }
225 }
226 match else_branch {
227 Some(e) => eval(e, ctx),
228 None => Ok(Value::Null),
229 }
230 }
231 Expr::Cast { expr, ty, .. } => {
232 let source_ty = explicit_expr_type(expr);
233 let v = eval(expr, ctx)?;
234 cast_value_with_type_resolution(&v, source_ty, ty, ctx.engine)
235 }
236 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
237 Err(SQLError::Unsupported(
238 "query-valued expressions must be lowered to physical ScalarExpr/QueryPlan slots"
239 .into(),
240 ))
241 }
242 Expr::Binary { op, lhs, rhs } => eval_binary(*op, lhs, rhs, ctx),
243 Expr::UnaryMinus(inner) => {
244 let source_ty = explicit_expr_type(inner);
245 let value = eval(inner, ctx)?;
246 negate_value(&value, source_ty)
247 }
248 Expr::Not(inner) => {
249 let v = eval(inner, ctx)?;
251 if matches!(v, Value::Null) {
252 return Ok(Value::Null);
253 }
254 Ok(Value::Bool(!truthy(&v)))
255 }
256 Expr::And(items) => {
257 let mut saw_null = false;
259 for item in items {
260 let v = eval(item, ctx)?;
261 if matches!(v, Value::Null) {
262 saw_null = true;
263 } else if !truthy(&v) {
264 return Ok(Value::Bool(false));
265 }
266 }
267 if saw_null {
268 return Ok(Value::Null);
269 }
270 Ok(Value::Bool(true))
271 }
272 Expr::Or(items) => {
273 let mut saw_null = false;
275 for item in items {
276 let v = eval(item, ctx)?;
277 if matches!(v, Value::Null) {
278 saw_null = true;
279 } else if truthy(&v) {
280 return Ok(Value::Bool(true));
281 }
282 }
283 if saw_null {
284 return Ok(Value::Null);
285 }
286 Ok(Value::Bool(false))
287 }
288 Expr::IsNull { expr, negated } => {
289 let v = eval(expr, ctx)?;
290 Ok(Value::Bool(uqa_core::sql_null_test(Some(&v), *negated)))
291 }
292 Expr::Between { expr, low, high } => eval_between(expr, low, high, ctx, 0),
293 Expr::InList {
294 expr,
295 list,
296 negated,
297 } => {
298 let v = eval(expr, ctx)?;
301 let mut saw_null = matches!(v, Value::Null);
302 for item in list {
303 let candidate = eval(item, ctx)?;
304 match compare(BinaryOp::Equal, &v, &candidate, ctx, 0)? {
305 Some(true) => return Ok(Value::Bool(!*negated)),
306 Some(false) => {}
307 None => saw_null = true,
308 }
309 }
310 if saw_null {
311 return Ok(Value::Null);
312 }
313 Ok(Value::Bool(*negated))
314 }
315 }
316}
317
318fn explicit_expr_type(expr: &Expr) -> Option<&str> {
319 match expr {
320 Expr::Cast { ty, .. }
321 | Expr::TypedLiteral { ty, .. }
322 | Expr::CompositeRow {
323 binding: crate::ast::CompositeRowBinding { ty, .. },
324 ..
325 } => Some(ty),
326 Expr::Literal(Value::Int(value)) if i32::try_from(*value).is_ok() => Some("integer"),
327 Expr::Literal(Value::Int(_)) => Some("bigint"),
328 Expr::Literal(Value::Bytes(_)) => Some("bytea"),
329 _ => None,
330 }
331}
332
333fn eval_between(
336 expression: &Expr,
337 low: &Expr,
338 high: &Expr,
339 context: &EvalContext<'_>,
340 slot: usize,
341) -> Result<Value> {
342 let ge = compare(
343 BinaryOp::GreaterEqual,
344 &eval(expression, context)?,
345 &eval(low, context)?,
346 context,
347 slot,
348 )?;
349 if ge == Some(false) {
350 return Ok(Value::Bool(false));
351 }
352 let le = compare(
353 BinaryOp::LessEqual,
354 &eval(expression, context)?,
355 &eval(high, context)?,
356 context,
357 slot + 1,
358 )?;
359 Ok(match (ge, le) {
360 (_, Some(false)) => Value::Bool(false),
361 (Some(true), Some(true)) => Value::Bool(true),
362 _ => Value::Null,
363 })
364}
365
366fn compare(
367 op: BinaryOp,
368 left: &Value,
369 right: &Value,
370 context: &EvalContext<'_>,
371 slot: usize,
372) -> Result<Option<bool>> {
373 eval_comparison_truth_with_engine(
374 op,
375 left,
376 right,
377 &uqa_core::memory::ProductionControl::uncontrolled(),
378 context.engine,
379 context.enum_comparison_state_at(slot),
380 )
381}