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