1use std::mem::discriminant;
5
6use arrow_array::{Array, ArrayRef};
7use arrow_schema::FieldRef;
8use reifydb_core::{
9 expression::VariableExpression,
10 value::{
11 batch::{is_scalar, scalar_value, scalar_value_type},
12 column::{
13 arith::{length_mismatch, option::binary_op_unwrap_option},
14 builder::ColumnBuilder,
15 cast::error::CastError,
16 factory,
17 },
18 },
19};
20use reifydb_value::{
21 error::{BinaryOp, Error, IntoDiagnostic, RuntimeErrorKind, TypeError},
22 fragment::Fragment,
23 value::{
24 Value,
25 column_view::{ColumnView, ViewData},
26 constraint::{Constraint, TypeConstraint, precision::Precision, scale::Scale},
27 system_columns::user_columns,
28 value_type::ValueType,
29 },
30};
31
32use crate::{
33 Result,
34 expression::{
35 compare::{Equal, compare_columns},
36 context::EvalContext,
37 },
38 stack::Variable,
39};
40
41pub(crate) fn variable_column(
42 ctx: &EvalContext,
43 expr: &VariableExpression,
44 row_count: usize,
45) -> Result<(FieldRef, ArrayRef)> {
46 let variable_name = expr.name();
47
48 if variable_name == "env" {
49 return Err(TypeError::Runtime {
50 kind: RuntimeErrorKind::VariableIsDataframe {
51 name: variable_name.to_string(),
52 },
53 message: format!(
54 "Variable '{}' contains a dataframe and cannot be used directly in scalar expressions",
55 variable_name
56 ),
57 }
58 .into());
59 }
60
61 match ctx.symbols.get(variable_name) {
62 Some(Variable::Columns {
63 batch,
64 }) if is_scalar(batch) => {
65 let value = match scalar_value(batch)? {
66 Value::Any(inner)
67 if matches!(
68 inner.as_ref(),
69 Value::List(items)
70 if items.iter().all(|v| matches!(v, Value::Record(_)))
71 ) =>
72 {
73 *inner
74 }
75 other => other,
76 };
77 if matches!(
78 value,
79 Value::None {
80 inner: ValueType::Any
81 }
82 ) {
83 return Ok(factory::none(variable_name, row_count));
84 }
85 let mut data = ColumnBuilder::with_capacity(scalar_value_type(batch, &value)?, row_count);
86 for _ in 0..row_count {
87 data.push_value(value.clone());
88 }
89 Ok(data.finish(variable_name))
90 }
91 Some(Variable::Columns {
92 ..
93 })
94 | Some(Variable::ForIterator {
95 ..
96 })
97 | Some(Variable::Closure(_)) => Err(TypeError::Runtime {
98 kind: RuntimeErrorKind::VariableIsDataframe {
99 name: variable_name.to_string(),
100 },
101 message: format!(
102 "Variable '{}' contains a dataframe and cannot be used directly in scalar expressions",
103 variable_name
104 ),
105 }
106 .into()),
107 None => {
108 if let Some(value) = ctx.params.get_named(variable_name) {
109 let mut data = ColumnBuilder::with_capacity(value.get_type(), row_count);
110 for _ in 0..row_count {
111 data.push_value(value.clone());
112 }
113 return Ok(data.finish(variable_name));
114 }
115 Err(TypeError::Runtime {
116 kind: RuntimeErrorKind::VariableNotFound {
117 fragment: expr.fragment.clone(),
118 },
119 message: format!("Variable '{}' is not defined", variable_name),
120 }
121 .into())
122 }
123 }
124}
125
126pub(crate) fn variable_field_column(
127 ctx: &EvalContext,
128 variable: &VariableExpression,
129 field_name: &str,
130 row_count: usize,
131) -> Result<(FieldRef, ArrayRef)> {
132 let variable_name = variable.name();
133 match ctx.symbols.get(variable_name) {
134 Some(Variable::Columns {
135 batch,
136 }) if !is_scalar(batch) => {
137 let found = user_columns(batch).find(|(field, _)| field.name() == field_name);
138 match found {
139 Some((field, array)) => {
140 let value = ColumnView::try_from((array, field.as_ref()))?.get_value(0);
141 let mut data = ColumnBuilder::with_capacity(value.get_type(), row_count);
142 for _ in 0..row_count {
143 data.push_value(value.clone());
144 }
145 Ok(data.finish(field_name))
146 }
147 None => {
148 let available: Vec<String> = user_columns(batch)
149 .map(|(field, _)| field.name().to_string())
150 .collect();
151 Err(TypeError::Runtime {
152 kind: RuntimeErrorKind::FieldNotFound {
153 variable: variable_name.to_string(),
154 field: field_name.to_string(),
155 available,
156 },
157 message: format!(
158 "Field '{}' not found on variable '{}'",
159 field_name, variable_name
160 ),
161 }
162 .into())
163 }
164 }
165 }
166 Some(Variable::Columns {
167 ..
168 })
169 | Some(Variable::Closure(_)) => Err(TypeError::Runtime {
170 kind: RuntimeErrorKind::FieldNotFound {
171 variable: variable_name.to_string(),
172 field: field_name.to_string(),
173 available: vec![],
174 },
175 message: format!("Field '{}' not found on variable '{}'", field_name, variable_name),
176 }
177 .into()),
178 Some(Variable::ForIterator {
179 ..
180 }) => Err(TypeError::Runtime {
181 kind: RuntimeErrorKind::VariableIsDataframe {
182 name: variable_name.to_string(),
183 },
184 message: format!(
185 "Variable '{}' contains a dataframe and cannot be used directly in scalar expressions",
186 variable_name
187 ),
188 }
189 .into()),
190 None => Err(TypeError::Runtime {
191 kind: RuntimeErrorKind::VariableNotFound {
192 fragment: variable.fragment.clone(),
193 },
194 message: format!("Variable '{}' is not defined", variable_name),
195 }
196 .into()),
197 }
198}
199
200pub(crate) fn type_column(row_count: usize, ty: &ValueType, fragment: &Fragment) -> (FieldRef, ArrayRef) {
201 let values: Vec<Value> = (0..row_count).map(|_| Value::Type(ty.clone())).collect();
202 factory::any(fragment.text(), values)
203}
204
205pub(crate) fn combine_bool_columns(
206 left: (FieldRef, ArrayRef),
207 right: (FieldRef, ArrayRef),
208 fragment: Fragment,
209 combine_fn: fn(bool, bool) -> bool,
210) -> Result<(FieldRef, ArrayRef)> {
211 if left.1.len() != right.1.len() {
212 return Err(length_mismatch(left.1.len(), right.1.len(), &fragment));
213 }
214
215 binary_op_unwrap_option(&left, &right, fragment.clone(), |left, right| {
216 let (left, right) = (ColumnView::try_from(left)?, ColumnView::try_from(right)?);
217 match (&left.data, &right.data) {
218 (ViewData::Bool(l), ViewData::Bool(r)) => {
219 let len = l.len();
220 let mut data = Vec::with_capacity(len);
221 let mut bitvec = Vec::with_capacity(len);
222
223 for i in 0..len {
224 data.push(combine_fn(l.value(i), r.value(i)));
225 bitvec.push(true);
226 }
227
228 Ok(factory::bool_with_bitvec(fragment.text(), data, bitvec))
229 }
230 _ => {
231 unreachable!("combine_bool_columns should only be called with boolean columns")
232 }
233 }
234 })
235}
236
237fn list_items_contain(items: &[Value], element: &Value, fragment: &Fragment) -> bool {
238 if items.iter().any(|item| item == element) {
239 return true;
240 }
241 if items.is_empty() {
242 return false;
243 }
244
245 if let Some(items_col) = build_homogeneous_buffer(items, fragment.text()) {
246 let elems_col = factory::from_many(fragment.text(), element.clone(), items.len());
247 return compare_columns::<Equal>(&items_col, &elems_col, fragment.clone(), |f, l, r| {
248 TypeError::BinaryOperatorNotApplicable {
249 operator: BinaryOp::Equal,
250 left: l,
251 right: r,
252 fragment: f,
253 }
254 .into_diagnostic()
255 })
256 .and_then(|c| bool_column_has_true(&c))
257 .unwrap_or(false);
258 }
259
260 list_items_contain_per_item(items, element, fragment)
261}
262
263fn list_items_contain_per_item(items: &[Value], element: &Value, fragment: &Fragment) -> bool {
264 items.iter().any(|item| {
265 let item_col = factory::from_one(fragment.text(), item.clone());
266 let elem_col = factory::from_one(fragment.text(), element.clone());
267 compare_columns::<Equal>(&item_col, &elem_col, fragment.clone(), |f, l, r| {
268 TypeError::BinaryOperatorNotApplicable {
269 operator: BinaryOp::Equal,
270 left: l,
271 right: r,
272 fragment: f,
273 }
274 .into_diagnostic()
275 })
276 .and_then(|c| {
277 let view = ColumnView::try_from(&c)?;
278 Ok(match &view.data {
279 ViewData::Bool(b) if view.logical_nulls().is_none() => b.value(0),
280 _ => false,
281 })
282 })
283 .unwrap_or(false)
284 })
285}
286
287fn bool_column_has_true(col: &(FieldRef, ArrayRef)) -> Result<bool> {
288 let view = ColumnView::try_from(col)?;
289 Ok(match &view.data {
290 ViewData::Bool(b) => match view.logical_nulls() {
291 Some(nulls) => (nulls.inner() & b.values()).has_true(),
292 None => b.values().has_true(),
293 },
294 _ => false,
295 })
296}
297
298fn build_homogeneous_buffer(items: &[Value], name: &str) -> Option<(FieldRef, ArrayRef)> {
299 let first = items.first()?;
300 let first_disc = discriminant(first);
301 if !items.iter().all(|v| discriminant(v) == first_disc) {
302 return None;
303 }
304
305 macro_rules! collect {
306 ($variant:ident, $constructor:ident $([$($arg:expr),*])?, |$x:ident| $convert:expr) => {{
307 let data: Vec<_> = items
308 .iter()
309 .map(|v| match v {
310 Value::$variant($x) => $convert,
311 _ => unreachable!("homogeneous check guarantees variant"),
312 })
313 .collect();
314 Some(factory::$constructor(name, $($($arg,)*)? data))
315 }};
316 }
317
318 match first {
319 Value::Boolean(_) => collect!(Boolean, bool, |x| *x),
320 Value::Float4(_) => collect!(Float4, float4, |x| x.value()),
321 Value::Float8(_) => collect!(Float8, float8, |x| x.value()),
322 Value::Int1(_) => collect!(Int1, int1, |x| *x),
323 Value::Int2(_) => collect!(Int2, int2, |x| *x),
324 Value::Int4(_) => collect!(Int4, int4, |x| *x),
325 Value::Int8(_) => collect!(Int8, int8, |x| *x),
326 Value::Int16(_) => collect!(Int16, int16, |x| *x),
327 Value::Uint1(_) => collect!(Uint1, uint1, |x| *x),
328 Value::Uint2(_) => collect!(Uint2, uint2, |x| *x),
329 Value::Uint4(_) => collect!(Uint4, uint4, |x| *x),
330 Value::Uint8(_) => collect!(Uint8, uint8, |x| *x),
331 Value::Uint16(_) => collect!(Uint16, uint16, |x| *x),
332 Value::Utf8(_) => collect!(Utf8, utf8, |x| x.clone()),
333 Value::Date(_) => collect!(Date, date, |x| *x),
334 Value::DateTime(_) => collect!(DateTime, datetime, |x| *x),
335 Value::Time(_) => collect!(Time, time, |x| *x),
336 Value::Duration(_) => collect!(Duration, duration, |x| *x),
337 Value::Uuid4(_) => collect!(Uuid4, uuid4, |x| *x),
338 Value::Uuid7(_) => collect!(Uuid7, uuid7, |x| *x),
339 Value::IdentityId(_) => collect!(IdentityId, identity_id, |x| *x),
340 Value::Blob(_) => collect!(Blob, blob, |x| x.clone()),
341 Value::Decimal(_) => {
342 let scale = items
343 .iter()
344 .filter_map(|v| match v {
345 Value::Decimal(d) => Some(d.scale()),
346 _ => None,
347 })
348 .max()?;
349 if !items.iter().all(|v| matches!(v, Value::Decimal(d) if d.rescale(scale).is_some())) {
350 return None;
351 }
352 collect!(Decimal, decimal[Precision::MAX, Scale::new(scale)], |x| x.clone())
353 }
354 Value::DictionaryId(_) => collect!(DictionaryId, dictionary_id, |x| *x),
355
356 _ => None,
357 }
358}
359
360pub(crate) fn list_contains_element(
361 list_col: &(FieldRef, ArrayRef),
362 element_col: &(FieldRef, ArrayRef),
363 fragment: &Fragment,
364) -> Result<(FieldRef, ArrayRef)> {
365 let (list_view, element_view) = (ColumnView::try_from(list_col)?, ColumnView::try_from(element_col)?);
366 let len = list_col.1.len();
367 let mut data = Vec::with_capacity(len);
368
369 for i in 0..len {
370 let list_value = list_view.get_value(i);
371 let element_value = element_view.get_value(i);
372
373 let contained = match &list_value {
374 Value::List(items) => list_items_contain(items, &element_value, fragment),
375 Value::Tuple(items) => list_items_contain(items, &element_value, fragment),
376 Value::Any(boxed) => match boxed.as_ref() {
377 Value::List(items) => list_items_contain(items, &element_value, fragment),
378 Value::Tuple(items) => list_items_contain(items, &element_value, fragment),
379 _ => false,
380 },
381 _ => false,
382 };
383 data.push(contained);
384 }
385
386 Ok(factory::bool(fragment.text(), data))
387}
388
389pub(crate) fn is_truthy(value: &Value) -> bool {
390 match value {
391 Value::Boolean(true) => true,
392 Value::Boolean(false) => false,
393 Value::None {
394 ..
395 } => false,
396 Value::Int1(0) | Value::Int2(0) | Value::Int4(0) | Value::Int8(0) | Value::Int16(0) => false,
397 Value::Uint1(0) | Value::Uint2(0) | Value::Uint4(0) | Value::Uint8(0) | Value::Uint16(0) => false,
398 Value::Int1(_) | Value::Int2(_) | Value::Int4(_) | Value::Int8(_) | Value::Int16(_) => true,
399 Value::Uint1(_) | Value::Uint2(_) | Value::Uint4(_) | Value::Uint8(_) | Value::Uint16(_) => true,
400 Value::Utf8(s) => !s.is_empty(),
401 _ => true,
402 }
403}
404
405pub(crate) fn enforce_cast_constraint(
406 column: &(FieldRef, ArrayRef),
407 target: &ValueType,
408 constraint: &Option<Constraint>,
409 fragment: &Fragment,
410) -> Result<()> {
411 let Some(constraint) = constraint else {
412 return Ok(());
413 };
414 let checked = TypeConstraint::with_constraint(target.clone(), constraint.clone());
415 let view = ColumnView::try_from(column)?;
416 for row in (0..view.len()).filter(|&row| !view.none_at(row)) {
417 if let Err(mut error) = checked.coerce(&mut view.get_value(row)) {
418 error.0.fragment = fragment.clone();
419 return Err(error);
420 }
421 }
422 Ok(())
423}
424
425pub(crate) fn wrap_cast_error(err: Error, fragment: Fragment, target: &ValueType) -> Error {
426 if err.0.code.starts_with("CAST_") {
427 return err;
428 }
429 let cause = err.diagnostic();
430 let wrapped = if target.is_bool() {
431 CastError::InvalidBoolean {
432 fragment,
433 cause,
434 }
435 } else if target.is_temporal() {
436 CastError::InvalidTemporal {
437 fragment,
438 target: target.clone(),
439 cause,
440 }
441 } else if target.is_uuid() || *target == ValueType::IdentityId {
442 CastError::InvalidUuid {
443 fragment,
444 target: target.clone(),
445 cause,
446 }
447 } else {
448 CastError::InvalidNumber {
449 fragment,
450 target: target.clone(),
451 cause,
452 }
453 };
454 Error::from(wrapped)
455}
456
457#[cfg(test)]
458mod tests {
459 use std::str::FromStr;
460
461 use arrow_array::{ArrayRef, RecordBatch};
462 use arrow_schema::FieldRef;
463 use reifydb_core::{
464 expression::{
465 CastExpression, ColumnExpression, ConstantExpression, ElseIfExpression, Expression,
466 IfExpression, MapExpression, TypeExpression,
467 },
468 interface::identifier::ColumnIdentifier,
469 value::{batch::batch, column::factory},
470 };
471 use reifydb_value::{
472 fragment::Fragment,
473 value::{
474 Value,
475 column_view::ColumnView,
476 constraint::{precision::Precision, scale::Scale},
477 decimal::Decimal,
478 value_type::ValueType,
479 },
480 };
481
482 use super::combine_bool_columns;
483 use crate::expression::{context::EvalContext, eval::evaluate};
484
485 fn column(name: &str) -> Expression {
486 Expression::Column(ColumnExpression(ColumnIdentifier::with_alias(
487 Fragment::internal("t"),
488 Fragment::internal(name),
489 )))
490 }
491
492 fn uncastable() -> Expression {
493 Expression::Cast(CastExpression {
494 fragment: Fragment::testing_empty(),
495 expression: Box::new(Expression::Constant(ConstantExpression::Text {
496 fragment: Fragment::internal("not-a-number"),
497 })),
498 to: TypeExpression {
499 fragment: Fragment::testing_empty(),
500 ty: ValueType::Int4,
501 constraint: None,
502 },
503 })
504 }
505
506 fn conditional(
507 condition: Expression,
508 then_expr: Expression,
509 else_ifs: Vec<(Expression, Expression)>,
510 else_expr: Option<Expression>,
511 ) -> Expression {
512 Expression::If(IfExpression {
513 condition: Box::new(condition),
514 then_expr: Box::new(then_expr),
515 else_ifs: else_ifs
516 .into_iter()
517 .map(|(condition, then_expr)| ElseIfExpression {
518 condition: Box::new(condition),
519 then_expr: Box::new(then_expr),
520 fragment: Fragment::testing_empty(),
521 })
522 .collect(),
523 else_expr: else_expr.map(Box::new),
524 fragment: Fragment::testing_empty(),
525 })
526 }
527
528 fn bools(name: &str, data: [bool; 4]) -> (FieldRef, ArrayRef) {
529 factory::bool(name, data)
530 }
531
532 fn ints(name: &str, data: [i32; 4]) -> (FieldRef, ArrayRef) {
533 factory::int4(name, data)
534 }
535
536 #[test]
537 fn every_row_reads_its_own_index_from_the_branch_it_selected() {
538 let base = EvalContext::testing();
541 let ctx = base.with_eval(
542 batch(vec![
543 bools("flag", [true, false, false, true]),
544 ints("hi", [100, 200, 300, 400]),
545 ints("lo", [1, 2, 3, 4]),
546 ])
547 .unwrap(),
548 4,
549 );
550
551 let result =
552 evaluate(&ctx, &conditional(column("flag"), column("hi"), vec![], Some(column("lo")))).unwrap();
553
554 assert_eq!(result.1.as_ref(), factory::int4("", [100, 2, 3, 400]).1.as_ref());
555 }
556
557 #[test]
558 fn an_else_if_chain_gives_each_row_its_first_matching_branch() {
559 let base = EvalContext::testing();
562 let ctx = base.with_eval(
563 batch(vec![
564 bools("first", [true, false, false, false]),
565 bools("second", [true, true, true, false]),
566 ints("a", [10, 20, 30, 40]),
567 ints("b", [1, 2, 3, 4]),
568 ints("c", [-1, -2, -3, -4]),
569 ])
570 .unwrap(),
571 4,
572 );
573
574 let result = evaluate(
575 &ctx,
576 &conditional(
577 column("first"),
578 column("a"),
579 vec![(column("second"), column("b"))],
580 Some(column("c")),
581 ),
582 )
583 .unwrap();
584
585 assert_eq!(result.1.as_ref(), factory::int4("", [10, 2, 3, -4]).1.as_ref());
586 }
587
588 #[test]
589 fn a_row_that_matches_no_branch_becomes_none() {
590 let base = EvalContext::testing();
593 let ctx = base.with_eval(
594 batch(vec![bools("flag", [true, false, false, true]), ints("hi", [7, 8, 9, 10])]).unwrap(),
595 4,
596 );
597
598 let result = evaluate(&ctx, &conditional(column("flag"), column("hi"), vec![], None)).unwrap();
599
600 let view = ColumnView::try_from(&result).unwrap();
601 assert_eq!(result.1.len(), 4);
602 assert_eq!(view.get_value(0), Value::Int4(7));
603 assert!(matches!(view.get_value(1), Value::None { .. }));
604 assert!(matches!(view.get_value(2), Value::None { .. }));
605 assert_eq!(view.get_value(3), Value::Int4(10));
606 }
607
608 #[test]
609 fn a_branch_that_no_row_selects_is_never_evaluated() {
610 let base = EvalContext::testing();
613 let all_true = base.with_eval(
614 batch(vec![bools("flag", [true, true, true, true]), ints("hi", [1, 2, 3, 4])]).unwrap(),
615 4,
616 );
617
618 let skipped =
619 evaluate(&all_true, &conditional(column("flag"), column("hi"), vec![], Some(uncastable())));
620
621 assert!(skipped.is_ok(), "an else branch no row selects must not be evaluated");
622
623 let one_false = base.with_eval(
624 batch(vec![bools("flag", [true, true, false, true]), ints("hi", [1, 2, 3, 4])]).unwrap(),
625 4,
626 );
627
628 let taken =
629 evaluate(&one_false, &conditional(column("flag"), column("hi"), vec![], Some(uncastable())));
630
631 assert!(
632 taken.is_err(),
633 "the branch must really fail when a row selects it, or the case above is vacuous"
634 );
635 }
636
637 fn multi(names: [&str; 2]) -> Expression {
638 Expression::Map(MapExpression {
639 expressions: names.iter().map(|name| column(name)).collect(),
640 fragment: Fragment::testing_empty(),
641 })
642 }
643
644 fn none_literal() -> Expression {
645 Expression::Constant(ConstantExpression::None {
646 fragment: Fragment::testing_empty(),
647 })
648 }
649
650 fn four_row_ctx(extra: Vec<(FieldRef, ArrayRef)>) -> RecordBatch {
651 let mut cols = vec![bools("flag", [true, false, false, true])];
652 cols.extend(extra);
653 batch(cols).unwrap()
654 }
655
656 #[test]
657 fn branches_of_different_types_are_rejected_instead_of_aborting() {
658 let base = EvalContext::testing();
661 let ctx = base.with_eval(
662 four_row_ctx(vec![ints("small", [1, 2, 3, 4]), factory::int8("wide", [5i64, 6, 7, 8])]),
663 4,
664 );
665
666 let err = evaluate(&ctx, &conditional(column("flag"), column("small"), vec![], Some(column("wide"))))
667 .expect_err("int4 and int8 branches must not be accepted");
668
669 assert_eq!(err.0.code, "RUNTIME_012");
670 }
671
672 #[test]
673 fn an_optional_branch_still_pairs_with_its_bare_type() {
674 let base = EvalContext::testing();
677 let ctx = base.with_eval(
678 four_row_ctx(vec![
679 ints("bare", [1, 2, 3, 4]),
680 factory::int4_with_bitvec("opt", [9, 8, 7, 6], vec![true, false, true, true]),
681 ]),
682 4,
683 );
684
685 let result = evaluate(&ctx, &conditional(column("flag"), column("bare"), vec![], Some(column("opt"))))
686 .expect("a bare and an optional branch of one base type must agree");
687
688 assert_eq!(result.1.len(), 4);
689 }
690
691 #[test]
692 fn a_none_branch_widens_to_the_other_branch_type() {
693 let base = EvalContext::testing();
696 let ctx = base.with_eval(four_row_ctx(vec![ints("hi", [7, 8, 9, 10])]), 4);
697
698 let result = evaluate(&ctx, &conditional(column("flag"), column("hi"), vec![], Some(none_literal())))
699 .expect("a none branch must widen to the other branch type");
700
701 let view = ColumnView::try_from(&result).unwrap();
702 assert_eq!(view.get_value(0), Value::Int4(7));
703 assert!(matches!(view.get_value(1), Value::None { .. }));
704 }
705
706 #[test]
707 fn a_none_first_row_does_not_fix_the_result_type() {
708 let base = EvalContext::testing();
711 let ctx = base.with_eval(
712 batch(vec![bools("flag", [false, true, false, true]), ints("hi", [7, 8, 9, 10])]).unwrap(),
713 4,
714 );
715
716 let result = evaluate(&ctx, &conditional(column("flag"), column("hi"), vec![], Some(none_literal())))
717 .expect("a none first row must not fix the result type");
718
719 let view = ColumnView::try_from(&result).unwrap();
720 assert!(matches!(view.get_value(0), Value::None { .. }));
721 assert_eq!(view.get_value(1), Value::Int4(8));
722 assert!(matches!(view.get_value(2), Value::None { .. }));
723 assert_eq!(view.get_value(3), Value::Int4(10));
724 }
725
726 #[test]
727 fn branches_of_different_widths_are_rejected() {
728 let base = EvalContext::testing();
731 let ctx = base.with_eval(four_row_ctx(vec![ints("a", [1, 2, 3, 4]), ints("b", [10, 20, 30, 40])]), 4);
732
733 let wide_then =
734 evaluate(&ctx, &conditional(column("flag"), multi(["a", "b"]), vec![], Some(column("a"))))
735 .expect_err("a two column branch must not pair with a one column branch");
736 assert_eq!(wide_then.0.code, "RUNTIME_012");
737
738 let wide_else =
739 evaluate(&ctx, &conditional(column("flag"), column("a"), vec![], Some(multi(["a", "b"]))))
740 .expect_err("a one column branch must not pair with a two column branch");
741 assert_eq!(wide_else.0.code, "RUNTIME_012");
742 }
743
744 #[test]
745 fn a_mismatched_branch_no_row_selects_is_rejected() {
746 let base = EvalContext::testing();
749 let ctx = base.with_eval(
750 batch(vec![
751 bools("flag", [true, true, true, true]),
752 ints("small", [1, 2, 3, 4]),
753 factory::int8("wide", [5i64, 6, 7, 8]),
754 ])
755 .unwrap(),
756 4,
757 );
758
759 let err = evaluate(&ctx, &conditional(column("flag"), column("small"), vec![], Some(column("wide"))))
760 .expect_err("an unselected branch of another type must be rejected");
761
762 assert_eq!(err.code, "RUNTIME_012", "expected a conditional branch mismatch, got {err:?}");
763 }
764
765 #[test]
766 fn decimal_and_float8_branches_unify_to_float8_in_either_order() {
767 let base = EvalContext::testing();
770 let decimals = ["10.5", "20.5", "30.5", "40.5"].map(|text| Decimal::from_str(text).unwrap());
771 let ctx = base.with_eval(
772 four_row_ctx(vec![
773 factory::float8("f", [1.5, 2.5, 3.5, 4.5]),
774 factory::decimal("d", Precision::new(3), Scale::new(1), decimals),
775 ]),
776 4,
777 );
778
779 let float_then = evaluate(&ctx, &conditional(column("flag"), column("f"), vec![], Some(column("d"))))
780 .expect("a float then branch must unify with a decimal else branch");
781 let view = ColumnView::try_from(&float_then).unwrap();
782 assert_eq!(*view.get_type().inner_type(), ValueType::Float8);
783 assert_eq!(view.get_value(0), Value::float8(1.5));
784 assert_eq!(view.get_value(1), Value::float8(20.5));
785 assert_eq!(view.get_value(2), Value::float8(30.5));
786 assert_eq!(view.get_value(3), Value::float8(4.5));
787
788 let decimal_then = evaluate(&ctx, &conditional(column("flag"), column("d"), vec![], Some(column("f"))))
789 .expect("a decimal then branch must unify with a float else branch");
790 let view = ColumnView::try_from(&decimal_then).unwrap();
791 assert_eq!(*view.get_type().inner_type(), ValueType::Float8);
792 assert_eq!(view.get_value(0), Value::float8(10.5));
793 assert_eq!(view.get_value(1), Value::float8(2.5));
794 assert_eq!(view.get_value(2), Value::float8(3.5));
795 assert_eq!(view.get_value(3), Value::float8(40.5));
796 }
797
798 #[test]
799 fn a_multi_column_value_in_a_single_column_slot_is_rejected() {
800 let base = EvalContext::testing();
803 let ctx = base.with_eval(four_row_ctx(vec![ints("a", [1, 2, 3, 4]), ints("b", [10, 20, 30, 40])]), 4);
804
805 let err = evaluate(&ctx, &multi(["a", "b"])).expect_err("a two column value must not be truncated");
806
807 assert_eq!(err.0.code, "RUNTIME_010");
808 }
809
810 #[test]
811 fn combining_boolean_columns_of_different_lengths_is_an_error() {
812 let left = factory::bool("l", [true, true, false]);
814 let right = factory::bool("r", [true, false]);
815
816 let result = combine_bool_columns(left, right, Fragment::internal("and"), |l, r| l && r);
817
818 assert!(
819 result.is_err(),
820 "a right side shorter than the left must be an error, got {:?}",
821 result.map(|c| c.1)
822 );
823 }
824}