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::document_body::decode_container_to_json(bytes)
961 .or_else(|| crate::serde_json::from_slice(bytes).ok()),
962 Value::Text(value) => crate::serde_json::from_str(value).ok(),
963 _ => None,
964 }
965}
966
967enum JsonPathStep<'a> {
968 Field(&'a str),
969 Index(usize),
970}
971
972fn parse_json_path(path: &str) -> Option<Vec<JsonPathStep<'_>>> {
973 let path = path.trim();
974 let rest = path.strip_prefix('$').unwrap_or(path);
975 let mut steps = Vec::new();
976 let bytes = rest.as_bytes();
977 let mut index = 0;
978 while index < bytes.len() {
979 match bytes[index] {
980 b'.' => {
981 index += 1;
982 let start = index;
983 while index < bytes.len() && bytes[index] != b'.' && bytes[index] != b'[' {
984 index += 1;
985 }
986 if start == index {
987 return None;
988 }
989 steps.push(JsonPathStep::Field(
990 std::str::from_utf8(&bytes[start..index]).ok()?,
991 ));
992 }
993 b'[' => {
994 index += 1;
995 let start = index;
996 while index < bytes.len() && bytes[index] != b']' {
997 index += 1;
998 }
999 if index >= bytes.len() {
1000 return None;
1001 }
1002 steps.push(JsonPathStep::Index(
1003 std::str::from_utf8(&bytes[start..index])
1004 .ok()?
1005 .parse()
1006 .ok()?,
1007 ));
1008 index += 1;
1009 }
1010 _ => return None,
1011 }
1012 }
1013 Some(steps)
1014}
1015
1016fn json_path_get<'a>(
1017 root: &'a crate::serde_json::Value,
1018 steps: &[JsonPathStep<'_>],
1019) -> Option<&'a crate::serde_json::Value> {
1020 let mut current = root;
1021 for step in steps {
1022 current = match (step, current) {
1023 (JsonPathStep::Field(name), crate::serde_json::Value::Object(map)) => map.get(*name)?,
1024 (JsonPathStep::Index(index), crate::serde_json::Value::Array(values)) => {
1025 values.get(*index)?
1026 }
1027 _ => return None,
1028 };
1029 }
1030 Some(current)
1031}
1032
1033fn eval_case(
1034 branches: &[(Expr, Expr)],
1035 else_: Option<&Expr>,
1036 row: &dyn Row,
1037) -> Result<Value, EvalError> {
1038 for (cond, value) in branches {
1039 let c = evaluate(cond, row)?;
1040 if matches!(c, Value::Boolean(true)) {
1041 return evaluate(value, row);
1042 }
1043 }
1044 match else_ {
1045 Some(e) => evaluate(e, row),
1046 None => Ok(Value::Null),
1047 }
1048}
1049
1050fn eval_in_list(
1051 target: &Expr,
1052 values: &[Expr],
1053 negated: bool,
1054 row: &dyn Row,
1055) -> Result<Value, EvalError> {
1056 if values.is_empty() {
1057 return Err(EvalError::EmptyInList);
1058 }
1059 let needle = evaluate(target, row)?;
1060 if needle.is_null() {
1061 return Ok(Value::Null);
1062 }
1063 let mut saw_null = false;
1064 for v in values {
1065 let candidate = evaluate(v, row)?;
1066 if candidate.is_null() {
1067 saw_null = true;
1068 continue;
1069 }
1070 if values_equal(&needle, &candidate) {
1071 return Ok(Value::Boolean(!negated));
1072 }
1073 }
1074 if saw_null {
1075 return Ok(Value::Null);
1076 }
1077 Ok(Value::Boolean(negated))
1078}
1079
1080fn eval_between(
1081 target: &Expr,
1082 low: &Expr,
1083 high: &Expr,
1084 negated: bool,
1085 row: &dyn Row,
1086) -> Result<Value, EvalError> {
1087 let v = evaluate(target, row)?;
1088 let lo = evaluate(low, row)?;
1089 let hi = evaluate(high, row)?;
1090 if v.is_null() || lo.is_null() || hi.is_null() {
1091 return Ok(Value::Null);
1092 }
1093 let lo_ok = compare_values(&v, &lo)
1094 .ok_or(EvalError::OperatorMismatch {
1095 op: BinOp::Ge,
1096 lhs: v.data_type(),
1097 rhs: lo.data_type(),
1098 })
1099 .map(|o| o != std::cmp::Ordering::Less)?;
1100 let hi_ok = compare_values(&v, &hi)
1101 .ok_or(EvalError::OperatorMismatch {
1102 op: BinOp::Le,
1103 lhs: v.data_type(),
1104 rhs: hi.data_type(),
1105 })
1106 .map(|o| o != std::cmp::Ordering::Greater)?;
1107 let inside = lo_ok && hi_ok;
1108 Ok(Value::Boolean(if negated { !inside } else { inside }))
1109}
1110
1111#[cfg(test)]
1112mod tests {
1113 use super::*;
1114 use crate::storage::query::ast::Span;
1115
1116 fn lit(v: Value) -> Expr {
1117 Expr::Literal {
1118 value: v,
1119 span: Span::synthetic(),
1120 }
1121 }
1122
1123 fn binop(op: BinOp, l: Expr, r: Expr) -> Expr {
1124 Expr::BinaryOp {
1125 op,
1126 lhs: Box::new(l),
1127 rhs: Box::new(r),
1128 span: Span::synthetic(),
1129 }
1130 }
1131
1132 fn empty_row() -> impl Row {
1133 |_field: &FieldRef| -> Option<Value> { None }
1134 }
1135
1136 #[test]
1137 fn contains_descends_into_json_object_values() {
1138 let detections = br#"[{"label":"person","confidence":0.9,"bbox":[0,0,1,1]},
1142 {"label":"car","confidence":0.7,"bbox":[1,1,2,2]}]"#;
1143 let json = Value::Json(detections.to_vec());
1144 assert!(value_contains(&json, "person"));
1145 assert!(value_contains(&json, "car"));
1146 assert!(!value_contains(&json, "bicycle"));
1147 assert!(!value_contains(&json, "label"));
1149 }
1150
1151 #[test]
1152 fn integer_addition_overflow_surfaces_as_eval_error() {
1153 let expr = binop(
1154 BinOp::Add,
1155 lit(Value::Integer(i64::MAX)),
1156 lit(Value::Integer(1)),
1157 );
1158 let err = evaluate(&expr, &empty_row()).unwrap_err();
1159 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Add });
1160 }
1161
1162 #[test]
1163 fn integer_multiplication_overflow_surfaces_as_eval_error() {
1164 let expr = binop(
1165 BinOp::Mul,
1166 lit(Value::Integer(i64::MAX)),
1167 lit(Value::Integer(2)),
1168 );
1169 let err = evaluate(&expr, &empty_row()).unwrap_err();
1170 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Mul });
1171 }
1172
1173 #[test]
1174 fn integer_subtraction_overflow_surfaces_as_eval_error() {
1175 let expr = binop(
1176 BinOp::Sub,
1177 lit(Value::Integer(i64::MIN)),
1178 lit(Value::Integer(1)),
1179 );
1180 let err = evaluate(&expr, &empty_row()).unwrap_err();
1181 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1182 }
1183
1184 #[test]
1185 fn unary_neg_overflow_on_min_int_surfaces_as_eval_error() {
1186 let expr = Expr::UnaryOp {
1187 op: UnaryOp::Neg,
1188 operand: Box::new(lit(Value::Integer(i64::MIN))),
1189 span: Span::synthetic(),
1190 };
1191 let err = evaluate(&expr, &empty_row()).unwrap_err();
1192 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1193 }
1194
1195 #[test]
1196 fn null_propagates_through_arithmetic() {
1197 let expr = binop(BinOp::Add, lit(Value::Null), lit(Value::Integer(7)));
1198 let v = evaluate(&expr, &empty_row()).unwrap();
1199 assert_eq!(v, Value::Null);
1200 }
1201
1202 #[test]
1203 fn null_propagates_through_comparison() {
1204 let expr = binop(BinOp::Lt, lit(Value::Integer(1)), lit(Value::Null));
1205 let v = evaluate(&expr, &empty_row()).unwrap();
1206 assert_eq!(v, Value::Null);
1207 }
1208
1209 #[test]
1210 fn null_propagates_through_concat() {
1211 let expr = binop(
1212 BinOp::Concat,
1213 lit(Value::Text(Arc::from("hi"))),
1214 lit(Value::Null),
1215 );
1216 let v = evaluate(&expr, &empty_row()).unwrap();
1217 assert_eq!(v, Value::Null);
1218 }
1219
1220 #[test]
1221 fn three_valued_and_returns_false_when_one_side_false_even_with_null() {
1222 let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(false)));
1223 let v = evaluate(&expr, &empty_row()).unwrap();
1224 assert_eq!(v, Value::Boolean(false));
1225 }
1226
1227 #[test]
1228 fn three_valued_or_returns_true_when_one_side_true_even_with_null() {
1229 let expr = binop(BinOp::Or, lit(Value::Null), lit(Value::Boolean(true)));
1230 let v = evaluate(&expr, &empty_row()).unwrap();
1231 assert_eq!(v, Value::Boolean(true));
1232 }
1233
1234 #[test]
1235 fn three_valued_and_returns_null_for_null_and_true() {
1236 let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(true)));
1237 let v = evaluate(&expr, &empty_row()).unwrap();
1238 assert_eq!(v, Value::Null);
1239 }
1240
1241 #[test]
1242 fn implicit_cast_triggers_for_decimal_plus_integer() {
1243 let expr = binop(
1252 BinOp::Add,
1253 lit(Value::Decimal(10000)),
1254 lit(Value::Integer(2)),
1255 );
1256 let v = evaluate(&expr, &empty_row()).unwrap();
1257 assert_eq!(v, Value::Decimal(30000));
1258 }
1259
1260 #[test]
1261 fn integer_plus_bigint_resolves_to_preferred_float_overload() {
1262 let expr = binop(
1268 BinOp::Add,
1269 lit(Value::Integer(5)),
1270 lit(Value::BigInt(40_000_000_000)),
1271 );
1272 let v = evaluate(&expr, &empty_row()).unwrap();
1273 assert_eq!(v, Value::Float(40_000_000_005.0));
1274 }
1275
1276 #[test]
1277 fn implicit_cast_promotes_integer_to_float_for_float_addition() {
1278 let expr = binop(BinOp::Add, lit(Value::Integer(2)), lit(Value::Float(0.5)));
1282 let v = evaluate(&expr, &empty_row()).unwrap();
1283 assert_eq!(v, Value::Float(2.5));
1284 }
1285
1286 #[test]
1287 fn integer_division_promotes_to_float() {
1288 let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(2)));
1289 let v = evaluate(&expr, &empty_row()).unwrap();
1290 assert_eq!(v, Value::Float(3.5));
1291 }
1292
1293 #[test]
1294 fn division_by_zero_is_eval_error() {
1295 let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(0)));
1296 let err = evaluate(&expr, &empty_row()).unwrap_err();
1297 assert_eq!(err, EvalError::DivisionByZero);
1298 }
1299
1300 #[test]
1301 fn unknown_function_surfaces_as_eval_error() {
1302 let expr = Expr::FunctionCall {
1303 name: "no_such_fn".to_string(),
1304 args: vec![lit(Value::Integer(1))],
1305 span: Span::synthetic(),
1306 };
1307 let err = evaluate(&expr, &empty_row()).unwrap_err();
1308 match err {
1309 EvalError::UnknownFunction { name, args } => {
1310 assert_eq!(name, "no_such_fn");
1311 assert_eq!(args, vec![DataType::Integer]);
1312 }
1313 other => panic!("expected UnknownFunction, got {other:?}"),
1314 }
1315 }
1316
1317 #[test]
1318 fn coalesce_returns_first_non_null() {
1319 let expr = Expr::FunctionCall {
1320 name: "COALESCE".to_string(),
1321 args: vec![
1322 lit(Value::Null),
1323 lit(Value::Null),
1324 lit(Value::Integer(42)),
1325 lit(Value::Integer(99)),
1326 ],
1327 span: Span::synthetic(),
1328 };
1329 let v = evaluate(&expr, &empty_row()).unwrap();
1330 assert_eq!(v, Value::Integer(42));
1331 }
1332
1333 #[test]
1334 fn coalesce_all_null_returns_null() {
1335 let expr = Expr::FunctionCall {
1336 name: "COALESCE".to_string(),
1337 args: vec![lit(Value::Null), lit(Value::Null)],
1338 span: Span::synthetic(),
1339 };
1340 let v = evaluate(&expr, &empty_row()).unwrap();
1341 assert_eq!(v, Value::Null);
1342 }
1343
1344 #[test]
1345 fn upper_lower_dispatch_through_function_catalog() {
1346 let expr = Expr::FunctionCall {
1347 name: "UPPER".to_string(),
1348 args: vec![lit(Value::Text(Arc::from("hello")))],
1349 span: Span::synthetic(),
1350 };
1351 let v = evaluate(&expr, &empty_row()).unwrap();
1352 assert_eq!(v, Value::Text(Arc::from("HELLO")));
1353 }
1354
1355 #[test]
1356 fn length_of_null_propagates() {
1357 let expr = Expr::FunctionCall {
1358 name: "LENGTH".to_string(),
1359 args: vec![lit(Value::Null)],
1360 span: Span::synthetic(),
1361 };
1362 let v = evaluate(&expr, &empty_row()).unwrap();
1363 assert_eq!(v, Value::Null);
1364 }
1365
1366 #[test]
1367 fn case_when_picks_first_true_branch() {
1368 let expr = Expr::Case {
1369 branches: vec![
1370 (lit(Value::Boolean(false)), lit(Value::Integer(1))),
1371 (lit(Value::Boolean(true)), lit(Value::Integer(2))),
1372 (lit(Value::Boolean(true)), lit(Value::Integer(3))),
1373 ],
1374 else_: Some(Box::new(lit(Value::Integer(99)))),
1375 span: Span::synthetic(),
1376 };
1377 let v = evaluate(&expr, &empty_row()).unwrap();
1378 assert_eq!(v, Value::Integer(2));
1379 }
1380
1381 #[test]
1382 fn case_falls_through_to_else_when_no_branch_matches() {
1383 let expr = Expr::Case {
1384 branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1385 else_: Some(Box::new(lit(Value::Integer(99)))),
1386 span: Span::synthetic(),
1387 };
1388 let v = evaluate(&expr, &empty_row()).unwrap();
1389 assert_eq!(v, Value::Integer(99));
1390 }
1391
1392 #[test]
1393 fn case_returns_null_when_no_branch_matches_and_no_else() {
1394 let expr = Expr::Case {
1395 branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1396 else_: None,
1397 span: Span::synthetic(),
1398 };
1399 let v = evaluate(&expr, &empty_row()).unwrap();
1400 assert_eq!(v, Value::Null);
1401 }
1402
1403 #[test]
1404 fn is_null_handles_null_and_non_null() {
1405 let null_expr = Expr::IsNull {
1406 operand: Box::new(lit(Value::Null)),
1407 negated: false,
1408 span: Span::synthetic(),
1409 };
1410 assert_eq!(
1411 evaluate(&null_expr, &empty_row()).unwrap(),
1412 Value::Boolean(true)
1413 );
1414
1415 let non_null_expr = Expr::IsNull {
1416 operand: Box::new(lit(Value::Integer(7))),
1417 negated: false,
1418 span: Span::synthetic(),
1419 };
1420 assert_eq!(
1421 evaluate(&non_null_expr, &empty_row()).unwrap(),
1422 Value::Boolean(false)
1423 );
1424 }
1425
1426 #[test]
1427 fn between_inclusive_bounds() {
1428 let expr = Expr::Between {
1429 target: Box::new(lit(Value::Integer(5))),
1430 low: Box::new(lit(Value::Integer(1))),
1431 high: Box::new(lit(Value::Integer(10))),
1432 negated: false,
1433 span: Span::synthetic(),
1434 };
1435 assert_eq!(evaluate(&expr, &empty_row()).unwrap(), Value::Boolean(true));
1436 }
1437
1438 #[test]
1439 fn in_list_match_and_miss() {
1440 let hit = Expr::InList {
1441 target: Box::new(lit(Value::Integer(2))),
1442 values: vec![
1443 lit(Value::Integer(1)),
1444 lit(Value::Integer(2)),
1445 lit(Value::Integer(3)),
1446 ],
1447 negated: false,
1448 span: Span::synthetic(),
1449 };
1450 assert_eq!(evaluate(&hit, &empty_row()).unwrap(), Value::Boolean(true));
1451
1452 let miss = Expr::InList {
1453 target: Box::new(lit(Value::Integer(99))),
1454 values: vec![lit(Value::Integer(1)), lit(Value::Integer(2))],
1455 negated: false,
1456 span: Span::synthetic(),
1457 };
1458 assert_eq!(
1459 evaluate(&miss, &empty_row()).unwrap(),
1460 Value::Boolean(false)
1461 );
1462 }
1463
1464 #[test]
1465 fn column_lookup_walks_field_ref() {
1466 let row = |field: &FieldRef| -> Option<Value> {
1467 match field {
1468 FieldRef::TableColumn { table, column } if table == "t" && column == "x" => {
1469 Some(Value::Integer(11))
1470 }
1471 _ => None,
1472 }
1473 };
1474 let expr = Expr::Column {
1475 field: FieldRef::TableColumn {
1476 table: "t".to_string(),
1477 column: "x".to_string(),
1478 },
1479 span: Span::synthetic(),
1480 };
1481 assert_eq!(evaluate(&expr, &row).unwrap(), Value::Integer(11));
1482 }
1483
1484 #[test]
1485 fn missing_column_surfaces_unknown_column() {
1486 let row = |_: &FieldRef| -> Option<Value> { None };
1487 let expr = Expr::Column {
1488 field: FieldRef::TableColumn {
1489 table: "t".to_string(),
1490 column: "missing".to_string(),
1491 },
1492 span: Span::synthetic(),
1493 };
1494 let err = evaluate(&expr, &row).unwrap_err();
1495 match err {
1496 EvalError::UnknownColumn(_) => {}
1497 other => panic!("expected UnknownColumn, got {other:?}"),
1498 }
1499 }
1500
1501 #[test]
1502 fn parameter_without_bind_is_eval_error() {
1503 let expr = Expr::Parameter {
1504 index: 1,
1505 span: Span::synthetic(),
1506 };
1507 let err = evaluate(&expr, &empty_row()).unwrap_err();
1508 assert_eq!(err, EvalError::UnboundParameter(1));
1509 }
1510
1511 #[test]
1512 fn cast_integer_to_text_uses_explicit_path() {
1513 let expr = Expr::Cast {
1514 inner: Box::new(lit(Value::Integer(42))),
1515 target: DataType::Text,
1516 span: Span::synthetic(),
1517 };
1518 assert_eq!(
1519 evaluate(&expr, &empty_row()).unwrap(),
1520 Value::Text(Arc::from("42"))
1521 );
1522 }
1523
1524 #[test]
1525 fn concat_returns_text() {
1526 let expr = binop(
1527 BinOp::Concat,
1528 lit(Value::Text(Arc::from("foo"))),
1529 lit(Value::Text(Arc::from("bar"))),
1530 );
1531 assert_eq!(
1532 evaluate(&expr, &empty_row()).unwrap(),
1533 Value::Text(Arc::from("foobar"))
1534 );
1535 }
1536}