1use std::sync::Arc;
64
65use super::ast::{BinOp, Expr, FieldRef, UnaryOp};
66use crate::storage::schema::coerce::coerce_via_catalog;
67use crate::storage::schema::coercion_spine;
68use crate::storage::schema::function_catalog::FUNCTION_CATALOG;
69use crate::storage::schema::operator_catalog::OperatorEntry;
70use crate::storage::schema::{DataType, Value};
71
72pub trait Row {
78 fn get(&self, field: &FieldRef) -> Option<Value>;
83}
84
85impl<F> Row for F
89where
90 F: Fn(&FieldRef) -> Option<Value>,
91{
92 fn get(&self, field: &FieldRef) -> Option<Value> {
93 self(field)
94 }
95}
96
97#[derive(Debug, Clone, PartialEq)]
101pub enum EvalError {
102 UnknownColumn(FieldRef),
104 UnboundParameter(usize),
108 OperatorMismatch {
111 op: BinOp,
112 lhs: DataType,
113 rhs: DataType,
114 },
115 UnaryMismatch { op: UnaryOp, operand: DataType },
117 UnknownFunction { name: String, args: Vec<DataType> },
121 ImplicitCastFailed {
126 from: DataType,
127 to: DataType,
128 reason: String,
129 },
130 CastFailed {
132 from: DataType,
133 to: DataType,
134 reason: String,
135 },
136 ArithmeticOverflow { op: BinOp },
138 DivisionByZero,
140 InvalidNumericResult { function: String, reason: String },
143 EmptyInList,
147}
148
149impl std::fmt::Display for EvalError {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 match self {
152 EvalError::UnknownColumn(field) => write!(f, "unknown column: {field:?}"),
153 EvalError::UnboundParameter(idx) => {
154 write!(f, "unbound query parameter: ${idx}")
155 }
156 EvalError::OperatorMismatch { op, lhs, rhs } => {
157 write!(f, "operator {op:?} not defined for ({lhs:?}, {rhs:?})")
158 }
159 EvalError::UnaryMismatch { op, operand } => {
160 write!(f, "unary {op:?} not defined for {operand:?}")
161 }
162 EvalError::UnknownFunction { name, args } => {
163 write!(f, "unknown function: {name}({args:?})")
164 }
165 EvalError::ImplicitCastFailed { from, to, reason } => {
166 write!(f, "implicit cast {from:?} -> {to:?} failed: {reason}")
167 }
168 EvalError::CastFailed { from, to, reason } => {
169 write!(f, "cast {from:?} -> {to:?} failed: {reason}")
170 }
171 EvalError::ArithmeticOverflow { op } => {
172 write!(f, "arithmetic overflow in {op:?}")
173 }
174 EvalError::DivisionByZero => write!(f, "division by zero"),
175 EvalError::InvalidNumericResult { function, reason } => {
176 write!(f, "invalid numeric result in {function}: {reason}")
177 }
178 EvalError::EmptyInList => write!(f, "IN list is empty"),
179 }
180 }
181}
182
183impl std::error::Error for EvalError {}
184
185pub fn evaluate(expr: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
190 match expr {
191 Expr::Literal { value, .. } => Ok(value.clone()),
192 Expr::Column { field, .. } => row
193 .get(field)
194 .ok_or_else(|| EvalError::UnknownColumn(field.clone())),
195 Expr::Parameter { index, .. } => Err(EvalError::UnboundParameter(*index)),
196 Expr::UnaryOp { op, operand, .. } => eval_unary(*op, operand, row),
197 Expr::BinaryOp { op, lhs, rhs, .. } => eval_binary(*op, lhs, rhs, row),
198 Expr::Cast { inner, target, .. } => eval_cast(inner, *target, row),
199 Expr::FunctionCall { name, args, .. } => eval_function(name, args, row),
200 Expr::Case {
201 branches, else_, ..
202 } => eval_case(branches, else_.as_deref(), row),
203 Expr::IsNull {
204 operand, negated, ..
205 } => {
206 let v = evaluate(operand, row)?;
207 let is_null = v.is_null();
208 Ok(Value::Boolean(if *negated { !is_null } else { is_null }))
209 }
210 Expr::InList {
211 target,
212 values,
213 negated,
214 ..
215 } => eval_in_list(target, values, *negated, row),
216 Expr::Between {
217 target,
218 low,
219 high,
220 negated,
221 ..
222 } => eval_between(target, low, high, *negated, row),
223 Expr::Subquery { .. } => Err(EvalError::UnknownFunction {
224 name: "SUBQUERY".to_string(),
225 args: Vec::new(),
226 }),
227 Expr::WindowFunctionCall { name, .. } => Err(EvalError::UnknownFunction {
228 name: format!("{name} OVER (...)"),
229 args: Vec::new(),
230 }),
231 }
232}
233
234fn eval_unary(op: UnaryOp, operand: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
235 let v = evaluate(operand, row)?;
236 if v.is_null() {
237 return Ok(Value::Null);
238 }
239 match op {
240 UnaryOp::Neg => match &v {
241 Value::Integer(n) => n
242 .checked_neg()
243 .map(Value::Integer)
244 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
245 Value::BigInt(n) => n
246 .checked_neg()
247 .map(Value::BigInt)
248 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
249 Value::Float(n) => Ok(Value::Float(-*n)),
250 Value::Decimal(n) => n
251 .checked_neg()
252 .map(Value::Decimal)
253 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
254 other => Err(EvalError::UnaryMismatch {
255 op,
256 operand: other.data_type(),
257 }),
258 },
259 UnaryOp::Not => match &v {
260 Value::Boolean(b) => Ok(Value::Boolean(!b)),
261 other => Err(EvalError::UnaryMismatch {
262 op,
263 operand: other.data_type(),
264 }),
265 },
266 }
267}
268
269fn eval_binary(op: BinOp, lhs: &Expr, rhs: &Expr, row: &dyn Row) -> Result<Value, EvalError> {
270 let l = evaluate(lhs, row)?;
274 let r = evaluate(rhs, row)?;
275
276 match op {
277 BinOp::And => return three_valued_and(&l, &r),
278 BinOp::Or => return three_valued_or(&l, &r),
279 _ => {}
280 }
281
282 if l.is_null() || r.is_null() {
283 return Ok(Value::Null);
284 }
285
286 let lhs_dt = l.data_type();
287 let rhs_dt = r.data_type();
288 let (entry, coercions) =
289 coercion_spine::resolve_binop(op, lhs_dt, rhs_dt).ok_or(EvalError::OperatorMismatch {
290 op,
291 lhs: lhs_dt,
292 rhs: rhs_dt,
293 })?;
294
295 let l = match coercions.at(0) {
296 Some(target) => apply_implicit_cast(&l, lhs_dt, target)?,
297 None => l,
298 };
299 let r = match coercions.at(1) {
300 Some(target) => apply_implicit_cast(&r, rhs_dt, target)?,
301 None => r,
302 };
303
304 dispatch_binop(op, entry, l, r)
305}
306
307fn dispatch_binop(
308 op: BinOp,
309 entry: &OperatorEntry,
310 l: Value,
311 r: Value,
312) -> Result<Value, EvalError> {
313 match op {
314 BinOp::Add => arith_add(entry, l, r),
315 BinOp::Sub => arith_sub(entry, l, r),
316 BinOp::Mul => arith_mul(entry, l, r),
317 BinOp::Div => arith_div(entry, l, r),
318 BinOp::Mod => arith_mod(entry, l, r),
319 BinOp::Concat => match (&l, &r) {
320 (Value::Text(a), Value::Text(b)) => {
321 let mut s = String::with_capacity(a.len() + b.len());
322 s.push_str(a);
323 s.push_str(b);
324 Ok(Value::Text(Arc::from(s)))
325 }
326 _ => Err(EvalError::OperatorMismatch {
327 op,
328 lhs: l.data_type(),
329 rhs: r.data_type(),
330 }),
331 },
332 BinOp::Eq => Ok(Value::Boolean(values_equal(&l, &r))),
333 BinOp::Ne => Ok(Value::Boolean(!values_equal(&l, &r))),
334 BinOp::Lt => cmp_op(op, l, r, |o| o == std::cmp::Ordering::Less),
335 BinOp::Le => cmp_op(op, l, r, |o| o != std::cmp::Ordering::Greater),
336 BinOp::Gt => cmp_op(op, l, r, |o| o == std::cmp::Ordering::Greater),
337 BinOp::Ge => cmp_op(op, l, r, |o| o != std::cmp::Ordering::Less),
338 BinOp::And | BinOp::Or => unreachable!("handled before dispatch"),
339 }
340}
341
342fn arith_add(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
343 match entry.return_type {
344 DataType::Integer => match (l, r) {
345 (Value::Integer(a), Value::Integer(b)) => a
346 .checked_add(b)
347 .map(Value::Integer)
348 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
349 _ => unreachable_after_coercion("Add", DataType::Integer),
350 },
351 DataType::BigInt => match (l, r) {
352 (Value::BigInt(a), Value::BigInt(b)) => a
353 .checked_add(b)
354 .map(Value::BigInt)
355 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
356 _ => unreachable_after_coercion("Add", DataType::BigInt),
357 },
358 DataType::Float => checked_float_binop(BinOp::Add, as_f64(&l) + as_f64(&r)),
359 DataType::Decimal => match (l, r) {
360 (Value::Decimal(a), Value::Decimal(b)) => a
361 .checked_add(b)
362 .map(Value::Decimal)
363 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Add }),
364 _ => unreachable_after_coercion("Add", DataType::Decimal),
365 },
366 other => Err(EvalError::OperatorMismatch {
367 op: BinOp::Add,
368 lhs: other,
369 rhs: other,
370 }),
371 }
372}
373
374fn arith_sub(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
375 match entry.return_type {
376 DataType::Integer => match (l, r) {
377 (Value::Integer(a), Value::Integer(b)) => a
378 .checked_sub(b)
379 .map(Value::Integer)
380 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
381 _ => unreachable_after_coercion("Sub", DataType::Integer),
382 },
383 DataType::BigInt => match (l, r) {
384 (Value::BigInt(a), Value::BigInt(b)) => a
385 .checked_sub(b)
386 .map(Value::BigInt)
387 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
388 _ => unreachable_after_coercion("Sub", DataType::BigInt),
389 },
390 DataType::Float => checked_float_binop(BinOp::Sub, as_f64(&l) - as_f64(&r)),
391 DataType::Decimal => match (l, r) {
392 (Value::Decimal(a), Value::Decimal(b)) => a
393 .checked_sub(b)
394 .map(Value::Decimal)
395 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
396 _ => unreachable_after_coercion("Sub", DataType::Decimal),
397 },
398 other => Err(EvalError::OperatorMismatch {
399 op: BinOp::Sub,
400 lhs: other,
401 rhs: other,
402 }),
403 }
404}
405
406fn arith_mul(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
407 match entry.return_type {
408 DataType::Integer => match (l, r) {
409 (Value::Integer(a), Value::Integer(b)) => a
410 .checked_mul(b)
411 .map(Value::Integer)
412 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mul }),
413 _ => unreachable_after_coercion("Mul", DataType::Integer),
414 },
415 DataType::BigInt => match (l, r) {
416 (Value::BigInt(a), Value::BigInt(b)) => a
417 .checked_mul(b)
418 .map(Value::BigInt)
419 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mul }),
420 _ => unreachable_after_coercion("Mul", DataType::BigInt),
421 },
422 DataType::Float => checked_float_binop(BinOp::Mul, as_f64(&l) * as_f64(&r)),
423 other => Err(EvalError::OperatorMismatch {
424 op: BinOp::Mul,
425 lhs: other,
426 rhs: other,
427 }),
428 }
429}
430
431fn arith_div(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
432 match entry.return_type {
435 DataType::Float => {
436 let denom = as_f64(&r);
437 if denom == 0.0 {
438 return Err(EvalError::DivisionByZero);
439 }
440 checked_float_binop(BinOp::Div, as_f64(&l) / denom)
441 }
442 other => Err(EvalError::OperatorMismatch {
443 op: BinOp::Div,
444 lhs: other,
445 rhs: other,
446 }),
447 }
448}
449
450fn arith_mod(entry: &OperatorEntry, l: Value, r: Value) -> Result<Value, EvalError> {
451 match entry.return_type {
452 DataType::Integer => match (l, r) {
453 (Value::Integer(_), Value::Integer(0)) => Err(EvalError::DivisionByZero),
454 (Value::Integer(a), Value::Integer(b)) => a
455 .checked_rem(b)
456 .map(Value::Integer)
457 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mod }),
458 _ => unreachable_after_coercion("Mod", DataType::Integer),
459 },
460 DataType::BigInt => match (l, r) {
461 (Value::BigInt(_), Value::BigInt(0)) => Err(EvalError::DivisionByZero),
462 (Value::BigInt(a), Value::BigInt(b)) => a
463 .checked_rem(b)
464 .map(Value::BigInt)
465 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Mod }),
466 _ => unreachable_after_coercion("Mod", DataType::BigInt),
467 },
468 other => Err(EvalError::OperatorMismatch {
469 op: BinOp::Mod,
470 lhs: other,
471 rhs: other,
472 }),
473 }
474}
475
476fn unreachable_after_coercion(op: &'static str, expected: DataType) -> Result<Value, EvalError> {
477 Err(EvalError::OperatorMismatch {
478 op: match op {
479 "Add" => BinOp::Add,
480 "Sub" => BinOp::Sub,
481 "Mul" => BinOp::Mul,
482 "Div" => BinOp::Div,
483 "Mod" => BinOp::Mod,
484 _ => BinOp::Add,
485 },
486 lhs: expected,
487 rhs: expected,
488 })
489}
490
491fn checked_float_binop(op: BinOp, value: f64) -> Result<Value, EvalError> {
492 if value.is_finite() {
493 Ok(Value::Float(value))
494 } else {
495 Err(EvalError::InvalidNumericResult {
496 function: format!("{op:?}"),
497 reason: "result is NaN or infinite".to_string(),
498 })
499 }
500}
501
502fn as_f64(v: &Value) -> f64 {
503 match v {
504 Value::Float(x) => *x,
505 Value::Integer(x) => *x as f64,
506 Value::BigInt(x) => *x as f64,
507 Value::UnsignedInteger(x) => *x as f64,
508 Value::Decimal(x) => *x as f64,
509 _ => 0.0,
510 }
511}
512
513fn cmp_op<F>(op: BinOp, l: Value, r: Value, pick: F) -> Result<Value, EvalError>
514where
515 F: Fn(std::cmp::Ordering) -> bool,
516{
517 let ord = compare_values(&l, &r).ok_or(EvalError::OperatorMismatch {
518 op,
519 lhs: l.data_type(),
520 rhs: r.data_type(),
521 })?;
522 Ok(Value::Boolean(pick(ord)))
523}
524
525fn compare_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
530 use std::cmp::Ordering;
531 match (a, b) {
532 (Value::Integer(x), Value::Integer(y)) => Some(x.cmp(y)),
533 (Value::BigInt(x), Value::BigInt(y)) => Some(x.cmp(y)),
534 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y),
535 (Value::Text(x), Value::Text(y)) => Some(x.as_ref().cmp(y.as_ref())),
536 (Value::Boolean(x), Value::Boolean(y)) => Some(x.cmp(y)),
537 (Value::Timestamp(x), Value::Timestamp(y)) => Some(x.cmp(y)),
538 (Value::TimestampMs(x), Value::TimestampMs(y)) => Some(x.cmp(y)),
539 (Value::Date(x), Value::Date(y)) => Some(x.cmp(y)),
540 (Value::Time(x), Value::Time(y)) => Some(x.cmp(y)),
541 (Value::Uuid(x), Value::Uuid(y)) => Some(x.cmp(y)),
542 (Value::Decimal(x), Value::Decimal(y)) => Some(x.cmp(y)),
543 (Value::Integer(_) | Value::Float(_) | Value::BigInt(_), _) => {
547 let l = as_f64(a);
548 let r = as_f64(b);
549 l.partial_cmp(&r)
550 }
551 _ => None,
552 }
553}
554
555fn values_equal(a: &Value, b: &Value) -> bool {
556 match (a, b) {
557 (Value::Float(x), Value::Float(y)) => x == y,
558 _ => a == b,
559 }
560}
561
562fn three_valued_and(l: &Value, r: &Value) -> Result<Value, EvalError> {
563 match (l, r) {
564 (Value::Boolean(false), _) | (_, Value::Boolean(false)) => Ok(Value::Boolean(false)),
565 (Value::Boolean(true), Value::Boolean(true)) => Ok(Value::Boolean(true)),
566 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
567 _ => Err(EvalError::OperatorMismatch {
568 op: BinOp::And,
569 lhs: l.data_type(),
570 rhs: r.data_type(),
571 }),
572 }
573}
574
575fn three_valued_or(l: &Value, r: &Value) -> Result<Value, EvalError> {
576 match (l, r) {
577 (Value::Boolean(true), _) | (_, Value::Boolean(true)) => Ok(Value::Boolean(true)),
578 (Value::Boolean(false), Value::Boolean(false)) => Ok(Value::Boolean(false)),
579 (Value::Null, _) | (_, Value::Null) => Ok(Value::Null),
580 _ => Err(EvalError::OperatorMismatch {
581 op: BinOp::Or,
582 lhs: l.data_type(),
583 rhs: r.data_type(),
584 }),
585 }
586}
587
588fn apply_implicit_cast(value: &Value, src: DataType, target: DataType) -> Result<Value, EvalError> {
589 if src == target {
590 return Ok(value.clone());
591 }
592 coerce_via_catalog(value, src, target, None).map_err(|reason| EvalError::ImplicitCastFailed {
593 from: src,
594 to: target,
595 reason,
596 })
597}
598
599fn eval_cast(inner: &Expr, target: DataType, row: &dyn Row) -> Result<Value, EvalError> {
600 let v = evaluate(inner, row)?;
601 if v.is_null() {
602 return Ok(Value::Null);
603 }
604 let src = v.data_type();
605 if src == target {
606 return Ok(v);
607 }
608 coerce_via_catalog(&v, src, target, None).map_err(|reason| EvalError::CastFailed {
609 from: src,
610 to: target,
611 reason,
612 })
613}
614
615fn eval_function(name: &str, args: &[Expr], row: &dyn Row) -> Result<Value, EvalError> {
616 if name.eq_ignore_ascii_case("COALESCE") {
622 for arg in args {
623 let v = evaluate(arg, row)?;
624 if !v.is_null() {
625 return Ok(v);
626 }
627 }
628 return Ok(Value::Null);
629 }
630
631 let arg_values: Vec<Value> = args
632 .iter()
633 .map(|a| evaluate(a, row))
634 .collect::<Result<Vec<_>, _>>()?;
635 let arg_types: Vec<DataType> = arg_values.iter().map(|v| v.data_type()).collect();
636
637 if arg_values.iter().any(Value::is_null)
643 && FUNCTION_CATALOG
644 .iter()
645 .any(|e| e.name.eq_ignore_ascii_case(name))
646 {
647 return Ok(Value::Null);
648 }
649
650 let (entry, coercions) =
651 coercion_spine::resolve_function(name, &arg_types).ok_or_else(|| {
652 EvalError::UnknownFunction {
653 name: name.to_string(),
654 args: arg_types.clone(),
655 }
656 })?;
657
658 let mut coerced: Vec<Value> = Vec::with_capacity(arg_values.len());
660 for (idx, value) in arg_values.into_iter().enumerate() {
661 let src = arg_types[idx];
662 match coercions.at(idx) {
663 Some(target) if src != target => {
664 coerced.push(apply_implicit_cast(&value, src, target)?);
665 }
666 _ => coerced.push(value),
667 }
668 }
669
670 if !entry.variadic && coerced.iter().any(|v| v.is_null()) {
674 return Ok(Value::Null);
675 }
676
677 dispatch_function(entry.name, &coerced)
678}
679
680fn dispatch_function(name: &str, args: &[Value]) -> Result<Value, EvalError> {
681 match name {
682 "UPPER" => match &args[0] {
683 Value::Text(s) => Ok(Value::Text(Arc::from(s.to_uppercase()))),
684 other => Err(EvalError::UnknownFunction {
685 name: name.to_string(),
686 args: vec![other.data_type()],
687 }),
688 },
689 "LOWER" => match &args[0] {
690 Value::Text(s) => Ok(Value::Text(Arc::from(s.to_lowercase()))),
691 other => Err(EvalError::UnknownFunction {
692 name: name.to_string(),
693 args: vec![other.data_type()],
694 }),
695 },
696 "TRIM" => trim_function(name, args, true, true),
697 "LTRIM" => trim_function(name, args, true, false),
698 "RTRIM" => trim_function(name, args, false, true),
699 "LENGTH" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => match &args[0] {
700 Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
701 other => Err(EvalError::UnknownFunction {
702 name: name.to_string(),
703 args: vec![other.data_type()],
704 }),
705 },
706 "OCTET_LENGTH" => match &args[0] {
707 Value::Text(s) => Ok(Value::Integer(s.len() as i64)),
708 Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
709 other => Err(EvalError::UnknownFunction {
710 name: name.to_string(),
711 args: vec![other.data_type()],
712 }),
713 },
714 "JSON_EXTRACT" => Ok(json_extract_value(&args[0], &args[1], false)),
715 "JSON_EXTRACT_TEXT" => Ok(json_extract_value(&args[0], &args[1], true)),
716 "CONTAINS" => Ok(contains_value(&args[0], &args[1])),
717 "ABS" => match &args[0] {
718 Value::Integer(n) => n
719 .checked_abs()
720 .map(Value::Integer)
721 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
722 Value::BigInt(n) => n
723 .checked_abs()
724 .map(Value::BigInt)
725 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
726 Value::Float(n) => Ok(Value::Float(n.abs())),
727 Value::Decimal(n) => n
728 .checked_abs()
729 .map(Value::Decimal)
730 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
731 other => Err(EvalError::UnknownFunction {
732 name: name.to_string(),
733 args: vec![other.data_type()],
734 }),
735 },
736 "SQRT" => unary_math(name, args, |x| {
737 if x < 0.0 {
738 return Err("input must be greater than or equal to zero");
739 }
740 Ok(x.sqrt())
741 }),
742 "POWER" | "POW" => binary_math(name, args, |base, exp| Ok(base.powf(exp))),
743 "EXP" => unary_math(name, args, |x| Ok(x.exp())),
744 "LN" => unary_math(name, args, |x| {
745 if x <= 0.0 {
746 return Err("input must be greater than zero");
747 }
748 Ok(x.ln())
749 }),
750 "LOG" if args.len() == 1 => unary_math(name, args, |x| {
751 if x <= 0.0 {
752 return Err("input must be greater than zero");
753 }
754 Ok(x.log10())
755 }),
756 "LOG" => binary_math(name, args, |base, x| {
757 if base <= 0.0 {
758 return Err("base must be greater than zero");
759 }
760 if base == 1.0 {
761 return Err("base must not equal one");
762 }
763 if x <= 0.0 {
764 return Err("input must be greater than zero");
765 }
766 Ok(x.log(base))
767 }),
768 "LOG10" => unary_math(name, args, |x| {
769 if x <= 0.0 {
770 return Err("input must be greater than zero");
771 }
772 Ok(x.log10())
773 }),
774 "SIN" => unary_math(name, args, |x| Ok(x.sin())),
775 "COS" => unary_math(name, args, |x| Ok(x.cos())),
776 "TAN" => unary_math(name, args, |x| Ok(x.tan())),
777 "ASIN" | "ARCSIN" => unary_math(name, args, |x| {
778 if !(-1.0..=1.0).contains(&x) {
779 return Err("input must be between -1 and 1");
780 }
781 Ok(x.asin())
782 }),
783 "ACOS" | "ARCCOS" => unary_math(name, args, |x| {
784 if !(-1.0..=1.0).contains(&x) {
785 return Err("input must be between -1 and 1");
786 }
787 Ok(x.acos())
788 }),
789 "ATAN" | "ARCTAN" => unary_math(name, args, |x| Ok(x.atan())),
790 "ATAN2" => binary_math(name, args, |y, x| Ok(y.atan2(x))),
791 "COT" => unary_math(name, args, |x| {
792 let tan = x.tan();
793 if tan == 0.0 {
794 return Err("input must not produce zero tangent");
795 }
796 Ok(1.0 / tan)
797 }),
798 "DEGREES" => unary_math(name, args, |x| Ok(x.to_degrees())),
799 "RADIANS" => unary_math(name, args, |x| Ok(x.to_radians())),
800 "PI" => checked_math_result(name, std::f64::consts::PI),
801 other => Err(EvalError::UnknownFunction {
806 name: other.to_string(),
807 args: args.iter().map(|v| v.data_type()).collect(),
808 }),
809 }
810}
811
812fn trim_function(name: &str, args: &[Value], left: bool, right: bool) -> Result<Value, EvalError> {
813 if args.len() != 1 {
814 return Err(EvalError::UnknownFunction {
815 name: name.to_string(),
816 args: args.iter().map(|v| v.data_type()).collect(),
817 });
818 }
819
820 match &args[0] {
821 Value::Text(s) => {
822 let trimmed = match (left, right) {
823 (true, true) => s.trim(),
824 (true, false) => s.trim_start(),
825 (false, true) => s.trim_end(),
826 (false, false) => s,
827 };
828 Ok(Value::Text(Arc::from(trimmed)))
829 }
830 other => Err(EvalError::UnknownFunction {
831 name: name.to_string(),
832 args: vec![other.data_type()],
833 }),
834 }
835}
836
837fn unary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
838where
839 F: FnOnce(f64) -> Result<f64, &'static str>,
840{
841 let input = math_arg(name, args.first(), 0)?;
842 let value = op(input).map_err(|reason| EvalError::InvalidNumericResult {
843 function: name.to_string(),
844 reason: reason.to_string(),
845 })?;
846 checked_math_result(name, value)
847}
848
849fn binary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
850where
851 F: FnOnce(f64, f64) -> Result<f64, &'static str>,
852{
853 let left = math_arg(name, args.first(), 0)?;
854 let right = math_arg(name, args.get(1), 1)?;
855 let value = op(left, right).map_err(|reason| EvalError::InvalidNumericResult {
856 function: name.to_string(),
857 reason: reason.to_string(),
858 })?;
859 checked_math_result(name, value)
860}
861
862fn math_arg(name: &str, value: Option<&Value>, index: usize) -> Result<f64, EvalError> {
863 let value = value.ok_or_else(|| EvalError::UnknownFunction {
864 name: name.to_string(),
865 args: Vec::new(),
866 })?;
867 let numeric = as_f64(value);
868 if numeric.is_finite() {
869 Ok(numeric)
870 } else {
871 Err(EvalError::InvalidNumericResult {
872 function: name.to_string(),
873 reason: format!("argument {index} is NaN or infinite"),
874 })
875 }
876}
877
878fn checked_math_result(name: &str, value: f64) -> Result<Value, EvalError> {
879 if value.is_finite() {
880 Ok(Value::Float(value))
881 } else {
882 Err(EvalError::InvalidNumericResult {
883 function: name.to_string(),
884 reason: "result is NaN or infinite".to_string(),
885 })
886 }
887}
888
889fn json_extract_value(input: &Value, path: &Value, as_text: bool) -> Value {
890 let Value::Text(path) = path else {
891 return Value::Null;
892 };
893 let Some(json) = value_to_json(input) else {
894 return Value::Null;
895 };
896 let Some(steps) = parse_json_path(path) else {
897 return Value::Null;
898 };
899 let Some(target) = json_path_get(&json, &steps) else {
900 return Value::Null;
901 };
902
903 if as_text {
904 match target {
905 crate::serde_json::Value::String(value) => Value::text(value.clone()),
906 crate::serde_json::Value::Null => Value::Null,
907 crate::serde_json::Value::Bool(value) => Value::text(value.to_string()),
908 crate::serde_json::Value::Number(value) => Value::text(value.to_string()),
909 other => Value::text(other.to_string_compact()),
910 }
911 } else {
912 Value::text(target.to_string_compact())
913 }
914}
915
916fn contains_value(input: &Value, needle: &Value) -> Value {
917 let Value::Text(needle) = needle else {
918 return Value::Null;
919 };
920 Value::Boolean(value_contains(input, needle))
921}
922
923fn value_contains(value: &Value, needle: &str) -> bool {
924 match value {
925 Value::Array(values) => values.iter().any(|value| value_contains(value, needle)),
926 Value::Json(_) => value_to_json(value)
927 .as_ref()
928 .is_some_and(|json| json_value_contains(json, needle)),
929 Value::Text(value) => value.contains(needle),
930 other => other.display_string().contains(needle),
931 }
932}
933
934fn json_value_contains(value: &crate::serde_json::Value, needle: &str) -> bool {
935 match value {
936 crate::serde_json::Value::Array(values) => values
937 .iter()
938 .any(|value| json_value_contains(value, needle)),
939 crate::serde_json::Value::Object(map) => {
945 map.values().any(|value| json_value_contains(value, needle))
946 }
947 crate::serde_json::Value::String(value) => value == needle,
948 crate::serde_json::Value::Number(value) => value.to_string() == needle,
949 crate::serde_json::Value::Bool(value) => value.to_string() == needle,
950 crate::serde_json::Value::Null => false,
951 }
952}
953
954fn value_to_json(value: &Value) -> Option<crate::serde_json::Value> {
955 match value {
956 Value::Null => Some(crate::serde_json::Value::Null),
957 Value::Json(bytes) => crate::serde_json::from_slice(bytes).ok(),
958 Value::Text(value) => crate::serde_json::from_str(value).ok(),
959 _ => None,
960 }
961}
962
963enum JsonPathStep<'a> {
964 Field(&'a str),
965 Index(usize),
966}
967
968fn parse_json_path(path: &str) -> Option<Vec<JsonPathStep<'_>>> {
969 let path = path.trim();
970 let rest = path.strip_prefix('$').unwrap_or(path);
971 let mut steps = Vec::new();
972 let bytes = rest.as_bytes();
973 let mut index = 0;
974 while index < bytes.len() {
975 match bytes[index] {
976 b'.' => {
977 index += 1;
978 let start = index;
979 while index < bytes.len() && bytes[index] != b'.' && bytes[index] != b'[' {
980 index += 1;
981 }
982 if start == index {
983 return None;
984 }
985 steps.push(JsonPathStep::Field(
986 std::str::from_utf8(&bytes[start..index]).ok()?,
987 ));
988 }
989 b'[' => {
990 index += 1;
991 let start = index;
992 while index < bytes.len() && bytes[index] != b']' {
993 index += 1;
994 }
995 if index >= bytes.len() {
996 return None;
997 }
998 steps.push(JsonPathStep::Index(
999 std::str::from_utf8(&bytes[start..index])
1000 .ok()?
1001 .parse()
1002 .ok()?,
1003 ));
1004 index += 1;
1005 }
1006 _ => return None,
1007 }
1008 }
1009 Some(steps)
1010}
1011
1012fn json_path_get<'a>(
1013 root: &'a crate::serde_json::Value,
1014 steps: &[JsonPathStep<'_>],
1015) -> Option<&'a crate::serde_json::Value> {
1016 let mut current = root;
1017 for step in steps {
1018 current = match (step, current) {
1019 (JsonPathStep::Field(name), crate::serde_json::Value::Object(map)) => map.get(*name)?,
1020 (JsonPathStep::Index(index), crate::serde_json::Value::Array(values)) => {
1021 values.get(*index)?
1022 }
1023 _ => return None,
1024 };
1025 }
1026 Some(current)
1027}
1028
1029fn eval_case(
1030 branches: &[(Expr, Expr)],
1031 else_: Option<&Expr>,
1032 row: &dyn Row,
1033) -> Result<Value, EvalError> {
1034 for (cond, value) in branches {
1035 let c = evaluate(cond, row)?;
1036 if matches!(c, Value::Boolean(true)) {
1037 return evaluate(value, row);
1038 }
1039 }
1040 match else_ {
1041 Some(e) => evaluate(e, row),
1042 None => Ok(Value::Null),
1043 }
1044}
1045
1046fn eval_in_list(
1047 target: &Expr,
1048 values: &[Expr],
1049 negated: bool,
1050 row: &dyn Row,
1051) -> Result<Value, EvalError> {
1052 if values.is_empty() {
1053 return Err(EvalError::EmptyInList);
1054 }
1055 let needle = evaluate(target, row)?;
1056 if needle.is_null() {
1057 return Ok(Value::Null);
1058 }
1059 let mut saw_null = false;
1060 for v in values {
1061 let candidate = evaluate(v, row)?;
1062 if candidate.is_null() {
1063 saw_null = true;
1064 continue;
1065 }
1066 if values_equal(&needle, &candidate) {
1067 return Ok(Value::Boolean(!negated));
1068 }
1069 }
1070 if saw_null {
1071 return Ok(Value::Null);
1072 }
1073 Ok(Value::Boolean(negated))
1074}
1075
1076fn eval_between(
1077 target: &Expr,
1078 low: &Expr,
1079 high: &Expr,
1080 negated: bool,
1081 row: &dyn Row,
1082) -> Result<Value, EvalError> {
1083 let v = evaluate(target, row)?;
1084 let lo = evaluate(low, row)?;
1085 let hi = evaluate(high, row)?;
1086 if v.is_null() || lo.is_null() || hi.is_null() {
1087 return Ok(Value::Null);
1088 }
1089 let lo_ok = compare_values(&v, &lo)
1090 .ok_or(EvalError::OperatorMismatch {
1091 op: BinOp::Ge,
1092 lhs: v.data_type(),
1093 rhs: lo.data_type(),
1094 })
1095 .map(|o| o != std::cmp::Ordering::Less)?;
1096 let hi_ok = compare_values(&v, &hi)
1097 .ok_or(EvalError::OperatorMismatch {
1098 op: BinOp::Le,
1099 lhs: v.data_type(),
1100 rhs: hi.data_type(),
1101 })
1102 .map(|o| o != std::cmp::Ordering::Greater)?;
1103 let inside = lo_ok && hi_ok;
1104 Ok(Value::Boolean(if negated { !inside } else { inside }))
1105}
1106
1107#[cfg(test)]
1108mod tests {
1109 use super::*;
1110 use crate::storage::query::ast::Span;
1111
1112 fn lit(v: Value) -> Expr {
1113 Expr::Literal {
1114 value: v,
1115 span: Span::synthetic(),
1116 }
1117 }
1118
1119 fn binop(op: BinOp, l: Expr, r: Expr) -> Expr {
1120 Expr::BinaryOp {
1121 op,
1122 lhs: Box::new(l),
1123 rhs: Box::new(r),
1124 span: Span::synthetic(),
1125 }
1126 }
1127
1128 fn empty_row() -> impl Row {
1129 |_field: &FieldRef| -> Option<Value> { None }
1130 }
1131
1132 #[test]
1133 fn contains_descends_into_json_object_values() {
1134 let detections = br#"[{"label":"person","confidence":0.9,"bbox":[0,0,1,1]},
1138 {"label":"car","confidence":0.7,"bbox":[1,1,2,2]}]"#;
1139 let json = Value::Json(detections.to_vec());
1140 assert!(value_contains(&json, "person"));
1141 assert!(value_contains(&json, "car"));
1142 assert!(!value_contains(&json, "bicycle"));
1143 assert!(!value_contains(&json, "label"));
1145 }
1146
1147 #[test]
1148 fn integer_addition_overflow_surfaces_as_eval_error() {
1149 let expr = binop(
1150 BinOp::Add,
1151 lit(Value::Integer(i64::MAX)),
1152 lit(Value::Integer(1)),
1153 );
1154 let err = evaluate(&expr, &empty_row()).unwrap_err();
1155 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Add });
1156 }
1157
1158 #[test]
1159 fn integer_multiplication_overflow_surfaces_as_eval_error() {
1160 let expr = binop(
1161 BinOp::Mul,
1162 lit(Value::Integer(i64::MAX)),
1163 lit(Value::Integer(2)),
1164 );
1165 let err = evaluate(&expr, &empty_row()).unwrap_err();
1166 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Mul });
1167 }
1168
1169 #[test]
1170 fn integer_subtraction_overflow_surfaces_as_eval_error() {
1171 let expr = binop(
1172 BinOp::Sub,
1173 lit(Value::Integer(i64::MIN)),
1174 lit(Value::Integer(1)),
1175 );
1176 let err = evaluate(&expr, &empty_row()).unwrap_err();
1177 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1178 }
1179
1180 #[test]
1181 fn unary_neg_overflow_on_min_int_surfaces_as_eval_error() {
1182 let expr = Expr::UnaryOp {
1183 op: UnaryOp::Neg,
1184 operand: Box::new(lit(Value::Integer(i64::MIN))),
1185 span: Span::synthetic(),
1186 };
1187 let err = evaluate(&expr, &empty_row()).unwrap_err();
1188 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1189 }
1190
1191 #[test]
1192 fn null_propagates_through_arithmetic() {
1193 let expr = binop(BinOp::Add, lit(Value::Null), lit(Value::Integer(7)));
1194 let v = evaluate(&expr, &empty_row()).unwrap();
1195 assert_eq!(v, Value::Null);
1196 }
1197
1198 #[test]
1199 fn null_propagates_through_comparison() {
1200 let expr = binop(BinOp::Lt, lit(Value::Integer(1)), lit(Value::Null));
1201 let v = evaluate(&expr, &empty_row()).unwrap();
1202 assert_eq!(v, Value::Null);
1203 }
1204
1205 #[test]
1206 fn null_propagates_through_concat() {
1207 let expr = binop(
1208 BinOp::Concat,
1209 lit(Value::Text(Arc::from("hi"))),
1210 lit(Value::Null),
1211 );
1212 let v = evaluate(&expr, &empty_row()).unwrap();
1213 assert_eq!(v, Value::Null);
1214 }
1215
1216 #[test]
1217 fn three_valued_and_returns_false_when_one_side_false_even_with_null() {
1218 let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(false)));
1219 let v = evaluate(&expr, &empty_row()).unwrap();
1220 assert_eq!(v, Value::Boolean(false));
1221 }
1222
1223 #[test]
1224 fn three_valued_or_returns_true_when_one_side_true_even_with_null() {
1225 let expr = binop(BinOp::Or, lit(Value::Null), lit(Value::Boolean(true)));
1226 let v = evaluate(&expr, &empty_row()).unwrap();
1227 assert_eq!(v, Value::Boolean(true));
1228 }
1229
1230 #[test]
1231 fn three_valued_and_returns_null_for_null_and_true() {
1232 let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(true)));
1233 let v = evaluate(&expr, &empty_row()).unwrap();
1234 assert_eq!(v, Value::Null);
1235 }
1236
1237 #[test]
1238 fn implicit_cast_triggers_for_decimal_plus_integer() {
1239 let expr = binop(
1248 BinOp::Add,
1249 lit(Value::Decimal(10000)),
1250 lit(Value::Integer(2)),
1251 );
1252 let v = evaluate(&expr, &empty_row()).unwrap();
1253 assert_eq!(v, Value::Decimal(30000));
1254 }
1255
1256 #[test]
1257 fn integer_plus_bigint_resolves_to_preferred_float_overload() {
1258 let expr = binop(
1264 BinOp::Add,
1265 lit(Value::Integer(5)),
1266 lit(Value::BigInt(40_000_000_000)),
1267 );
1268 let v = evaluate(&expr, &empty_row()).unwrap();
1269 assert_eq!(v, Value::Float(40_000_000_005.0));
1270 }
1271
1272 #[test]
1273 fn implicit_cast_promotes_integer_to_float_for_float_addition() {
1274 let expr = binop(BinOp::Add, lit(Value::Integer(2)), lit(Value::Float(0.5)));
1278 let v = evaluate(&expr, &empty_row()).unwrap();
1279 assert_eq!(v, Value::Float(2.5));
1280 }
1281
1282 #[test]
1283 fn integer_division_promotes_to_float() {
1284 let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(2)));
1285 let v = evaluate(&expr, &empty_row()).unwrap();
1286 assert_eq!(v, Value::Float(3.5));
1287 }
1288
1289 #[test]
1290 fn division_by_zero_is_eval_error() {
1291 let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(0)));
1292 let err = evaluate(&expr, &empty_row()).unwrap_err();
1293 assert_eq!(err, EvalError::DivisionByZero);
1294 }
1295
1296 #[test]
1297 fn unknown_function_surfaces_as_eval_error() {
1298 let expr = Expr::FunctionCall {
1299 name: "no_such_fn".to_string(),
1300 args: vec![lit(Value::Integer(1))],
1301 span: Span::synthetic(),
1302 };
1303 let err = evaluate(&expr, &empty_row()).unwrap_err();
1304 match err {
1305 EvalError::UnknownFunction { name, args } => {
1306 assert_eq!(name, "no_such_fn");
1307 assert_eq!(args, vec![DataType::Integer]);
1308 }
1309 other => panic!("expected UnknownFunction, got {other:?}"),
1310 }
1311 }
1312
1313 #[test]
1314 fn coalesce_returns_first_non_null() {
1315 let expr = Expr::FunctionCall {
1316 name: "COALESCE".to_string(),
1317 args: vec![
1318 lit(Value::Null),
1319 lit(Value::Null),
1320 lit(Value::Integer(42)),
1321 lit(Value::Integer(99)),
1322 ],
1323 span: Span::synthetic(),
1324 };
1325 let v = evaluate(&expr, &empty_row()).unwrap();
1326 assert_eq!(v, Value::Integer(42));
1327 }
1328
1329 #[test]
1330 fn coalesce_all_null_returns_null() {
1331 let expr = Expr::FunctionCall {
1332 name: "COALESCE".to_string(),
1333 args: vec![lit(Value::Null), lit(Value::Null)],
1334 span: Span::synthetic(),
1335 };
1336 let v = evaluate(&expr, &empty_row()).unwrap();
1337 assert_eq!(v, Value::Null);
1338 }
1339
1340 #[test]
1341 fn upper_lower_dispatch_through_function_catalog() {
1342 let expr = Expr::FunctionCall {
1343 name: "UPPER".to_string(),
1344 args: vec![lit(Value::Text(Arc::from("hello")))],
1345 span: Span::synthetic(),
1346 };
1347 let v = evaluate(&expr, &empty_row()).unwrap();
1348 assert_eq!(v, Value::Text(Arc::from("HELLO")));
1349 }
1350
1351 #[test]
1352 fn length_of_null_propagates() {
1353 let expr = Expr::FunctionCall {
1354 name: "LENGTH".to_string(),
1355 args: vec![lit(Value::Null)],
1356 span: Span::synthetic(),
1357 };
1358 let v = evaluate(&expr, &empty_row()).unwrap();
1359 assert_eq!(v, Value::Null);
1360 }
1361
1362 #[test]
1363 fn case_when_picks_first_true_branch() {
1364 let expr = Expr::Case {
1365 branches: vec![
1366 (lit(Value::Boolean(false)), lit(Value::Integer(1))),
1367 (lit(Value::Boolean(true)), lit(Value::Integer(2))),
1368 (lit(Value::Boolean(true)), lit(Value::Integer(3))),
1369 ],
1370 else_: Some(Box::new(lit(Value::Integer(99)))),
1371 span: Span::synthetic(),
1372 };
1373 let v = evaluate(&expr, &empty_row()).unwrap();
1374 assert_eq!(v, Value::Integer(2));
1375 }
1376
1377 #[test]
1378 fn case_falls_through_to_else_when_no_branch_matches() {
1379 let expr = Expr::Case {
1380 branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1381 else_: Some(Box::new(lit(Value::Integer(99)))),
1382 span: Span::synthetic(),
1383 };
1384 let v = evaluate(&expr, &empty_row()).unwrap();
1385 assert_eq!(v, Value::Integer(99));
1386 }
1387
1388 #[test]
1389 fn case_returns_null_when_no_branch_matches_and_no_else() {
1390 let expr = Expr::Case {
1391 branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1392 else_: None,
1393 span: Span::synthetic(),
1394 };
1395 let v = evaluate(&expr, &empty_row()).unwrap();
1396 assert_eq!(v, Value::Null);
1397 }
1398
1399 #[test]
1400 fn is_null_handles_null_and_non_null() {
1401 let null_expr = Expr::IsNull {
1402 operand: Box::new(lit(Value::Null)),
1403 negated: false,
1404 span: Span::synthetic(),
1405 };
1406 assert_eq!(
1407 evaluate(&null_expr, &empty_row()).unwrap(),
1408 Value::Boolean(true)
1409 );
1410
1411 let non_null_expr = Expr::IsNull {
1412 operand: Box::new(lit(Value::Integer(7))),
1413 negated: false,
1414 span: Span::synthetic(),
1415 };
1416 assert_eq!(
1417 evaluate(&non_null_expr, &empty_row()).unwrap(),
1418 Value::Boolean(false)
1419 );
1420 }
1421
1422 #[test]
1423 fn between_inclusive_bounds() {
1424 let expr = Expr::Between {
1425 target: Box::new(lit(Value::Integer(5))),
1426 low: Box::new(lit(Value::Integer(1))),
1427 high: Box::new(lit(Value::Integer(10))),
1428 negated: false,
1429 span: Span::synthetic(),
1430 };
1431 assert_eq!(evaluate(&expr, &empty_row()).unwrap(), Value::Boolean(true));
1432 }
1433
1434 #[test]
1435 fn in_list_match_and_miss() {
1436 let hit = Expr::InList {
1437 target: Box::new(lit(Value::Integer(2))),
1438 values: vec![
1439 lit(Value::Integer(1)),
1440 lit(Value::Integer(2)),
1441 lit(Value::Integer(3)),
1442 ],
1443 negated: false,
1444 span: Span::synthetic(),
1445 };
1446 assert_eq!(evaluate(&hit, &empty_row()).unwrap(), Value::Boolean(true));
1447
1448 let miss = Expr::InList {
1449 target: Box::new(lit(Value::Integer(99))),
1450 values: vec![lit(Value::Integer(1)), lit(Value::Integer(2))],
1451 negated: false,
1452 span: Span::synthetic(),
1453 };
1454 assert_eq!(
1455 evaluate(&miss, &empty_row()).unwrap(),
1456 Value::Boolean(false)
1457 );
1458 }
1459
1460 #[test]
1461 fn column_lookup_walks_field_ref() {
1462 let row = |field: &FieldRef| -> Option<Value> {
1463 match field {
1464 FieldRef::TableColumn { table, column } if table == "t" && column == "x" => {
1465 Some(Value::Integer(11))
1466 }
1467 _ => None,
1468 }
1469 };
1470 let expr = Expr::Column {
1471 field: FieldRef::TableColumn {
1472 table: "t".to_string(),
1473 column: "x".to_string(),
1474 },
1475 span: Span::synthetic(),
1476 };
1477 assert_eq!(evaluate(&expr, &row).unwrap(), Value::Integer(11));
1478 }
1479
1480 #[test]
1481 fn missing_column_surfaces_unknown_column() {
1482 let row = |_: &FieldRef| -> Option<Value> { None };
1483 let expr = Expr::Column {
1484 field: FieldRef::TableColumn {
1485 table: "t".to_string(),
1486 column: "missing".to_string(),
1487 },
1488 span: Span::synthetic(),
1489 };
1490 let err = evaluate(&expr, &row).unwrap_err();
1491 match err {
1492 EvalError::UnknownColumn(_) => {}
1493 other => panic!("expected UnknownColumn, got {other:?}"),
1494 }
1495 }
1496
1497 #[test]
1498 fn parameter_without_bind_is_eval_error() {
1499 let expr = Expr::Parameter {
1500 index: 1,
1501 span: Span::synthetic(),
1502 };
1503 let err = evaluate(&expr, &empty_row()).unwrap_err();
1504 assert_eq!(err, EvalError::UnboundParameter(1));
1505 }
1506
1507 #[test]
1508 fn cast_integer_to_text_uses_explicit_path() {
1509 let expr = Expr::Cast {
1510 inner: Box::new(lit(Value::Integer(42))),
1511 target: DataType::Text,
1512 span: Span::synthetic(),
1513 };
1514 assert_eq!(
1515 evaluate(&expr, &empty_row()).unwrap(),
1516 Value::Text(Arc::from("42"))
1517 );
1518 }
1519
1520 #[test]
1521 fn concat_returns_text() {
1522 let expr = binop(
1523 BinOp::Concat,
1524 lit(Value::Text(Arc::from("foo"))),
1525 lit(Value::Text(Arc::from("bar"))),
1526 );
1527 assert_eq!(
1528 evaluate(&expr, &empty_row()).unwrap(),
1529 Value::Text(Arc::from("foobar"))
1530 );
1531 }
1532}