1use crate::error::{QueryError, Result};
40use crate::schema::{ColumnName, DataType};
41use crate::value::Value;
42
43#[derive(Debug, Clone)]
49pub enum ScalarExpr {
50 Literal(Value),
52 Column(ColumnName),
54
55 Upper(Box<ScalarExpr>),
58 Lower(Box<ScalarExpr>),
60 Length(Box<ScalarExpr>),
62 Trim(Box<ScalarExpr>),
64 Concat(Vec<ScalarExpr>),
67
68 Abs(Box<ScalarExpr>),
73 Round(Box<ScalarExpr>),
75 RoundScale(Box<ScalarExpr>, i32),
79 Ceil(Box<ScalarExpr>),
81 Floor(Box<ScalarExpr>),
83
84 Coalesce(Vec<ScalarExpr>),
87 Nullif(Box<ScalarExpr>, Box<ScalarExpr>),
89
90 Cast(Box<ScalarExpr>, DataType),
97}
98
99pub struct EvalContext<'a> {
102 pub columns: &'a [ColumnName],
103 pub row: &'a [Value],
104}
105
106impl<'a> EvalContext<'a> {
107 pub fn new(columns: &'a [ColumnName], row: &'a [Value]) -> Self {
108 assert!(
109 columns.len() == row.len(),
110 "EvalContext precondition: columns and row must have equal length",
111 );
112 Self { columns, row }
113 }
114
115 fn lookup(&self, name: &ColumnName) -> Result<&Value> {
116 self.columns
117 .iter()
118 .position(|c| c == name)
119 .and_then(|idx| self.row.get(idx))
120 .ok_or_else(|| QueryError::ColumnNotFound {
121 table: String::new(),
122 column: name.to_string(),
123 })
124 }
125}
126
127pub fn evaluate(expr: &ScalarExpr, ctx: &EvalContext<'_>) -> Result<Value> {
134 match expr {
135 ScalarExpr::Literal(v) => Ok(v.clone()),
136 ScalarExpr::Column(name) => Ok(ctx.lookup(name)?.clone()),
137
138 ScalarExpr::Upper(inner) => match evaluate(inner, ctx)? {
140 Value::Null => Ok(Value::Null),
141 Value::Text(s) => Ok(Value::Text(s.to_uppercase())),
142 other => Err(type_error("UPPER", "Text", &other)),
143 },
144 ScalarExpr::Lower(inner) => match evaluate(inner, ctx)? {
145 Value::Null => Ok(Value::Null),
146 Value::Text(s) => Ok(Value::Text(s.to_lowercase())),
147 other => Err(type_error("LOWER", "Text", &other)),
148 },
149 ScalarExpr::Length(inner) => match evaluate(inner, ctx)? {
150 Value::Null => Ok(Value::Null),
151 Value::Text(s) => {
152 let chars = s.chars().count();
154 debug_assert_eq!(chars, s.chars().count());
157 Ok(Value::BigInt(chars as i64))
158 }
159 other => Err(type_error("LENGTH", "Text", &other)),
160 },
161 ScalarExpr::Trim(inner) => match evaluate(inner, ctx)? {
162 Value::Null => Ok(Value::Null),
163 Value::Text(s) => Ok(Value::Text(s.trim().to_string())),
164 other => Err(type_error("TRIM", "Text", &other)),
165 },
166 ScalarExpr::Concat(parts) => {
167 assert!(
168 !parts.is_empty(),
169 "CONCAT precondition: at least one argument"
170 );
171 let mut out = String::new();
172 for p in parts {
173 match evaluate(p, ctx)? {
174 Value::Null => return Ok(Value::Null),
175 Value::Text(s) => out.push_str(&s),
176 other => return Err(type_error("CONCAT", "Text", &other)),
177 }
178 }
179 Ok(Value::Text(out))
180 }
181
182 ScalarExpr::Abs(inner) => match evaluate(inner, ctx)? {
184 Value::Null => Ok(Value::Null),
185 Value::TinyInt(n) => Ok(Value::TinyInt(n.saturating_abs())),
186 Value::SmallInt(n) => Ok(Value::SmallInt(n.saturating_abs())),
187 Value::Integer(n) => Ok(Value::Integer(n.saturating_abs())),
188 Value::BigInt(n) => Ok(Value::BigInt(n.saturating_abs())),
189 Value::Real(n) => Ok(Value::Real(n.abs())),
190 Value::Decimal(val, scale) => Ok(Value::Decimal(val.saturating_abs(), scale)),
191 other => Err(type_error("ABS", "Numeric", &other)),
192 },
193 ScalarExpr::Round(inner) => match evaluate(inner, ctx)? {
194 Value::Null => Ok(Value::Null),
195 v @ (Value::TinyInt(_) | Value::SmallInt(_) | Value::Integer(_) | Value::BigInt(_)) => {
197 Ok(v)
198 }
199 Value::Real(x) => Ok(Value::Real(x.round())),
200 Value::Decimal(val, scale) => Ok(decimal_round_to_scale(val, scale, 0)),
201 other => Err(type_error("ROUND", "Numeric", &other)),
202 },
203 ScalarExpr::RoundScale(inner, target_scale) => {
204 assert!(
205 *target_scale >= 0 && *target_scale < i32::from(u8::MAX),
206 "ROUND scale must fit in a non-negative u8",
207 );
208 let target = u8::try_from(*target_scale).unwrap_or(0);
209 match evaluate(inner, ctx)? {
210 Value::Null => Ok(Value::Null),
211 v @ (Value::TinyInt(_)
212 | Value::SmallInt(_)
213 | Value::Integer(_)
214 | Value::BigInt(_)) => Ok(v),
215 Value::Real(x) => {
216 let factor = 10f64.powi(i32::from(target));
219 Ok(Value::Real((x * factor).round() / factor))
220 }
221 Value::Decimal(val, scale) => Ok(decimal_round_to_scale(val, scale, target)),
222 other => Err(type_error("ROUND", "Numeric", &other)),
223 }
224 }
225 ScalarExpr::Ceil(inner) => match evaluate(inner, ctx)? {
226 Value::Null => Ok(Value::Null),
227 v @ (Value::TinyInt(_) | Value::SmallInt(_) | Value::Integer(_) | Value::BigInt(_)) => {
228 Ok(v)
229 }
230 Value::Real(x) => Ok(Value::Real(x.ceil())),
231 Value::Decimal(val, scale) => {
232 if scale == 0 {
233 Ok(Value::Decimal(val, 0))
234 } else {
235 Ok(decimal_ceil(val, scale))
236 }
237 }
238 other => Err(type_error("CEIL", "Numeric", &other)),
239 },
240 ScalarExpr::Floor(inner) => match evaluate(inner, ctx)? {
241 Value::Null => Ok(Value::Null),
242 v @ (Value::TinyInt(_) | Value::SmallInt(_) | Value::Integer(_) | Value::BigInt(_)) => {
243 Ok(v)
244 }
245 Value::Real(x) => Ok(Value::Real(x.floor())),
246 Value::Decimal(val, scale) => {
247 if scale == 0 {
248 Ok(Value::Decimal(val, 0))
249 } else {
250 Ok(decimal_floor(val, scale))
251 }
252 }
253 other => Err(type_error("FLOOR", "Numeric", &other)),
254 },
255
256 ScalarExpr::Coalesce(exprs) => {
258 assert!(
259 !exprs.is_empty(),
260 "COALESCE precondition: at least one argument"
261 );
262 for e in exprs {
263 let v = evaluate(e, ctx)?;
264 if !matches!(v, Value::Null) {
265 return Ok(v);
266 }
267 }
268 Ok(Value::Null)
269 }
270 ScalarExpr::Nullif(a, b) => {
271 let av = evaluate(a, ctx)?;
272 let bv = evaluate(b, ctx)?;
273 if av == bv { Ok(Value::Null) } else { Ok(av) }
274 }
275
276 ScalarExpr::Cast(inner, target) => cast_value(evaluate(inner, ctx)?, *target),
278 }
279}
280
281fn cast_value(value: Value, target: DataType) -> Result<Value> {
288 if matches!(value, Value::Null) {
289 return Ok(Value::Null);
290 }
291 match (value, target) {
292 (v @ Value::TinyInt(_), DataType::TinyInt)
295 | (v @ Value::SmallInt(_), DataType::SmallInt)
296 | (v @ Value::Integer(_), DataType::Integer)
297 | (v @ Value::BigInt(_), DataType::BigInt)
298 | (v @ Value::Real(_), DataType::Real)
299 | (v @ Value::Text(_), DataType::Text)
300 | (v @ Value::Bytes(_), DataType::Bytes)
301 | (v @ Value::Boolean(_), DataType::Boolean)
302 | (v @ Value::Date(_), DataType::Date)
303 | (v @ Value::Time(_), DataType::Time)
304 | (v @ Value::Timestamp(_), DataType::Timestamp)
305 | (v @ Value::Uuid(_), DataType::Uuid)
306 | (v @ Value::Json(_), DataType::Json) => Ok(v),
307
308 (Value::TinyInt(n), DataType::SmallInt) => Ok(Value::SmallInt(i16::from(n))),
310 (Value::TinyInt(n), DataType::Integer) => Ok(Value::Integer(i32::from(n))),
311 (Value::TinyInt(n), DataType::BigInt) => Ok(Value::BigInt(i64::from(n))),
312 (Value::SmallInt(n), DataType::Integer) => Ok(Value::Integer(i32::from(n))),
313 (Value::SmallInt(n), DataType::BigInt) => Ok(Value::BigInt(i64::from(n))),
314 (Value::Integer(n), DataType::BigInt) => Ok(Value::BigInt(i64::from(n))),
315
316 (Value::SmallInt(n), DataType::TinyInt) => i8::try_from(n)
318 .map(Value::TinyInt)
319 .map_err(|_| cast_error("SmallInt", "TinyInt", "overflow")),
320 (Value::Integer(n), DataType::TinyInt) => i8::try_from(n)
321 .map(Value::TinyInt)
322 .map_err(|_| cast_error("Integer", "TinyInt", "overflow")),
323 (Value::Integer(n), DataType::SmallInt) => i16::try_from(n)
324 .map(Value::SmallInt)
325 .map_err(|_| cast_error("Integer", "SmallInt", "overflow")),
326 (Value::BigInt(n), DataType::TinyInt) => i8::try_from(n)
327 .map(Value::TinyInt)
328 .map_err(|_| cast_error("BigInt", "TinyInt", "overflow")),
329 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n)
330 .map(Value::SmallInt)
331 .map_err(|_| cast_error("BigInt", "SmallInt", "overflow")),
332 (Value::BigInt(n), DataType::Integer) => i32::try_from(n)
333 .map(Value::Integer)
334 .map_err(|_| cast_error("BigInt", "Integer", "overflow")),
335
336 (Value::TinyInt(n), DataType::Real) => Ok(Value::Real(f64::from(n))),
339 (Value::SmallInt(n), DataType::Real) => Ok(Value::Real(f64::from(n))),
340 (Value::Integer(n), DataType::Real) => Ok(Value::Real(f64::from(n))),
341 #[allow(clippy::cast_precision_loss)]
342 (Value::BigInt(n), DataType::Real) => Ok(Value::Real(n as f64)),
343
344 (Value::Real(x), DataType::TinyInt) => f64_to_int::<i8>(x, "TinyInt").map(Value::TinyInt),
346 (Value::Real(x), DataType::SmallInt) => {
347 f64_to_int::<i16>(x, "SmallInt").map(Value::SmallInt)
348 }
349 (Value::Real(x), DataType::Integer) => f64_to_int::<i32>(x, "Integer").map(Value::Integer),
350 (Value::Real(x), DataType::BigInt) => f64_to_int::<i64>(x, "BigInt").map(Value::BigInt),
351
352 (Value::Text(s), DataType::TinyInt) => s
354 .trim()
355 .parse::<i8>()
356 .map(Value::TinyInt)
357 .map_err(|_| cast_error("Text", "TinyInt", &s)),
358 (Value::Text(s), DataType::SmallInt) => s
359 .trim()
360 .parse::<i16>()
361 .map(Value::SmallInt)
362 .map_err(|_| cast_error("Text", "SmallInt", &s)),
363 (Value::Text(s), DataType::Integer) => s
364 .trim()
365 .parse::<i32>()
366 .map(Value::Integer)
367 .map_err(|_| cast_error("Text", "Integer", &s)),
368 (Value::Text(s), DataType::BigInt) => s
369 .trim()
370 .parse::<i64>()
371 .map(Value::BigInt)
372 .map_err(|_| cast_error("Text", "BigInt", &s)),
373 (Value::Text(s), DataType::Real) => s
374 .trim()
375 .parse::<f64>()
376 .map(Value::Real)
377 .map_err(|_| cast_error("Text", "Real", &s)),
378 (Value::Text(s), DataType::Boolean) => match s.trim().to_ascii_lowercase().as_str() {
379 "true" | "t" | "1" => Ok(Value::Boolean(true)),
380 "false" | "f" | "0" => Ok(Value::Boolean(false)),
381 _ => Err(cast_error("Text", "Boolean", &s)),
382 },
383
384 (Value::TinyInt(n), DataType::Text) => Ok(Value::Text(n.to_string())),
386 (Value::SmallInt(n), DataType::Text) => Ok(Value::Text(n.to_string())),
387 (Value::Integer(n), DataType::Text) => Ok(Value::Text(n.to_string())),
388 (Value::BigInt(n), DataType::Text) => Ok(Value::Text(n.to_string())),
389 (Value::Real(n), DataType::Text) => Ok(Value::Text(n.to_string())),
390 (Value::Boolean(b), DataType::Text) => {
391 Ok(Value::Text(if b { "true" } else { "false" }.to_string()))
392 }
393
394 (v, t) => Err(QueryError::TypeMismatch {
396 expected: format!("CAST to {t:?}"),
397 actual: format!("{v:?}"),
398 }),
399 }
400}
401
402fn cast_error(from: &str, to: &str, detail: &str) -> QueryError {
403 QueryError::TypeMismatch {
404 expected: format!("CAST from {from} to {to}"),
405 actual: detail.to_string(),
406 }
407}
408
409fn f64_to_int<T>(x: f64, target: &str) -> Result<T>
412where
413 T: TryFrom<i64>,
414{
415 if !x.is_finite() {
416 return Err(cast_error("Real", target, &format!("{x}")));
417 }
418 let truncated = x.trunc();
420 #[allow(clippy::cast_possible_truncation)]
422 let as_i64 = if (i64::MIN as f64) <= truncated && truncated <= (i64::MAX as f64) {
423 truncated as i64
424 } else {
425 return Err(cast_error("Real", target, &format!("{x}")));
426 };
427 T::try_from(as_i64).map_err(|_| cast_error("Real", target, &format!("{x}")))
428}
429
430fn type_error(func: &str, expected: &str, got: &Value) -> QueryError {
431 QueryError::TypeMismatch {
432 expected: format!("{func} argument of type {expected}"),
433 actual: format!("{got:?}"),
434 }
435}
436
437fn decimal_round_to_scale(val: i128, from_scale: u8, to_scale: u8) -> Value {
442 if from_scale == to_scale {
443 return Value::Decimal(val, to_scale);
444 }
445 if to_scale > from_scale {
446 let diff = u32::from(to_scale - from_scale);
447 let factor = 10i128.pow(diff);
448 return Value::Decimal(val.saturating_mul(factor), to_scale);
449 }
450 let diff = u32::from(from_scale - to_scale);
452 let divisor = 10i128.pow(diff);
453 let half = divisor / 2;
454 let rounded = if val >= 0 {
455 (val + half) / divisor
456 } else {
457 (val - half) / divisor
458 };
459 Value::Decimal(rounded, to_scale)
460}
461
462fn decimal_ceil(val: i128, scale: u8) -> Value {
463 let divisor = 10i128.pow(u32::from(scale));
464 let floor_val = val / divisor;
465 let remainder = val % divisor;
466 let ceil = if remainder > 0 {
467 floor_val + 1
468 } else {
469 floor_val
470 };
471 Value::Decimal(ceil, 0)
472}
473
474fn decimal_floor(val: i128, scale: u8) -> Value {
475 let divisor = 10i128.pow(u32::from(scale));
476 let floor_val = val / divisor;
477 let remainder = val % divisor;
478 let floor = if remainder < 0 {
480 floor_val - 1
481 } else {
482 floor_val
483 };
484 Value::Decimal(floor, 0)
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 fn ctx_empty() -> (Vec<ColumnName>, Vec<Value>) {
492 (Vec::new(), Vec::new())
493 }
494
495 fn lit(v: Value) -> ScalarExpr {
496 ScalarExpr::Literal(v)
497 }
498
499 fn eval_standalone(expr: &ScalarExpr) -> Result<Value> {
500 let (cols, row) = ctx_empty();
501 evaluate(expr, &EvalContext::new(&cols, &row))
502 }
503
504 #[test]
505 fn upper_lower_length_trim() {
506 assert_eq!(
507 eval_standalone(&ScalarExpr::Upper(Box::new(lit(Value::Text(
508 "hello".into()
509 )))))
510 .unwrap(),
511 Value::Text("HELLO".into()),
512 );
513 assert_eq!(
514 eval_standalone(&ScalarExpr::Lower(Box::new(lit(Value::Text(
515 "WORLD".into()
516 )))))
517 .unwrap(),
518 Value::Text("world".into()),
519 );
520 assert_eq!(
521 eval_standalone(&ScalarExpr::Length(Box::new(lit(Value::Text(
522 "café".into()
523 )))))
524 .unwrap(),
525 Value::BigInt(4),
526 "LENGTH is char count, not byte count",
527 );
528 assert_eq!(
529 eval_standalone(&ScalarExpr::Trim(Box::new(lit(Value::Text(
530 " hi ".into(),
531 )))))
532 .unwrap(),
533 Value::Text("hi".into()),
534 );
535 }
536
537 #[test]
538 fn concat_propagates_null_like_postgres() {
539 let ex = ScalarExpr::Concat(vec![
540 lit(Value::Text("a".into())),
541 lit(Value::Null),
542 lit(Value::Text("b".into())),
543 ]);
544 assert_eq!(eval_standalone(&ex).unwrap(), Value::Null);
545 }
546
547 #[test]
548 fn abs_preserves_subtype() {
549 assert_eq!(
550 eval_standalone(&ScalarExpr::Abs(Box::new(lit(Value::Integer(-5))))).unwrap(),
551 Value::Integer(5),
552 );
553 assert_eq!(
554 eval_standalone(&ScalarExpr::Abs(Box::new(lit(Value::Real(-1.5))))).unwrap(),
555 Value::Real(1.5),
556 );
557 }
558
559 #[test]
560 fn round_with_scale_rounds_decimal() {
561 let rounded = eval_standalone(&ScalarExpr::RoundScale(
563 Box::new(lit(Value::Decimal(12345, 2))),
564 1,
565 ))
566 .unwrap();
567 assert_eq!(rounded, Value::Decimal(1235, 1));
568
569 let rounded = eval_standalone(&ScalarExpr::RoundScale(
571 Box::new(lit(Value::Decimal(12344, 2))),
572 1,
573 ))
574 .unwrap();
575 assert_eq!(rounded, Value::Decimal(1234, 1));
576
577 let rounded = eval_standalone(&ScalarExpr::RoundScale(
579 Box::new(lit(Value::Decimal(-12345, 2))),
580 1,
581 ))
582 .unwrap();
583 assert_eq!(rounded, Value::Decimal(-1235, 1));
584 }
585
586 #[test]
587 fn ceil_and_floor_decimal() {
588 let c =
589 eval_standalone(&ScalarExpr::Ceil(Box::new(lit(Value::Decimal(12345, 2))))).unwrap();
590 assert_eq!(c, Value::Decimal(124, 0));
591 let f =
592 eval_standalone(&ScalarExpr::Floor(Box::new(lit(Value::Decimal(12345, 2))))).unwrap();
593 assert_eq!(f, Value::Decimal(123, 0));
594 }
595
596 #[test]
597 fn coalesce_returns_first_non_null() {
598 let ex = ScalarExpr::Coalesce(vec![
599 lit(Value::Null),
600 lit(Value::Null),
601 lit(Value::BigInt(42)),
602 lit(Value::BigInt(99)),
603 ]);
604 assert_eq!(eval_standalone(&ex).unwrap(), Value::BigInt(42));
605 }
606
607 #[test]
608 fn nullif_returns_null_when_equal() {
609 let eq = ScalarExpr::Nullif(
610 Box::new(lit(Value::Text("x".into()))),
611 Box::new(lit(Value::Text("x".into()))),
612 );
613 assert_eq!(eval_standalone(&eq).unwrap(), Value::Null);
614 let ne = ScalarExpr::Nullif(
615 Box::new(lit(Value::Text("x".into()))),
616 Box::new(lit(Value::Text("y".into()))),
617 );
618 assert_eq!(eval_standalone(&ne).unwrap(), Value::Text("x".into()));
619 }
620
621 #[test]
622 fn column_reference_resolves() {
623 let cols = vec![ColumnName::new(String::from("name"))];
624 let row = vec![Value::Text("Ada".into())];
625 let ctx = EvalContext::new(&cols, &row);
626 let ex = ScalarExpr::Upper(Box::new(ScalarExpr::Column(ColumnName::new(String::from(
627 "name",
628 )))));
629 assert_eq!(evaluate(&ex, &ctx).unwrap(), Value::Text("ADA".into()));
630 }
631
632 #[test]
633 fn null_input_propagates_through_scalar_fns() {
634 for expr in [
635 ScalarExpr::Upper(Box::new(lit(Value::Null))),
636 ScalarExpr::Lower(Box::new(lit(Value::Null))),
637 ScalarExpr::Length(Box::new(lit(Value::Null))),
638 ScalarExpr::Trim(Box::new(lit(Value::Null))),
639 ScalarExpr::Abs(Box::new(lit(Value::Null))),
640 ScalarExpr::Round(Box::new(lit(Value::Null))),
641 ScalarExpr::Ceil(Box::new(lit(Value::Null))),
642 ScalarExpr::Floor(Box::new(lit(Value::Null))),
643 ScalarExpr::Cast(Box::new(lit(Value::Null)), DataType::Integer),
644 ] {
645 assert_eq!(eval_standalone(&expr).unwrap(), Value::Null);
646 }
647 }
648
649 #[test]
650 fn cast_integer_widening_and_narrowing() {
651 let w = eval_standalone(&ScalarExpr::Cast(
653 Box::new(lit(Value::TinyInt(42))),
654 DataType::BigInt,
655 ))
656 .unwrap();
657 assert_eq!(w, Value::BigInt(42));
658
659 let ok = eval_standalone(&ScalarExpr::Cast(
661 Box::new(lit(Value::BigInt(127))),
662 DataType::TinyInt,
663 ))
664 .unwrap();
665 assert_eq!(ok, Value::TinyInt(127));
666
667 let err = eval_standalone(&ScalarExpr::Cast(
669 Box::new(lit(Value::BigInt(i64::from(i16::MAX) + 1))),
670 DataType::SmallInt,
671 ));
672 assert!(err.is_err(), "narrowing overflow must be an error");
673 }
674
675 #[test]
676 fn cast_text_to_numeric_parses() {
677 assert_eq!(
678 eval_standalone(&ScalarExpr::Cast(
679 Box::new(lit(Value::Text("42".into()))),
680 DataType::Integer,
681 ))
682 .unwrap(),
683 Value::Integer(42),
684 );
685 assert_eq!(
686 eval_standalone(&ScalarExpr::Cast(
687 Box::new(lit(Value::Text("1.5".into()))),
688 DataType::Real,
689 ))
690 .unwrap(),
691 Value::Real(1.5),
692 );
693 assert!(
694 eval_standalone(&ScalarExpr::Cast(
695 Box::new(lit(Value::Text("nope".into()))),
696 DataType::Integer,
697 ))
698 .is_err(),
699 "unparseable text must error rather than coerce to 0"
700 );
701 }
702
703 #[test]
704 fn cast_numeric_to_text_formats_canonically() {
705 assert_eq!(
706 eval_standalone(&ScalarExpr::Cast(
707 Box::new(lit(Value::BigInt(99))),
708 DataType::Text,
709 ))
710 .unwrap(),
711 Value::Text("99".into()),
712 );
713 assert_eq!(
714 eval_standalone(&ScalarExpr::Cast(
715 Box::new(lit(Value::Boolean(true))),
716 DataType::Text,
717 ))
718 .unwrap(),
719 Value::Text("true".into()),
720 );
721 }
722
723 #[test]
724 fn cast_real_to_int_truncates_toward_zero() {
725 assert_eq!(
726 eval_standalone(&ScalarExpr::Cast(
727 Box::new(lit(Value::Real(1.9))),
728 DataType::Integer,
729 ))
730 .unwrap(),
731 Value::Integer(1),
732 );
733 assert_eq!(
734 eval_standalone(&ScalarExpr::Cast(
735 Box::new(lit(Value::Real(-1.9))),
736 DataType::Integer,
737 ))
738 .unwrap(),
739 Value::Integer(-1),
740 );
741 assert!(
742 eval_standalone(&ScalarExpr::Cast(
743 Box::new(lit(Value::Real(f64::NAN))),
744 DataType::Integer,
745 ))
746 .is_err(),
747 "NaN cast must error"
748 );
749 }
750
751 #[test]
752 fn cast_text_to_boolean_accepts_common_literals() {
753 for (s, want) in [
754 ("true", true),
755 ("TRUE", true),
756 ("t", true),
757 ("1", true),
758 ("false", false),
759 ("F", false),
760 ("0", false),
761 ] {
762 assert_eq!(
763 eval_standalone(&ScalarExpr::Cast(
764 Box::new(lit(Value::Text(s.into()))),
765 DataType::Boolean,
766 ))
767 .unwrap(),
768 Value::Boolean(want),
769 "cast('{s}' as boolean)",
770 );
771 }
772 }
773}