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 "LENGTH" | "CHAR_LENGTH" | "CHARACTER_LENGTH" => match &args[0] {
697 Value::Text(s) => Ok(Value::Integer(s.chars().count() as i64)),
698 other => Err(EvalError::UnknownFunction {
699 name: name.to_string(),
700 args: vec![other.data_type()],
701 }),
702 },
703 "OCTET_LENGTH" => match &args[0] {
704 Value::Text(s) => Ok(Value::Integer(s.len() as i64)),
705 Value::Blob(b) => Ok(Value::Integer(b.len() as i64)),
706 other => Err(EvalError::UnknownFunction {
707 name: name.to_string(),
708 args: vec![other.data_type()],
709 }),
710 },
711 "JSON_EXTRACT" => Ok(json_extract_value(&args[0], &args[1], false)),
712 "JSON_EXTRACT_TEXT" => Ok(json_extract_value(&args[0], &args[1], true)),
713 "CONTAINS" => Ok(contains_value(&args[0], &args[1])),
714 "ABS" => match &args[0] {
715 Value::Integer(n) => n
716 .checked_abs()
717 .map(Value::Integer)
718 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
719 Value::BigInt(n) => n
720 .checked_abs()
721 .map(Value::BigInt)
722 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
723 Value::Float(n) => Ok(Value::Float(n.abs())),
724 Value::Decimal(n) => n
725 .checked_abs()
726 .map(Value::Decimal)
727 .ok_or(EvalError::ArithmeticOverflow { op: BinOp::Sub }),
728 other => Err(EvalError::UnknownFunction {
729 name: name.to_string(),
730 args: vec![other.data_type()],
731 }),
732 },
733 "SQRT" => unary_math(name, args, |x| {
734 if x < 0.0 {
735 return Err("input must be greater than or equal to zero");
736 }
737 Ok(x.sqrt())
738 }),
739 "POWER" | "POW" => binary_math(name, args, |base, exp| Ok(base.powf(exp))),
740 "EXP" => unary_math(name, args, |x| Ok(x.exp())),
741 "LN" => unary_math(name, args, |x| {
742 if x <= 0.0 {
743 return Err("input must be greater than zero");
744 }
745 Ok(x.ln())
746 }),
747 "LOG" if args.len() == 1 => unary_math(name, args, |x| {
748 if x <= 0.0 {
749 return Err("input must be greater than zero");
750 }
751 Ok(x.log10())
752 }),
753 "LOG" => binary_math(name, args, |base, x| {
754 if base <= 0.0 {
755 return Err("base must be greater than zero");
756 }
757 if base == 1.0 {
758 return Err("base must not equal one");
759 }
760 if x <= 0.0 {
761 return Err("input must be greater than zero");
762 }
763 Ok(x.log(base))
764 }),
765 "LOG10" => unary_math(name, args, |x| {
766 if x <= 0.0 {
767 return Err("input must be greater than zero");
768 }
769 Ok(x.log10())
770 }),
771 "SIN" => unary_math(name, args, |x| Ok(x.sin())),
772 "COS" => unary_math(name, args, |x| Ok(x.cos())),
773 "TAN" => unary_math(name, args, |x| Ok(x.tan())),
774 "ASIN" | "ARCSIN" => unary_math(name, args, |x| {
775 if !(-1.0..=1.0).contains(&x) {
776 return Err("input must be between -1 and 1");
777 }
778 Ok(x.asin())
779 }),
780 "ACOS" | "ARCCOS" => unary_math(name, args, |x| {
781 if !(-1.0..=1.0).contains(&x) {
782 return Err("input must be between -1 and 1");
783 }
784 Ok(x.acos())
785 }),
786 "ATAN" | "ARCTAN" => unary_math(name, args, |x| Ok(x.atan())),
787 "ATAN2" => binary_math(name, args, |y, x| Ok(y.atan2(x))),
788 "COT" => unary_math(name, args, |x| {
789 let tan = x.tan();
790 if tan == 0.0 {
791 return Err("input must not produce zero tangent");
792 }
793 Ok(1.0 / tan)
794 }),
795 "DEGREES" => unary_math(name, args, |x| Ok(x.to_degrees())),
796 "RADIANS" => unary_math(name, args, |x| Ok(x.to_radians())),
797 "PI" => checked_math_result(name, std::f64::consts::PI),
798 other => Err(EvalError::UnknownFunction {
803 name: other.to_string(),
804 args: args.iter().map(|v| v.data_type()).collect(),
805 }),
806 }
807}
808
809fn unary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
810where
811 F: FnOnce(f64) -> Result<f64, &'static str>,
812{
813 let input = math_arg(name, args.first(), 0)?;
814 let value = op(input).map_err(|reason| EvalError::InvalidNumericResult {
815 function: name.to_string(),
816 reason: reason.to_string(),
817 })?;
818 checked_math_result(name, value)
819}
820
821fn binary_math<F>(name: &str, args: &[Value], op: F) -> Result<Value, EvalError>
822where
823 F: FnOnce(f64, f64) -> Result<f64, &'static str>,
824{
825 let left = math_arg(name, args.first(), 0)?;
826 let right = math_arg(name, args.get(1), 1)?;
827 let value = op(left, right).map_err(|reason| EvalError::InvalidNumericResult {
828 function: name.to_string(),
829 reason: reason.to_string(),
830 })?;
831 checked_math_result(name, value)
832}
833
834fn math_arg(name: &str, value: Option<&Value>, index: usize) -> Result<f64, EvalError> {
835 let value = value.ok_or_else(|| EvalError::UnknownFunction {
836 name: name.to_string(),
837 args: Vec::new(),
838 })?;
839 let numeric = as_f64(value);
840 if numeric.is_finite() {
841 Ok(numeric)
842 } else {
843 Err(EvalError::InvalidNumericResult {
844 function: name.to_string(),
845 reason: format!("argument {index} is NaN or infinite"),
846 })
847 }
848}
849
850fn checked_math_result(name: &str, value: f64) -> Result<Value, EvalError> {
851 if value.is_finite() {
852 Ok(Value::Float(value))
853 } else {
854 Err(EvalError::InvalidNumericResult {
855 function: name.to_string(),
856 reason: "result is NaN or infinite".to_string(),
857 })
858 }
859}
860
861fn json_extract_value(input: &Value, path: &Value, as_text: bool) -> Value {
862 let Value::Text(path) = path else {
863 return Value::Null;
864 };
865 let Some(json) = value_to_json(input) else {
866 return Value::Null;
867 };
868 let Some(steps) = parse_json_path(path) else {
869 return Value::Null;
870 };
871 let Some(target) = json_path_get(&json, &steps) else {
872 return Value::Null;
873 };
874
875 if as_text {
876 match target {
877 crate::serde_json::Value::String(value) => Value::text(value.clone()),
878 crate::serde_json::Value::Null => Value::Null,
879 crate::serde_json::Value::Bool(value) => Value::text(value.to_string()),
880 crate::serde_json::Value::Number(value) => Value::text(value.to_string()),
881 other => Value::text(other.to_string_compact()),
882 }
883 } else {
884 Value::text(target.to_string_compact())
885 }
886}
887
888fn contains_value(input: &Value, needle: &Value) -> Value {
889 let Value::Text(needle) = needle else {
890 return Value::Null;
891 };
892 Value::Boolean(value_contains(input, needle))
893}
894
895fn value_contains(value: &Value, needle: &str) -> bool {
896 match value {
897 Value::Array(values) => values.iter().any(|value| value_contains(value, needle)),
898 Value::Json(_) => value_to_json(value)
899 .as_ref()
900 .is_some_and(|json| json_value_contains(json, needle)),
901 Value::Text(value) => value.contains(needle),
902 other => other.display_string().contains(needle),
903 }
904}
905
906fn json_value_contains(value: &crate::serde_json::Value, needle: &str) -> bool {
907 match value {
908 crate::serde_json::Value::Array(values) => values
909 .iter()
910 .any(|value| json_value_contains(value, needle)),
911 crate::serde_json::Value::String(value) => value == needle,
912 crate::serde_json::Value::Number(value) => value.to_string() == needle,
913 crate::serde_json::Value::Bool(value) => value.to_string() == needle,
914 crate::serde_json::Value::Null | crate::serde_json::Value::Object(_) => false,
915 }
916}
917
918fn value_to_json(value: &Value) -> Option<crate::serde_json::Value> {
919 match value {
920 Value::Null => Some(crate::serde_json::Value::Null),
921 Value::Json(bytes) => crate::serde_json::from_slice(bytes).ok(),
922 Value::Text(value) => crate::serde_json::from_str(value).ok(),
923 _ => None,
924 }
925}
926
927enum JsonPathStep<'a> {
928 Field(&'a str),
929 Index(usize),
930}
931
932fn parse_json_path(path: &str) -> Option<Vec<JsonPathStep<'_>>> {
933 let path = path.trim();
934 let rest = path.strip_prefix('$').unwrap_or(path);
935 let mut steps = Vec::new();
936 let bytes = rest.as_bytes();
937 let mut index = 0;
938 while index < bytes.len() {
939 match bytes[index] {
940 b'.' => {
941 index += 1;
942 let start = index;
943 while index < bytes.len() && bytes[index] != b'.' && bytes[index] != b'[' {
944 index += 1;
945 }
946 if start == index {
947 return None;
948 }
949 steps.push(JsonPathStep::Field(
950 std::str::from_utf8(&bytes[start..index]).ok()?,
951 ));
952 }
953 b'[' => {
954 index += 1;
955 let start = index;
956 while index < bytes.len() && bytes[index] != b']' {
957 index += 1;
958 }
959 if index >= bytes.len() {
960 return None;
961 }
962 steps.push(JsonPathStep::Index(
963 std::str::from_utf8(&bytes[start..index])
964 .ok()?
965 .parse()
966 .ok()?,
967 ));
968 index += 1;
969 }
970 _ => return None,
971 }
972 }
973 Some(steps)
974}
975
976fn json_path_get<'a>(
977 root: &'a crate::serde_json::Value,
978 steps: &[JsonPathStep<'_>],
979) -> Option<&'a crate::serde_json::Value> {
980 let mut current = root;
981 for step in steps {
982 current = match (step, current) {
983 (JsonPathStep::Field(name), crate::serde_json::Value::Object(map)) => map.get(*name)?,
984 (JsonPathStep::Index(index), crate::serde_json::Value::Array(values)) => {
985 values.get(*index)?
986 }
987 _ => return None,
988 };
989 }
990 Some(current)
991}
992
993fn eval_case(
994 branches: &[(Expr, Expr)],
995 else_: Option<&Expr>,
996 row: &dyn Row,
997) -> Result<Value, EvalError> {
998 for (cond, value) in branches {
999 let c = evaluate(cond, row)?;
1000 if matches!(c, Value::Boolean(true)) {
1001 return evaluate(value, row);
1002 }
1003 }
1004 match else_ {
1005 Some(e) => evaluate(e, row),
1006 None => Ok(Value::Null),
1007 }
1008}
1009
1010fn eval_in_list(
1011 target: &Expr,
1012 values: &[Expr],
1013 negated: bool,
1014 row: &dyn Row,
1015) -> Result<Value, EvalError> {
1016 if values.is_empty() {
1017 return Err(EvalError::EmptyInList);
1018 }
1019 let needle = evaluate(target, row)?;
1020 if needle.is_null() {
1021 return Ok(Value::Null);
1022 }
1023 let mut saw_null = false;
1024 for v in values {
1025 let candidate = evaluate(v, row)?;
1026 if candidate.is_null() {
1027 saw_null = true;
1028 continue;
1029 }
1030 if values_equal(&needle, &candidate) {
1031 return Ok(Value::Boolean(!negated));
1032 }
1033 }
1034 if saw_null {
1035 return Ok(Value::Null);
1036 }
1037 Ok(Value::Boolean(negated))
1038}
1039
1040fn eval_between(
1041 target: &Expr,
1042 low: &Expr,
1043 high: &Expr,
1044 negated: bool,
1045 row: &dyn Row,
1046) -> Result<Value, EvalError> {
1047 let v = evaluate(target, row)?;
1048 let lo = evaluate(low, row)?;
1049 let hi = evaluate(high, row)?;
1050 if v.is_null() || lo.is_null() || hi.is_null() {
1051 return Ok(Value::Null);
1052 }
1053 let lo_ok = compare_values(&v, &lo)
1054 .ok_or(EvalError::OperatorMismatch {
1055 op: BinOp::Ge,
1056 lhs: v.data_type(),
1057 rhs: lo.data_type(),
1058 })
1059 .map(|o| o != std::cmp::Ordering::Less)?;
1060 let hi_ok = compare_values(&v, &hi)
1061 .ok_or(EvalError::OperatorMismatch {
1062 op: BinOp::Le,
1063 lhs: v.data_type(),
1064 rhs: hi.data_type(),
1065 })
1066 .map(|o| o != std::cmp::Ordering::Greater)?;
1067 let inside = lo_ok && hi_ok;
1068 Ok(Value::Boolean(if negated { !inside } else { inside }))
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074 use crate::storage::query::ast::Span;
1075
1076 fn lit(v: Value) -> Expr {
1077 Expr::Literal {
1078 value: v,
1079 span: Span::synthetic(),
1080 }
1081 }
1082
1083 fn binop(op: BinOp, l: Expr, r: Expr) -> Expr {
1084 Expr::BinaryOp {
1085 op,
1086 lhs: Box::new(l),
1087 rhs: Box::new(r),
1088 span: Span::synthetic(),
1089 }
1090 }
1091
1092 fn empty_row() -> impl Row {
1093 |_field: &FieldRef| -> Option<Value> { None }
1094 }
1095
1096 #[test]
1097 fn integer_addition_overflow_surfaces_as_eval_error() {
1098 let expr = binop(
1099 BinOp::Add,
1100 lit(Value::Integer(i64::MAX)),
1101 lit(Value::Integer(1)),
1102 );
1103 let err = evaluate(&expr, &empty_row()).unwrap_err();
1104 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Add });
1105 }
1106
1107 #[test]
1108 fn integer_multiplication_overflow_surfaces_as_eval_error() {
1109 let expr = binop(
1110 BinOp::Mul,
1111 lit(Value::Integer(i64::MAX)),
1112 lit(Value::Integer(2)),
1113 );
1114 let err = evaluate(&expr, &empty_row()).unwrap_err();
1115 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Mul });
1116 }
1117
1118 #[test]
1119 fn integer_subtraction_overflow_surfaces_as_eval_error() {
1120 let expr = binop(
1121 BinOp::Sub,
1122 lit(Value::Integer(i64::MIN)),
1123 lit(Value::Integer(1)),
1124 );
1125 let err = evaluate(&expr, &empty_row()).unwrap_err();
1126 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1127 }
1128
1129 #[test]
1130 fn unary_neg_overflow_on_min_int_surfaces_as_eval_error() {
1131 let expr = Expr::UnaryOp {
1132 op: UnaryOp::Neg,
1133 operand: Box::new(lit(Value::Integer(i64::MIN))),
1134 span: Span::synthetic(),
1135 };
1136 let err = evaluate(&expr, &empty_row()).unwrap_err();
1137 assert_eq!(err, EvalError::ArithmeticOverflow { op: BinOp::Sub });
1138 }
1139
1140 #[test]
1141 fn null_propagates_through_arithmetic() {
1142 let expr = binop(BinOp::Add, lit(Value::Null), lit(Value::Integer(7)));
1143 let v = evaluate(&expr, &empty_row()).unwrap();
1144 assert_eq!(v, Value::Null);
1145 }
1146
1147 #[test]
1148 fn null_propagates_through_comparison() {
1149 let expr = binop(BinOp::Lt, lit(Value::Integer(1)), lit(Value::Null));
1150 let v = evaluate(&expr, &empty_row()).unwrap();
1151 assert_eq!(v, Value::Null);
1152 }
1153
1154 #[test]
1155 fn null_propagates_through_concat() {
1156 let expr = binop(
1157 BinOp::Concat,
1158 lit(Value::Text(Arc::from("hi"))),
1159 lit(Value::Null),
1160 );
1161 let v = evaluate(&expr, &empty_row()).unwrap();
1162 assert_eq!(v, Value::Null);
1163 }
1164
1165 #[test]
1166 fn three_valued_and_returns_false_when_one_side_false_even_with_null() {
1167 let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(false)));
1168 let v = evaluate(&expr, &empty_row()).unwrap();
1169 assert_eq!(v, Value::Boolean(false));
1170 }
1171
1172 #[test]
1173 fn three_valued_or_returns_true_when_one_side_true_even_with_null() {
1174 let expr = binop(BinOp::Or, lit(Value::Null), lit(Value::Boolean(true)));
1175 let v = evaluate(&expr, &empty_row()).unwrap();
1176 assert_eq!(v, Value::Boolean(true));
1177 }
1178
1179 #[test]
1180 fn three_valued_and_returns_null_for_null_and_true() {
1181 let expr = binop(BinOp::And, lit(Value::Null), lit(Value::Boolean(true)));
1182 let v = evaluate(&expr, &empty_row()).unwrap();
1183 assert_eq!(v, Value::Null);
1184 }
1185
1186 #[test]
1187 fn implicit_cast_triggers_for_decimal_plus_integer() {
1188 let expr = binop(
1197 BinOp::Add,
1198 lit(Value::Decimal(10000)),
1199 lit(Value::Integer(2)),
1200 );
1201 let v = evaluate(&expr, &empty_row()).unwrap();
1202 assert_eq!(v, Value::Decimal(30000));
1203 }
1204
1205 #[test]
1206 fn integer_plus_bigint_resolves_to_preferred_float_overload() {
1207 let expr = binop(
1213 BinOp::Add,
1214 lit(Value::Integer(5)),
1215 lit(Value::BigInt(40_000_000_000)),
1216 );
1217 let v = evaluate(&expr, &empty_row()).unwrap();
1218 assert_eq!(v, Value::Float(40_000_000_005.0));
1219 }
1220
1221 #[test]
1222 fn implicit_cast_promotes_integer_to_float_for_float_addition() {
1223 let expr = binop(BinOp::Add, lit(Value::Integer(2)), lit(Value::Float(0.5)));
1227 let v = evaluate(&expr, &empty_row()).unwrap();
1228 assert_eq!(v, Value::Float(2.5));
1229 }
1230
1231 #[test]
1232 fn integer_division_promotes_to_float() {
1233 let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(2)));
1234 let v = evaluate(&expr, &empty_row()).unwrap();
1235 assert_eq!(v, Value::Float(3.5));
1236 }
1237
1238 #[test]
1239 fn division_by_zero_is_eval_error() {
1240 let expr = binop(BinOp::Div, lit(Value::Integer(7)), lit(Value::Integer(0)));
1241 let err = evaluate(&expr, &empty_row()).unwrap_err();
1242 assert_eq!(err, EvalError::DivisionByZero);
1243 }
1244
1245 #[test]
1246 fn unknown_function_surfaces_as_eval_error() {
1247 let expr = Expr::FunctionCall {
1248 name: "no_such_fn".to_string(),
1249 args: vec![lit(Value::Integer(1))],
1250 span: Span::synthetic(),
1251 };
1252 let err = evaluate(&expr, &empty_row()).unwrap_err();
1253 match err {
1254 EvalError::UnknownFunction { name, args } => {
1255 assert_eq!(name, "no_such_fn");
1256 assert_eq!(args, vec![DataType::Integer]);
1257 }
1258 other => panic!("expected UnknownFunction, got {other:?}"),
1259 }
1260 }
1261
1262 #[test]
1263 fn coalesce_returns_first_non_null() {
1264 let expr = Expr::FunctionCall {
1265 name: "COALESCE".to_string(),
1266 args: vec![
1267 lit(Value::Null),
1268 lit(Value::Null),
1269 lit(Value::Integer(42)),
1270 lit(Value::Integer(99)),
1271 ],
1272 span: Span::synthetic(),
1273 };
1274 let v = evaluate(&expr, &empty_row()).unwrap();
1275 assert_eq!(v, Value::Integer(42));
1276 }
1277
1278 #[test]
1279 fn coalesce_all_null_returns_null() {
1280 let expr = Expr::FunctionCall {
1281 name: "COALESCE".to_string(),
1282 args: vec![lit(Value::Null), lit(Value::Null)],
1283 span: Span::synthetic(),
1284 };
1285 let v = evaluate(&expr, &empty_row()).unwrap();
1286 assert_eq!(v, Value::Null);
1287 }
1288
1289 #[test]
1290 fn upper_lower_dispatch_through_function_catalog() {
1291 let expr = Expr::FunctionCall {
1292 name: "UPPER".to_string(),
1293 args: vec![lit(Value::Text(Arc::from("hello")))],
1294 span: Span::synthetic(),
1295 };
1296 let v = evaluate(&expr, &empty_row()).unwrap();
1297 assert_eq!(v, Value::Text(Arc::from("HELLO")));
1298 }
1299
1300 #[test]
1301 fn length_of_null_propagates() {
1302 let expr = Expr::FunctionCall {
1303 name: "LENGTH".to_string(),
1304 args: vec![lit(Value::Null)],
1305 span: Span::synthetic(),
1306 };
1307 let v = evaluate(&expr, &empty_row()).unwrap();
1308 assert_eq!(v, Value::Null);
1309 }
1310
1311 #[test]
1312 fn case_when_picks_first_true_branch() {
1313 let expr = Expr::Case {
1314 branches: vec![
1315 (lit(Value::Boolean(false)), lit(Value::Integer(1))),
1316 (lit(Value::Boolean(true)), lit(Value::Integer(2))),
1317 (lit(Value::Boolean(true)), lit(Value::Integer(3))),
1318 ],
1319 else_: Some(Box::new(lit(Value::Integer(99)))),
1320 span: Span::synthetic(),
1321 };
1322 let v = evaluate(&expr, &empty_row()).unwrap();
1323 assert_eq!(v, Value::Integer(2));
1324 }
1325
1326 #[test]
1327 fn case_falls_through_to_else_when_no_branch_matches() {
1328 let expr = Expr::Case {
1329 branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1330 else_: Some(Box::new(lit(Value::Integer(99)))),
1331 span: Span::synthetic(),
1332 };
1333 let v = evaluate(&expr, &empty_row()).unwrap();
1334 assert_eq!(v, Value::Integer(99));
1335 }
1336
1337 #[test]
1338 fn case_returns_null_when_no_branch_matches_and_no_else() {
1339 let expr = Expr::Case {
1340 branches: vec![(lit(Value::Boolean(false)), lit(Value::Integer(1)))],
1341 else_: None,
1342 span: Span::synthetic(),
1343 };
1344 let v = evaluate(&expr, &empty_row()).unwrap();
1345 assert_eq!(v, Value::Null);
1346 }
1347
1348 #[test]
1349 fn is_null_handles_null_and_non_null() {
1350 let null_expr = Expr::IsNull {
1351 operand: Box::new(lit(Value::Null)),
1352 negated: false,
1353 span: Span::synthetic(),
1354 };
1355 assert_eq!(
1356 evaluate(&null_expr, &empty_row()).unwrap(),
1357 Value::Boolean(true)
1358 );
1359
1360 let non_null_expr = Expr::IsNull {
1361 operand: Box::new(lit(Value::Integer(7))),
1362 negated: false,
1363 span: Span::synthetic(),
1364 };
1365 assert_eq!(
1366 evaluate(&non_null_expr, &empty_row()).unwrap(),
1367 Value::Boolean(false)
1368 );
1369 }
1370
1371 #[test]
1372 fn between_inclusive_bounds() {
1373 let expr = Expr::Between {
1374 target: Box::new(lit(Value::Integer(5))),
1375 low: Box::new(lit(Value::Integer(1))),
1376 high: Box::new(lit(Value::Integer(10))),
1377 negated: false,
1378 span: Span::synthetic(),
1379 };
1380 assert_eq!(evaluate(&expr, &empty_row()).unwrap(), Value::Boolean(true));
1381 }
1382
1383 #[test]
1384 fn in_list_match_and_miss() {
1385 let hit = Expr::InList {
1386 target: Box::new(lit(Value::Integer(2))),
1387 values: vec![
1388 lit(Value::Integer(1)),
1389 lit(Value::Integer(2)),
1390 lit(Value::Integer(3)),
1391 ],
1392 negated: false,
1393 span: Span::synthetic(),
1394 };
1395 assert_eq!(evaluate(&hit, &empty_row()).unwrap(), Value::Boolean(true));
1396
1397 let miss = Expr::InList {
1398 target: Box::new(lit(Value::Integer(99))),
1399 values: vec![lit(Value::Integer(1)), lit(Value::Integer(2))],
1400 negated: false,
1401 span: Span::synthetic(),
1402 };
1403 assert_eq!(
1404 evaluate(&miss, &empty_row()).unwrap(),
1405 Value::Boolean(false)
1406 );
1407 }
1408
1409 #[test]
1410 fn column_lookup_walks_field_ref() {
1411 let row = |field: &FieldRef| -> Option<Value> {
1412 match field {
1413 FieldRef::TableColumn { table, column } if table == "t" && column == "x" => {
1414 Some(Value::Integer(11))
1415 }
1416 _ => None,
1417 }
1418 };
1419 let expr = Expr::Column {
1420 field: FieldRef::TableColumn {
1421 table: "t".to_string(),
1422 column: "x".to_string(),
1423 },
1424 span: Span::synthetic(),
1425 };
1426 assert_eq!(evaluate(&expr, &row).unwrap(), Value::Integer(11));
1427 }
1428
1429 #[test]
1430 fn missing_column_surfaces_unknown_column() {
1431 let row = |_: &FieldRef| -> Option<Value> { None };
1432 let expr = Expr::Column {
1433 field: FieldRef::TableColumn {
1434 table: "t".to_string(),
1435 column: "missing".to_string(),
1436 },
1437 span: Span::synthetic(),
1438 };
1439 let err = evaluate(&expr, &row).unwrap_err();
1440 match err {
1441 EvalError::UnknownColumn(_) => {}
1442 other => panic!("expected UnknownColumn, got {other:?}"),
1443 }
1444 }
1445
1446 #[test]
1447 fn parameter_without_bind_is_eval_error() {
1448 let expr = Expr::Parameter {
1449 index: 1,
1450 span: Span::synthetic(),
1451 };
1452 let err = evaluate(&expr, &empty_row()).unwrap_err();
1453 assert_eq!(err, EvalError::UnboundParameter(1));
1454 }
1455
1456 #[test]
1457 fn cast_integer_to_text_uses_explicit_path() {
1458 let expr = Expr::Cast {
1459 inner: Box::new(lit(Value::Integer(42))),
1460 target: DataType::Text,
1461 span: Span::synthetic(),
1462 };
1463 assert_eq!(
1464 evaluate(&expr, &empty_row()).unwrap(),
1465 Value::Text(Arc::from("42"))
1466 );
1467 }
1468
1469 #[test]
1470 fn concat_returns_text() {
1471 let expr = binop(
1472 BinOp::Concat,
1473 lit(Value::Text(Arc::from("foo"))),
1474 lit(Value::Text(Arc::from("bar"))),
1475 );
1476 assert_eq!(
1477 evaluate(&expr, &empty_row()).unwrap(),
1478 Value::Text(Arc::from("foobar"))
1479 );
1480 }
1481}