Skip to main content

reifydb_evaluate/expression/
compile.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{mem::discriminant, slice::from_ref, str::FromStr};
5
6use reifydb_core::value::column::{
7	ColumnWithName,
8	buffer::ColumnBuffer,
9	cast::{cast_column_data, error::CastError},
10	columns::Columns,
11};
12use reifydb_rql::expression::{Expression, name::display_label};
13use reifydb_value::{
14	error::{BinaryOp, Error, IntoDiagnostic, LogicalOp, RuntimeErrorKind, TypeError},
15	fragment::Fragment,
16	value::{Value, value_type::ValueType},
17};
18
19use super::{
20	context::CompileContext,
21	option::{binary_op_unwrap_option, unary_op_unwrap_option},
22};
23use crate::{
24	Result,
25	expression::{
26		access::access_lookup,
27		arith::{add::add_columns, div::div_columns, mul::mul_columns, rem::rem_columns, sub::sub_columns},
28		call::call_builtin,
29		compare::{Equal, GreaterThan, GreaterThanEqual, LessThan, LessThanEqual, NotEqual, compare_columns},
30		constant::constant_value,
31		context::EvalContext,
32		logic::{execute_logical_op, try_short_circuit_and, try_short_circuit_or},
33		lookup::column_lookup,
34		parameter::parameter_lookup,
35		prefix::prefix_apply,
36	},
37	stack::Variable,
38};
39
40type SingleExprFn = Box<dyn Fn(&EvalContext) -> Result<ColumnWithName> + Send + Sync>;
41type MultiExprFn = Box<dyn Fn(&EvalContext) -> Result<Vec<ColumnWithName>> + Send + Sync>;
42
43pub struct CompiledExpr {
44	inner: CompiledExprInner,
45	access_column_name: Option<String>,
46}
47
48enum CompiledExprInner {
49	Single(SingleExprFn),
50	Multi(MultiExprFn),
51}
52
53impl CompiledExpr {
54	pub fn new(f: impl Fn(&EvalContext) -> Result<ColumnWithName> + Send + Sync + 'static) -> Self {
55		Self {
56			inner: CompiledExprInner::Single(Box::new(f)),
57			access_column_name: None,
58		}
59	}
60
61	pub fn new_multi(f: impl Fn(&EvalContext) -> Result<Vec<ColumnWithName>> + Send + Sync + 'static) -> Self {
62		Self {
63			inner: CompiledExprInner::Multi(Box::new(f)),
64			access_column_name: None,
65		}
66	}
67
68	pub fn new_access(
69		name: String,
70		f: impl Fn(&EvalContext) -> Result<ColumnWithName> + Send + Sync + 'static,
71	) -> Self {
72		Self {
73			inner: CompiledExprInner::Single(Box::new(f)),
74			access_column_name: Some(name),
75		}
76	}
77
78	pub fn access_column_name(&self) -> Option<&str> {
79		self.access_column_name.as_deref()
80	}
81
82	pub fn execute(&self, ctx: &EvalContext) -> Result<ColumnWithName> {
83		match &self.inner {
84			CompiledExprInner::Single(f) => f(ctx),
85			CompiledExprInner::Multi(f) => {
86				let columns = f(ctx)?;
87				if columns.len() == 1 {
88					return Ok(columns.into_iter().next().unwrap());
89				}
90				Err(TypeError::Runtime {
91					kind: RuntimeErrorKind::ExpectedSingleColumn {
92						actual: columns.len(),
93					},
94					message: "expression produces more than one column where one is required"
95						.to_string(),
96				}
97				.into())
98			}
99		}
100	}
101
102	pub fn execute_multi(&self, ctx: &EvalContext) -> Result<Vec<ColumnWithName>> {
103		match &self.inner {
104			CompiledExprInner::Single(f) => Ok(vec![f(ctx)?]),
105			CompiledExprInner::Multi(f) => f(ctx),
106		}
107	}
108}
109
110macro_rules! compile_arith {
111	($ctx:expr, $parent:expr, $e:expr, $op_fn:path) => {{
112		let left = compile_expression($ctx, &$e.left)?;
113		let right = compile_expression($ctx, &$e.right)?;
114		let fragment = $e.full_fragment_owned();
115		let label = display_label($parent);
116		CompiledExpr::new(move |ctx| {
117			let l = left.execute(ctx)?;
118			let r = right.execute(ctx)?;
119			let mut col = $op_fn(ctx, &l, &r, || fragment.clone())?;
120			col.name = label.clone();
121			Ok(col)
122		})
123	}};
124}
125
126macro_rules! compile_compare {
127	($ctx:expr, $parent:expr, $e:expr, $cmp_type:ty, $binary_op:expr) => {{
128		let left = compile_expression($ctx, &$e.left)?;
129		let right = compile_expression($ctx, &$e.right)?;
130		let fragment = $e.full_fragment_owned();
131		let label = display_label($parent);
132		CompiledExpr::new(move |ctx| {
133			let l = left.execute(ctx)?;
134			let r = right.execute(ctx)?;
135			let mut col = compare_columns::<$cmp_type>(&l, &r, fragment.clone(), |f, l, r| {
136				TypeError::BinaryOperatorNotApplicable {
137					operator: $binary_op,
138					left: l,
139					right: r,
140					fragment: f,
141				}
142				.into_diagnostic()
143			})?;
144			col.name = label.clone();
145			Ok(col)
146		})
147	}};
148}
149
150pub fn compile_expression(_ctx: &CompileContext, expr: &Expression) -> Result<CompiledExpr> {
151	Ok(match expr {
152		Expression::Constant(e) => {
153			let constant = e.clone();
154			let label = display_label(expr);
155			CompiledExpr::new(move |ctx| {
156				let row_count = ctx.take.unwrap_or(ctx.row_count);
157				Ok(ColumnWithName {
158					name: label.clone(),
159					data: constant_value(&constant, row_count)?,
160				})
161			})
162		}
163
164		Expression::Column(e) => {
165			let expr = e.clone();
166			CompiledExpr::new(move |ctx| column_lookup(ctx, &expr))
167		}
168
169		Expression::Variable(e) => {
170			let expr = e.clone();
171			CompiledExpr::new(move |ctx| {
172				let variable_name = expr.name();
173
174				if variable_name == "env" {
175					return Err(TypeError::Runtime {
176						kind: RuntimeErrorKind::VariableIsDataframe {
177							name: variable_name.to_string(),
178						},
179						message: format!(
180							"Variable '{}' contains a dataframe and cannot be used directly in scalar expressions",
181							variable_name
182						),
183					}
184					.into());
185				}
186
187				match ctx.symbols.get(variable_name) {
188					Some(Variable::Columns {
189						columns,
190					}) if columns.is_scalar() => {
191						let value = columns.scalar_value();
192						let mut data =
193							ColumnBuffer::with_capacity(value.get_type(), ctx.row_count);
194						for _ in 0..ctx.row_count {
195							data.push_value(value.clone());
196						}
197						Ok(ColumnWithName {
198							name: Fragment::internal(variable_name),
199							data,
200						})
201					}
202					Some(Variable::Columns {
203						..
204					})
205					| Some(Variable::ForIterator {
206						..
207					})
208					| Some(Variable::Closure(_)) => Err(TypeError::Runtime {
209						kind: RuntimeErrorKind::VariableIsDataframe {
210							name: variable_name.to_string(),
211						},
212						message: format!(
213							"Variable '{}' contains a dataframe and cannot be used directly in scalar expressions",
214							variable_name
215						),
216					}
217					.into()),
218					None => {
219						if let Some(value) = ctx.params.get_named(variable_name) {
220							let mut data = ColumnBuffer::with_capacity(
221								value.get_type(),
222								ctx.row_count,
223							);
224							for _ in 0..ctx.row_count {
225								data.push_value(value.clone());
226							}
227							return Ok(ColumnWithName {
228								name: Fragment::internal(variable_name),
229								data,
230							});
231						}
232						Err(TypeError::Runtime {
233							kind: RuntimeErrorKind::VariableNotFound {
234								name: variable_name.to_string(),
235							},
236							message: format!("Variable '{}' is not defined", variable_name),
237						}
238						.into())
239					}
240				}
241			})
242		}
243
244		Expression::Parameter(e) => {
245			let expr = e.clone();
246			CompiledExpr::new(move |ctx| parameter_lookup(ctx, &expr))
247		}
248
249		Expression::Alias(e) => {
250			let inner = compile_expression(_ctx, &e.expression)?;
251			let alias = e.alias.0.clone();
252			CompiledExpr::new(move |ctx| {
253				let mut column = inner.execute(ctx)?;
254				column.name = alias.clone();
255				Ok(column)
256			})
257		}
258
259		Expression::Add(e) => compile_arith!(_ctx, expr, e, add_columns),
260		Expression::Sub(e) => compile_arith!(_ctx, expr, e, sub_columns),
261		Expression::Mul(e) => compile_arith!(_ctx, expr, e, mul_columns),
262		Expression::Div(e) => compile_arith!(_ctx, expr, e, div_columns),
263		Expression::Rem(e) => compile_arith!(_ctx, expr, e, rem_columns),
264
265		Expression::Equal(e) => compile_compare!(_ctx, expr, e, Equal, BinaryOp::Equal),
266		Expression::NotEqual(e) => compile_compare!(_ctx, expr, e, NotEqual, BinaryOp::NotEqual),
267		Expression::GreaterThan(e) => compile_compare!(_ctx, expr, e, GreaterThan, BinaryOp::GreaterThan),
268		Expression::GreaterThanEqual(e) => {
269			compile_compare!(_ctx, expr, e, GreaterThanEqual, BinaryOp::GreaterThanEqual)
270		}
271		Expression::LessThan(e) => compile_compare!(_ctx, expr, e, LessThan, BinaryOp::LessThan),
272		Expression::LessThanEqual(e) => compile_compare!(_ctx, expr, e, LessThanEqual, BinaryOp::LessThanEqual),
273
274		Expression::And(e) => {
275			let left = compile_expression(_ctx, &e.left)?;
276			let right = compile_expression(_ctx, &e.right)?;
277			let fragment = e.full_fragment_owned();
278			let label = display_label(expr);
279			CompiledExpr::new(move |ctx| {
280				let l = left.execute(ctx)?;
281				if let Some(mut short) = try_short_circuit_and(&l, &fragment, l.data().len()) {
282					short.name = label.clone();
283					return Ok(short);
284				}
285				let r = right.execute(ctx)?;
286				let mut col = execute_logical_op(&l, &r, &fragment, LogicalOp::And, |a, b| a && b)?;
287				col.name = label.clone();
288				Ok(col)
289			})
290		}
291
292		Expression::Or(e) => {
293			let left = compile_expression(_ctx, &e.left)?;
294			let right = compile_expression(_ctx, &e.right)?;
295			let fragment = e.full_fragment_owned();
296			let label = display_label(expr);
297			CompiledExpr::new(move |ctx| {
298				let l = left.execute(ctx)?;
299				if let Some(mut short) = try_short_circuit_or(&l, &fragment, l.data().len()) {
300					short.name = label.clone();
301					return Ok(short);
302				}
303				let r = right.execute(ctx)?;
304				let mut col = execute_logical_op(&l, &r, &fragment, LogicalOp::Or, |a, b| a || b)?;
305				col.name = label.clone();
306				Ok(col)
307			})
308		}
309
310		Expression::Xor(e) => {
311			let left = compile_expression(_ctx, &e.left)?;
312			let right = compile_expression(_ctx, &e.right)?;
313			let fragment = e.full_fragment_owned();
314			let label = display_label(expr);
315			CompiledExpr::new(move |ctx| {
316				let l = left.execute(ctx)?;
317				let r = right.execute(ctx)?;
318				let mut col = execute_logical_op(&l, &r, &fragment, LogicalOp::Xor, |a, b| a != b)?;
319				col.name = label.clone();
320				Ok(col)
321			})
322		}
323
324		Expression::Prefix(e) => {
325			let inner = compile_expression(_ctx, &e.expression)?;
326			let operator = e.operator.clone();
327			let fragment = e.full_fragment_owned();
328			let label = display_label(expr);
329			CompiledExpr::new(move |ctx| {
330				let column = inner.execute(ctx)?;
331				let mut col = prefix_apply(&column, &operator, &fragment)?;
332				col.name = label.clone();
333				Ok(col)
334			})
335		}
336
337		Expression::Type(e) => {
338			let ty = e.ty.clone();
339			let fragment = e.fragment.clone();
340			CompiledExpr::new(move |ctx| Ok(type_column(ctx, &ty, &fragment)))
341		}
342
343		Expression::AccessSource(e) => {
344			let col_name = e.column.name.text().to_string();
345			let expr = e.clone();
346			CompiledExpr::new_access(col_name, move |ctx| access_lookup(ctx, &expr))
347		}
348
349		Expression::Tuple(e) => {
350			if e.expressions.len() == 1 {
351				let inner = compile_expression(_ctx, &e.expressions[0])?;
352				CompiledExpr::new(move |ctx| inner.execute(ctx))
353			} else {
354				let compiled: Vec<CompiledExpr> = e
355					.expressions
356					.iter()
357					.map(|expr| compile_expression(_ctx, expr))
358					.collect::<Result<Vec<_>>>()?;
359				let fragment = e.fragment.clone();
360				CompiledExpr::new(move |ctx| {
361					let columns: Vec<ColumnWithName> = compiled
362						.iter()
363						.map(|expr| expr.execute(ctx))
364						.collect::<Result<Vec<_>>>()?;
365
366					let len = columns.first().map_or(1, |c| c.data().len());
367					let mut data: Vec<Value> = Vec::with_capacity(len);
368
369					for i in 0..len {
370						let items: Vec<Value> =
371							columns.iter().map(|col| col.data().get_value(i)).collect();
372						data.push(Value::Tuple(items));
373					}
374
375					Ok(ColumnWithName::new(fragment.clone(), ColumnBuffer::any(data)))
376				})
377			}
378		}
379
380		Expression::List(e) => {
381			let compiled: Vec<CompiledExpr> = e
382				.expressions
383				.iter()
384				.map(|expr| compile_expression(_ctx, expr))
385				.collect::<Result<Vec<_>>>()?;
386			let fragment = e.fragment.clone();
387			CompiledExpr::new(move |ctx| {
388				let columns: Vec<ColumnWithName> =
389					compiled.iter().map(|expr| expr.execute(ctx)).collect::<Result<Vec<_>>>()?;
390
391				let len = columns.first().map_or(1, |c| c.data().len());
392				let mut data: Vec<Value> = Vec::with_capacity(len);
393
394				for i in 0..len {
395					let items: Vec<Value> =
396						columns.iter().map(|col| col.data().get_value(i)).collect();
397					data.push(Value::List(items));
398				}
399
400				Ok(ColumnWithName::new(fragment.clone(), ColumnBuffer::any(data)))
401			})
402		}
403
404		Expression::Between(e) => {
405			let value = compile_expression(_ctx, &e.value)?;
406			let lower = compile_expression(_ctx, &e.lower)?;
407			let upper = compile_expression(_ctx, &e.upper)?;
408			let fragment = e.fragment.clone();
409			CompiledExpr::new(move |ctx| {
410				let value_col = value.execute(ctx)?;
411				let lower_col = lower.execute(ctx)?;
412				let upper_col = upper.execute(ctx)?;
413
414				let ge_result = compare_columns::<GreaterThanEqual>(
415					&value_col,
416					&lower_col,
417					fragment.clone(),
418					|f, l, r| {
419						TypeError::BinaryOperatorNotApplicable {
420							operator: BinaryOp::Between,
421							left: l,
422							right: r,
423							fragment: f,
424						}
425						.into_diagnostic()
426					},
427				)?;
428				let le_result = compare_columns::<LessThanEqual>(
429					&value_col,
430					&upper_col,
431					fragment.clone(),
432					|f, l, r| {
433						TypeError::BinaryOperatorNotApplicable {
434							operator: BinaryOp::Between,
435							left: l,
436							right: r,
437							fragment: f,
438						}
439						.into_diagnostic()
440					},
441				)?;
442
443				if !matches!(ge_result.data(), ColumnBuffer::Bool(_))
444					|| !matches!(le_result.data(), ColumnBuffer::Bool(_))
445				{
446					return Err(TypeError::BinaryOperatorNotApplicable {
447						operator: BinaryOp::Between,
448						left: value_col.get_type(),
449						right: lower_col.get_type(),
450						fragment: fragment.clone(),
451					}
452					.into());
453				}
454
455				match (ge_result.data(), le_result.data()) {
456					(ColumnBuffer::Bool(ge_container), ColumnBuffer::Bool(le_container)) => {
457						let mut data = Vec::with_capacity(ge_container.len());
458						let mut bitvec = Vec::with_capacity(ge_container.len());
459
460						for i in 0..ge_container.len() {
461							if ge_container.is_defined(i) && le_container.is_defined(i) {
462								data.push(ge_container.data().get(i)
463									&& le_container.data().get(i));
464								bitvec.push(true);
465							} else {
466								data.push(false);
467								bitvec.push(false);
468							}
469						}
470
471						Ok(ColumnWithName {
472							name: fragment.clone(),
473							data: ColumnBuffer::bool_with_bitvec(data, bitvec),
474						})
475					}
476					_ => unreachable!(
477						"Both comparison results should be boolean after the check above"
478					),
479				}
480			})
481		}
482
483		Expression::In(e) => {
484			let list_expressions = match e.list.as_ref() {
485				Expression::Tuple(tuple) => &tuple.expressions,
486				Expression::List(list) => &list.expressions,
487				_ => from_ref(e.list.as_ref()),
488			};
489			let value = compile_expression(_ctx, &e.value)?;
490			let list: Vec<CompiledExpr> = list_expressions
491				.iter()
492				.map(|expr| compile_expression(_ctx, expr))
493				.collect::<Result<Vec<_>>>()?;
494			let negated = e.negated;
495			let fragment = e.fragment.clone();
496			CompiledExpr::new(move |ctx| {
497				if list.is_empty() {
498					let value_col = value.execute(ctx)?;
499					let len = value_col.data().len();
500					let result = vec![negated; len];
501					return Ok(ColumnWithName::new(fragment.clone(), ColumnBuffer::bool(result)));
502				}
503
504				let value_col = value.execute(ctx)?;
505
506				let first_col = list[0].execute(ctx)?;
507				let mut result = compare_columns::<Equal>(
508					&value_col,
509					&first_col,
510					fragment.clone(),
511					|f, l, r| {
512						TypeError::BinaryOperatorNotApplicable {
513							operator: BinaryOp::Equal,
514							left: l,
515							right: r,
516							fragment: f,
517						}
518						.into_diagnostic()
519					},
520				)?;
521
522				for list_expr in list.iter().skip(1) {
523					let list_col = list_expr.execute(ctx)?;
524					let eq_result = compare_columns::<Equal>(
525						&value_col,
526						&list_col,
527						fragment.clone(),
528						|f, l, r| {
529							TypeError::BinaryOperatorNotApplicable {
530								operator: BinaryOp::Equal,
531								left: l,
532								right: r,
533								fragment: f,
534							}
535							.into_diagnostic()
536						},
537					)?;
538					result = combine_bool_columns(result, eq_result, fragment.clone(), |l, r| {
539						l || r
540					})?;
541				}
542
543				if negated {
544					result = negate_column(result, fragment.clone());
545				}
546
547				Ok(result)
548			})
549		}
550
551		Expression::Contains(e) => {
552			let list_expressions = match e.list.as_ref() {
553				Expression::Tuple(tuple) => &tuple.expressions,
554				Expression::List(list) => &list.expressions,
555				_ => from_ref(e.list.as_ref()),
556			};
557			let value = compile_expression(_ctx, &e.value)?;
558			let list: Vec<CompiledExpr> = list_expressions
559				.iter()
560				.map(|expr| compile_expression(_ctx, expr))
561				.collect::<Result<Vec<_>>>()?;
562			let fragment = e.fragment.clone();
563			CompiledExpr::new(move |ctx| {
564				let value_col = value.execute(ctx)?;
565
566				if list.is_empty() {
567					let len = value_col.data().len();
568					let result = vec![true; len];
569					return Ok(ColumnWithName::new(fragment.clone(), ColumnBuffer::bool(result)));
570				}
571
572				let first_col = list[0].execute(ctx)?;
573				let mut result = list_contains_element(&value_col, &first_col, &fragment)?;
574
575				for list_expr in list.iter().skip(1) {
576					let list_col = list_expr.execute(ctx)?;
577					let element_result = list_contains_element(&value_col, &list_col, &fragment)?;
578					result = combine_bool_columns(
579						result,
580						element_result,
581						fragment.clone(),
582						|l, r| l && r,
583					)?;
584				}
585
586				Ok(result)
587			})
588		}
589
590		Expression::Cast(e) => {
591			let label = display_label(expr);
592			if let Expression::Constant(const_expr) = e.expression.as_ref() {
593				let const_expr = const_expr.clone();
594				let target_type = e.to.ty.clone();
595				let inner_fragment = e.expression.full_fragment_owned();
596				CompiledExpr::new(move |ctx| {
597					let row_count = ctx.take.unwrap_or(ctx.row_count);
598					let data = constant_value(&const_expr, row_count)?;
599					let casted = if data.get_type() == target_type {
600						data
601					} else {
602						apply_cast(ctx, &data, &target_type, &inner_fragment)?
603					};
604					Ok(ColumnWithName::new(label.clone(), casted))
605				})
606			} else {
607				let inner = compile_expression(_ctx, &e.expression)?;
608				let target_type = e.to.ty.clone();
609				let inner_fragment = e.expression.full_fragment_owned();
610				CompiledExpr::new(move |ctx| {
611					let column = inner.execute(ctx)?;
612					let casted = apply_cast(ctx, column.data(), &target_type, &inner_fragment)?;
613					Ok(ColumnWithName::new(label.clone(), casted))
614				})
615			}
616		}
617
618		Expression::If(e) => {
619			let condition = compile_expression(_ctx, &e.condition)?;
620			let then_expr = compile_expressions(_ctx, from_ref(e.then_expr.as_ref()))?;
621			let else_ifs: Vec<(CompiledExpr, Vec<CompiledExpr>)> = e
622				.else_ifs
623				.iter()
624				.map(|ei| {
625					Ok((
626						compile_expression(_ctx, &ei.condition)?,
627						compile_expressions(_ctx, from_ref(ei.then_expr.as_ref()))?,
628					))
629				})
630				.collect::<Result<Vec<_>>>()?;
631			let else_branch: Option<Vec<CompiledExpr>> = match &e.else_expr {
632				Some(expr) => Some(compile_expressions(_ctx, from_ref(expr.as_ref()))?),
633				None => None,
634			};
635			let fragment = e.fragment.clone();
636			CompiledExpr::new_multi(move |ctx| {
637				execute_if_multi(ctx, &condition, &then_expr, &else_ifs, &else_branch, &fragment)
638			})
639		}
640
641		Expression::Map(e) => {
642			let expressions = compile_expressions(_ctx, &e.expressions)?;
643			CompiledExpr::new_multi(move |ctx| execute_projection_multi(ctx, &expressions))
644		}
645
646		Expression::Extend(e) => {
647			let expressions = compile_expressions(_ctx, &e.expressions)?;
648			CompiledExpr::new_multi(move |ctx| execute_projection_multi(ctx, &expressions))
649		}
650
651		Expression::Call(e) => {
652			let compiled_args: Vec<CompiledExpr> =
653				e.args.iter().map(|arg| compile_expression(_ctx, arg)).collect::<Result<Vec<_>>>()?;
654			let type_named_args: Vec<Option<(ValueType, Fragment)>> =
655				e.args.iter()
656					.map(|arg| match arg {
657						Expression::Column(column) => ValueType::from_str(column.0.name.text())
658							.ok()
659							.map(|ty| (ty, column.0.name.clone())),
660						_ => None,
661					})
662					.collect();
663			let expr = e.clone();
664			CompiledExpr::new(move |ctx| {
665				let type_positions = ctx
666					.routines
667					.get_function(expr.func.0.text())
668					.map(|function| function.type_argument_positions().to_vec())
669					.unwrap_or_default();
670				let mut arg_columns = Vec::with_capacity(compiled_args.len());
671				for (index, compiled_arg) in compiled_args.iter().enumerate() {
672					match &type_named_args[index] {
673						Some((ty, fragment)) if type_positions.contains(&index) => {
674							arg_columns.push(type_column(ctx, ty, fragment));
675						}
676						_ => arg_columns.push(compiled_arg.execute(ctx)?),
677					}
678				}
679				let arguments = Columns::new(arg_columns);
680				call_builtin(ctx, &expr, arguments)
681			})
682		}
683
684		Expression::SumTypeConstructor(_) => {
685			panic!(
686				"SumTypeConstructor in expression context - constructors should be expanded by InlineDataNode before expression compilation"
687			);
688		}
689
690		Expression::IsVariant(e) => {
691			let col_name = match e.expression.as_ref() {
692				Expression::Column(c) => c.0.name.text().to_string(),
693				other => display_label(other).text().to_string(),
694			};
695			let tag_col_name = format!("{}_tag", col_name);
696			let tag = e.tag.expect("IS variant tag must be resolved before compilation");
697			let fragment = e.fragment.clone();
698			CompiledExpr::new(move |ctx| {
699				if let Some(tag_col) =
700					ctx.columns.iter().find(|c| c.name().text() == tag_col_name.as_str())
701				{
702					match tag_col.data() {
703						ColumnBuffer::Uint1(container) => {
704							let results: Vec<bool> = container
705								.iter()
706								.take(ctx.row_count)
707								.map(|v| v == Some(tag))
708								.collect();
709							Ok(ColumnWithName::new(
710								fragment.clone(),
711								ColumnBuffer::bool(results),
712							))
713						}
714						_ => Ok(ColumnWithName {
715							name: fragment.clone(),
716							data: ColumnBuffer::none_typed(
717								ValueType::Boolean,
718								ctx.row_count,
719							),
720						}),
721					}
722				} else {
723					Ok(ColumnWithName {
724						name: fragment.clone(),
725						data: ColumnBuffer::none_typed(ValueType::Boolean, ctx.row_count),
726					})
727				}
728			})
729		}
730
731		Expression::FieldAccess(e) => {
732			let field_name = e.field.text().to_string();
733
734			let var_name = match e.object.as_ref() {
735				Expression::Variable(var_expr) => Some(var_expr.name().to_string()),
736				_ => None,
737			};
738			let object = compile_expression(_ctx, &e.object)?;
739			CompiledExpr::new(move |ctx| {
740				if let Some(ref variable_name) = var_name {
741					match ctx.symbols.get(variable_name) {
742						Some(Variable::Columns {
743							columns,
744						}) if !columns.is_scalar() => {
745							let col_pos = columns
746								.names
747								.iter()
748								.position(|n| n.text() == field_name);
749							match col_pos {
750								Some(pos) => {
751									let value = columns.columns[pos].get_value(0);
752									let row_count =
753										ctx.take.unwrap_or(ctx.row_count);
754									let mut data = ColumnBuffer::with_capacity(
755										value.get_type(),
756										row_count,
757									);
758									for _ in 0..row_count {
759										data.push_value(value.clone());
760									}
761									Ok(ColumnWithName {
762										name: Fragment::internal(&field_name),
763										data,
764									})
765								}
766								None => {
767									let available: Vec<String> = columns
768										.names
769										.iter()
770										.map(|n| n.text().to_string())
771										.collect();
772									Err(TypeError::Runtime {
773										kind: RuntimeErrorKind::FieldNotFound {
774											variable: variable_name
775												.to_string(),
776											field: field_name.to_string(),
777											available,
778										},
779										message: format!(
780											"Field '{}' not found on variable '{}'",
781											field_name, variable_name
782										),
783									}
784									.into())
785								}
786							}
787						}
788						Some(Variable::Columns {
789							..
790						})
791						| Some(Variable::Closure(_)) => Err(TypeError::Runtime {
792							kind: RuntimeErrorKind::FieldNotFound {
793								variable: variable_name.to_string(),
794								field: field_name.to_string(),
795								available: vec![],
796							},
797							message: format!(
798								"Field '{}' not found on variable '{}'",
799								field_name, variable_name
800							),
801						}
802						.into()),
803						Some(Variable::ForIterator {
804							..
805						}) => Err(TypeError::Runtime {
806							kind: RuntimeErrorKind::VariableIsDataframe {
807								name: variable_name.to_string(),
808							},
809							message: format!(
810								"Variable '{}' contains a dataframe and cannot be used directly in scalar expressions",
811								variable_name
812							),
813						}
814						.into()),
815						None => Err(TypeError::Runtime {
816							kind: RuntimeErrorKind::VariableNotFound {
817								name: variable_name.to_string(),
818							},
819							message: format!("Variable '{}' is not defined", variable_name),
820						}
821						.into()),
822					}
823				} else {
824					let _obj_col = object.execute(ctx)?;
825					Err(TypeError::Runtime {
826						kind: RuntimeErrorKind::FieldNotFound {
827							variable: "<expression>".to_string(),
828							field: field_name.to_string(),
829							available: vec![],
830						},
831						message: format!(
832							"Field '{}' not found on variable '<expression>'",
833							field_name
834						),
835					}
836					.into())
837				}
838			})
839		}
840	})
841}
842
843fn compile_expressions(ctx: &CompileContext, exprs: &[Expression]) -> Result<Vec<CompiledExpr>> {
844	exprs.iter().map(|e| compile_expression(ctx, e)).collect()
845}
846
847fn type_column(ctx: &EvalContext, ty: &ValueType, fragment: &Fragment) -> ColumnWithName {
848	let row_count = ctx.take.unwrap_or(ctx.row_count);
849	let values: Vec<Value> = (0..row_count).map(|_| Value::Type(ty.clone())).collect();
850	ColumnWithName::new(fragment.text(), ColumnBuffer::any(values))
851}
852
853fn combine_bool_columns(
854	left: ColumnWithName,
855	right: ColumnWithName,
856	fragment: Fragment,
857	combine_fn: fn(bool, bool) -> bool,
858) -> Result<ColumnWithName> {
859	binary_op_unwrap_option(&left, &right, fragment.clone(), |left, right| match (left.data(), right.data()) {
860		(ColumnBuffer::Bool(l), ColumnBuffer::Bool(r)) => {
861			let len = l.len();
862			let mut data = Vec::with_capacity(len);
863			let mut bitvec = Vec::with_capacity(len);
864
865			for i in 0..len {
866				let l_defined = l.is_defined(i);
867				let r_defined = r.is_defined(i);
868				let l_val = l.data().get(i);
869				let r_val = r.data().get(i);
870
871				if l_defined && r_defined {
872					data.push(combine_fn(l_val, r_val));
873					bitvec.push(true);
874				} else {
875					data.push(false);
876					bitvec.push(false);
877				}
878			}
879
880			Ok(ColumnWithName {
881				name: fragment.clone(),
882				data: ColumnBuffer::bool_with_bitvec(data, bitvec),
883			})
884		}
885		_ => {
886			unreachable!("combine_bool_columns should only be called with boolean columns")
887		}
888	})
889}
890
891fn list_items_contain(items: &[Value], element: &Value, fragment: &Fragment) -> bool {
892	if items.iter().any(|item| item == element) {
893		return true;
894	}
895	if items.is_empty() {
896		return false;
897	}
898
899	if let Some(items_buf) = build_homogeneous_buffer(items) {
900		let elems_buf = ColumnBuffer::from_many(element.clone(), items.len());
901		let items_col = ColumnWithName::new(fragment.clone(), items_buf);
902		let elems_col = ColumnWithName::new(fragment.clone(), elems_buf);
903		return compare_columns::<Equal>(&items_col, &elems_col, fragment.clone(), |f, l, r| {
904			TypeError::BinaryOperatorNotApplicable {
905				operator: BinaryOp::Equal,
906				left: l,
907				right: r,
908				fragment: f,
909			}
910			.into_diagnostic()
911		})
912		.map(|c| bool_column_has_true(&c))
913		.unwrap_or(false);
914	}
915
916	list_items_contain_per_item(items, element, fragment)
917}
918
919fn list_items_contain_per_item(items: &[Value], element: &Value, fragment: &Fragment) -> bool {
920	items.iter().any(|item| {
921		let item_col = ColumnWithName::new(fragment.clone(), ColumnBuffer::from(item.clone()));
922		let elem_col = ColumnWithName::new(fragment.clone(), ColumnBuffer::from(element.clone()));
923		compare_columns::<Equal>(&item_col, &elem_col, fragment.clone(), |f, l, r| {
924			TypeError::BinaryOperatorNotApplicable {
925				operator: BinaryOp::Equal,
926				left: l,
927				right: r,
928				fragment: f,
929			}
930			.into_diagnostic()
931		})
932		.ok()
933		.and_then(|c| match c.data() {
934			ColumnBuffer::Bool(b) => Some(b.data().get(0)),
935			_ => None,
936		})
937		.unwrap_or(false)
938	})
939}
940
941fn bool_column_has_true(col: &ColumnWithName) -> bool {
942	match col.data() {
943		ColumnBuffer::Bool(b) => b.data().any(),
944		ColumnBuffer::Option {
945			inner,
946			bitvec,
947		} => match inner.as_ref() {
948			ColumnBuffer::Bool(b) => {
949				let n = bitvec.len().min(b.len());
950				(0..n).any(|i| bitvec.get(i) && b.data().get(i))
951			}
952			_ => false,
953		},
954		_ => false,
955	}
956}
957
958fn build_homogeneous_buffer(items: &[Value]) -> Option<ColumnBuffer> {
959	let first = items.first()?;
960	let first_disc = discriminant(first);
961	if !items.iter().all(|v| discriminant(v) == first_disc) {
962		return None;
963	}
964
965	macro_rules! collect {
966		($variant:ident, $constructor:ident, |$x:ident| $convert:expr) => {{
967			let data: Vec<_> = items
968				.iter()
969				.map(|v| match v {
970					Value::$variant($x) => $convert,
971					_ => unreachable!("homogeneous check guarantees variant"),
972				})
973				.collect();
974			Some(ColumnBuffer::$constructor(data))
975		}};
976	}
977
978	match first {
979		Value::Boolean(_) => collect!(Boolean, bool, |x| *x),
980		Value::Float4(_) => collect!(Float4, float4, |x| x.value()),
981		Value::Float8(_) => collect!(Float8, float8, |x| x.value()),
982		Value::Int1(_) => collect!(Int1, int1, |x| *x),
983		Value::Int2(_) => collect!(Int2, int2, |x| *x),
984		Value::Int4(_) => collect!(Int4, int4, |x| *x),
985		Value::Int8(_) => collect!(Int8, int8, |x| *x),
986		Value::Int16(_) => collect!(Int16, int16, |x| *x),
987		Value::Uint1(_) => collect!(Uint1, uint1, |x| *x),
988		Value::Uint2(_) => collect!(Uint2, uint2, |x| *x),
989		Value::Uint4(_) => collect!(Uint4, uint4, |x| *x),
990		Value::Uint8(_) => collect!(Uint8, uint8, |x| *x),
991		Value::Uint16(_) => collect!(Uint16, uint16, |x| *x),
992		Value::Utf8(_) => collect!(Utf8, utf8, |x| x.clone()),
993		Value::Date(_) => collect!(Date, date, |x| *x),
994		Value::DateTime(_) => collect!(DateTime, datetime, |x| *x),
995		Value::Time(_) => collect!(Time, time, |x| *x),
996		Value::Duration(_) => collect!(Duration, duration, |x| *x),
997		Value::Uuid4(_) => collect!(Uuid4, uuid4, |x| *x),
998		Value::Uuid7(_) => collect!(Uuid7, uuid7, |x| *x),
999		Value::IdentityId(_) => collect!(IdentityId, identity_id, |x| *x),
1000		Value::Blob(_) => collect!(Blob, blob, |x| x.clone()),
1001		Value::Int(_) => collect!(Int, int, |x| x.clone()),
1002		Value::Uint(_) => collect!(Uint, uint, |x| x.clone()),
1003		Value::Decimal(_) => collect!(Decimal, decimal, |x| x.clone()),
1004		Value::DictionaryId(_) => collect!(DictionaryId, dictionary_id, |x| *x),
1005
1006		_ => None,
1007	}
1008}
1009
1010fn list_contains_element(
1011	list_col: &ColumnWithName,
1012	element_col: &ColumnWithName,
1013	fragment: &Fragment,
1014) -> Result<ColumnWithName> {
1015	let len = list_col.data().len();
1016	let mut data = Vec::with_capacity(len);
1017
1018	for i in 0..len {
1019		let list_value = list_col.data().get_value(i);
1020		let element_value = element_col.data().get_value(i);
1021
1022		let contained = match &list_value {
1023			Value::List(items) => list_items_contain(items, &element_value, fragment),
1024			Value::Tuple(items) => list_items_contain(items, &element_value, fragment),
1025			Value::Any(boxed) => match boxed.as_ref() {
1026				Value::List(items) => list_items_contain(items, &element_value, fragment),
1027				Value::Tuple(items) => list_items_contain(items, &element_value, fragment),
1028				_ => false,
1029			},
1030			_ => false,
1031		};
1032		data.push(contained);
1033	}
1034
1035	Ok(ColumnWithName::new(fragment.clone(), ColumnBuffer::bool(data)))
1036}
1037
1038fn negate_column(col: ColumnWithName, fragment: Fragment) -> ColumnWithName {
1039	unary_op_unwrap_option(&col, |col| match col.data() {
1040		ColumnBuffer::Bool(container) => {
1041			let len = container.len();
1042			let mut data = Vec::with_capacity(len);
1043			let mut bitvec = Vec::with_capacity(len);
1044
1045			for i in 0..len {
1046				if container.is_defined(i) {
1047					data.push(!container.data().get(i));
1048					bitvec.push(true);
1049				} else {
1050					data.push(false);
1051					bitvec.push(false);
1052				}
1053			}
1054
1055			Ok(ColumnWithName {
1056				name: fragment.clone(),
1057				data: ColumnBuffer::bool_with_bitvec(data, bitvec),
1058			})
1059		}
1060		_ => unreachable!("negate_column should only be called with boolean columns"),
1061	})
1062	.unwrap()
1063}
1064
1065fn is_truthy(value: &Value) -> bool {
1066	match value {
1067		Value::Boolean(true) => true,
1068		Value::Boolean(false) => false,
1069		Value::None {
1070			..
1071		} => false,
1072		Value::Int1(0) | Value::Int2(0) | Value::Int4(0) | Value::Int8(0) | Value::Int16(0) => false,
1073		Value::Uint1(0) | Value::Uint2(0) | Value::Uint4(0) | Value::Uint8(0) | Value::Uint16(0) => false,
1074		Value::Int1(_) | Value::Int2(_) | Value::Int4(_) | Value::Int8(_) | Value::Int16(_) => true,
1075		Value::Uint1(_) | Value::Uint2(_) | Value::Uint4(_) | Value::Uint8(_) | Value::Uint16(_) => true,
1076		Value::Utf8(s) => !s.is_empty(),
1077		_ => true,
1078	}
1079}
1080
1081fn describe_branch(columns: &[ColumnWithName]) -> Vec<String> {
1082	columns.iter().map(|col| format!("{}: {}", col.name.text(), col.data().get_type())).collect()
1083}
1084
1085fn execute_if_multi(
1086	ctx: &EvalContext,
1087	condition: &CompiledExpr,
1088	then_expr: &[CompiledExpr],
1089	else_ifs: &[(CompiledExpr, Vec<CompiledExpr>)],
1090	else_branch: &Option<Vec<CompiledExpr>>,
1091	_fragment: &Fragment,
1092) -> Result<Vec<ColumnWithName>> {
1093	const NO_BRANCH: usize = usize::MAX;
1094
1095	let condition_column = condition.execute(ctx)?;
1096
1097	let else_index = else_ifs.len() + 1;
1098	let mut selection: Vec<usize> = Vec::with_capacity(ctx.row_count);
1099	let mut unresolved: Vec<usize> = Vec::new();
1100
1101	for row_idx in 0..ctx.row_count {
1102		if is_truthy(&condition_column.data().get_value(row_idx)) {
1103			selection.push(0);
1104		} else {
1105			selection.push(NO_BRANCH);
1106			unresolved.push(row_idx);
1107		}
1108	}
1109
1110	for (offset, (else_if_condition, _)) in else_ifs.iter().enumerate() {
1111		if unresolved.is_empty() {
1112			break;
1113		}
1114		let else_if_column = else_if_condition.execute(ctx)?;
1115		unresolved.retain(|&row_idx| {
1116			if is_truthy(&else_if_column.data().get_value(row_idx)) {
1117				selection[row_idx] = offset + 1;
1118				false
1119			} else {
1120				true
1121			}
1122		});
1123	}
1124
1125	if else_branch.is_some() {
1126		for &row_idx in &unresolved {
1127			selection[row_idx] = else_index;
1128		}
1129	}
1130
1131	let mut evaluated: Vec<Option<Vec<ColumnWithName>>> = (0..=else_index).map(|_| None).collect();
1132	for &branch in &selection {
1133		if branch == NO_BRANCH || evaluated[branch].is_some() {
1134			continue;
1135		}
1136		let columns = if branch == 0 {
1137			execute_multi_exprs(ctx, then_expr)?
1138		} else if branch < else_index {
1139			execute_multi_exprs(ctx, &else_ifs[branch - 1].1)?
1140		} else {
1141			execute_multi_exprs(ctx, else_branch.as_ref().unwrap())?
1142		};
1143		evaluated[branch] = Some(columns);
1144	}
1145
1146	let mut layout: Option<(Vec<ValueType>, Vec<String>)> = None;
1147	for columns in evaluated.iter().flatten() {
1148		let Some((expected, expected_names)) = layout.as_mut() else {
1149			layout = Some((
1150				columns.iter().map(|col| col.data().get_type().inner_type().clone()).collect(),
1151				describe_branch(columns),
1152			));
1153			continue;
1154		};
1155
1156		let mut disagrees = columns.len() != expected.len();
1157		if !disagrees {
1158			for (slot, col) in expected.iter_mut().zip(columns.iter()) {
1159				let incoming = col.data().get_type().inner_type().clone();
1160				if *slot == ValueType::Any {
1161					*slot = incoming;
1162				} else if incoming != ValueType::Any && incoming != *slot {
1163					disagrees = true;
1164					break;
1165				}
1166			}
1167		}
1168
1169		if disagrees {
1170			return Err(TypeError::Runtime {
1171				kind: RuntimeErrorKind::ConditionalBranchMismatch {
1172					expected: expected_names.clone(),
1173					actual: describe_branch(columns),
1174					fragment: _fragment.clone(),
1175				},
1176				message: "conditional branches produce different columns".to_string(),
1177			}
1178			.into());
1179		}
1180	}
1181
1182	let mut result_data: Option<Vec<ColumnBuffer>> = None;
1183	let mut result_names: Vec<Fragment> = Vec::new();
1184
1185	for (row_idx, &selected) in selection.iter().enumerate() {
1186		let branch_results: &[ColumnWithName] = match selected {
1187			NO_BRANCH => &[],
1188			branch => evaluated[branch].as_deref().unwrap(),
1189		};
1190
1191		if branch_results.is_empty() {
1192			if let Some(data) = result_data.as_mut() {
1193				for col_data in data.iter_mut() {
1194					col_data.push_value(Value::none());
1195				}
1196			}
1197			continue;
1198		}
1199
1200		if result_data.is_none() {
1201			let mut data: Vec<ColumnBuffer> = branch_results
1202				.iter()
1203				.map(|col| ColumnBuffer::with_capacity(col.data().get_type(), ctx.row_count))
1204				.collect();
1205			for _ in 0..row_idx {
1206				for col_data in data.iter_mut() {
1207					col_data.push_value(Value::none());
1208				}
1209			}
1210			result_data = Some(data);
1211			result_names = branch_results.iter().map(|col| col.name.clone()).collect();
1212		}
1213
1214		let data = result_data.as_mut().unwrap();
1215		for (slot, branch_col) in data.iter_mut().zip(branch_results.iter()) {
1216			slot.push_value(branch_col.data().get_value(row_idx));
1217		}
1218	}
1219
1220	let result_data = result_data.unwrap_or_default();
1221	let result: Vec<ColumnWithName> = result_data
1222		.into_iter()
1223		.enumerate()
1224		.map(|(i, data)| ColumnWithName {
1225			name: result_names.get(i).cloned().unwrap_or_else(|| Fragment::internal("column")),
1226			data,
1227		})
1228		.collect();
1229
1230	if result.is_empty() {
1231		Ok(vec![ColumnWithName {
1232			name: Fragment::internal("none"),
1233			data: ColumnBuffer::none_typed(ValueType::Boolean, ctx.row_count),
1234		}])
1235	} else {
1236		Ok(result)
1237	}
1238}
1239
1240fn execute_multi_exprs(ctx: &EvalContext, exprs: &[CompiledExpr]) -> Result<Vec<ColumnWithName>> {
1241	let mut result = Vec::new();
1242	for expr in exprs {
1243		result.extend(expr.execute_multi(ctx)?);
1244	}
1245	Ok(result)
1246}
1247
1248fn execute_projection_multi(ctx: &EvalContext, expressions: &[CompiledExpr]) -> Result<Vec<ColumnWithName>> {
1249	let mut result = Vec::with_capacity(expressions.len());
1250
1251	for expr in expressions {
1252		let column = expr.execute(ctx)?;
1253		let name = column.name.text().to_string();
1254		result.push(ColumnWithName::new(Fragment::internal(name), column.data));
1255	}
1256
1257	Ok(result)
1258}
1259
1260fn apply_cast(ctx: &EvalContext, data: &ColumnBuffer, target: &ValueType, fragment: &Fragment) -> Result<ColumnBuffer> {
1261	cast_column_data(ctx, data, target.clone(), &|| fragment.clone())
1262		.map_err(|e| wrap_cast_error(e, fragment.clone(), target))
1263}
1264
1265fn wrap_cast_error(err: Error, fragment: Fragment, target: &ValueType) -> Error {
1266	if err.0.code.starts_with("CAST_") {
1267		return err;
1268	}
1269	let cause = err.diagnostic();
1270	let wrapped = if target.is_bool() {
1271		CastError::InvalidBoolean {
1272			fragment,
1273			cause,
1274		}
1275	} else if target.is_temporal() {
1276		CastError::InvalidTemporal {
1277			fragment,
1278			target: target.clone(),
1279			cause,
1280		}
1281	} else if target.is_uuid() || *target == ValueType::IdentityId {
1282		CastError::InvalidUuid {
1283			fragment,
1284			target: target.clone(),
1285			cause,
1286		}
1287	} else {
1288		CastError::InvalidNumber {
1289			fragment,
1290			target: target.clone(),
1291			cause,
1292		}
1293	};
1294	Error::from(wrapped)
1295}
1296
1297#[cfg(test)]
1298mod tests {
1299	use reifydb_core::{
1300		interface::identifier::ColumnIdentifier,
1301		value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns},
1302	};
1303	use reifydb_rql::expression::{
1304		CastExpression, ColumnExpression, ConstantExpression, ElseIfExpression, Expression, IfExpression,
1305		MapExpression, TypeExpression,
1306	};
1307	use reifydb_value::{
1308		fragment::Fragment,
1309		value::{Value, value_type::ValueType},
1310	};
1311
1312	use crate::expression::{context::EvalContext, eval::evaluate};
1313
1314	fn column(name: &str) -> Expression {
1315		Expression::Column(ColumnExpression(ColumnIdentifier::with_alias(
1316			Fragment::internal("t"),
1317			Fragment::internal(name),
1318		)))
1319	}
1320
1321	fn uncastable() -> Expression {
1322		Expression::Cast(CastExpression {
1323			fragment: Fragment::testing_empty(),
1324			expression: Box::new(Expression::Constant(ConstantExpression::Text {
1325				fragment: Fragment::internal("not-a-number"),
1326			})),
1327			to: TypeExpression {
1328				fragment: Fragment::testing_empty(),
1329				ty: ValueType::Int4,
1330			},
1331		})
1332	}
1333
1334	fn conditional(
1335		condition: Expression,
1336		then_expr: Expression,
1337		else_ifs: Vec<(Expression, Expression)>,
1338		else_expr: Option<Expression>,
1339	) -> Expression {
1340		Expression::If(IfExpression {
1341			condition: Box::new(condition),
1342			then_expr: Box::new(then_expr),
1343			else_ifs: else_ifs
1344				.into_iter()
1345				.map(|(condition, then_expr)| ElseIfExpression {
1346					condition: Box::new(condition),
1347					then_expr: Box::new(then_expr),
1348					fragment: Fragment::testing_empty(),
1349				})
1350				.collect(),
1351			else_expr: else_expr.map(Box::new),
1352			fragment: Fragment::testing_empty(),
1353		})
1354	}
1355
1356	fn bools(name: &str, data: [bool; 4]) -> ColumnWithName {
1357		ColumnWithName::new(Fragment::internal(name), ColumnBuffer::bool(data))
1358	}
1359
1360	fn ints(name: &str, data: [i32; 4]) -> ColumnWithName {
1361		ColumnWithName::new(Fragment::internal(name), ColumnBuffer::int4(data))
1362	}
1363
1364	#[test]
1365	fn every_row_reads_its_own_index_from_the_branch_it_selected() {
1366		// A branch is evaluated as a whole column, so row i must take branch[i]. Assembling the
1367		// result in append order instead of by row index shifts every value after the first switch.
1368		let base = EvalContext::testing();
1369		let ctx = base.with_eval(
1370			Columns::new(vec![
1371				bools("flag", [true, false, false, true]),
1372				ints("hi", [100, 200, 300, 400]),
1373				ints("lo", [1, 2, 3, 4]),
1374			]),
1375			4,
1376		);
1377
1378		let result =
1379			evaluate(&ctx, &conditional(column("flag"), column("hi"), vec![], Some(column("lo")))).unwrap();
1380
1381		assert_eq!(*result.data(), ColumnBuffer::int4([100, 2, 3, 400]));
1382	}
1383
1384	#[test]
1385	fn an_else_if_chain_gives_each_row_its_first_matching_branch() {
1386		// Later conditions must never override an earlier match: row 1 satisfies both `second` and
1387		// nothing else, row 2 satisfies `second` alone, and row 3 falls through to the else.
1388		let base = EvalContext::testing();
1389		let ctx = base.with_eval(
1390			Columns::new(vec![
1391				bools("first", [true, false, false, false]),
1392				bools("second", [true, true, true, false]),
1393				ints("a", [10, 20, 30, 40]),
1394				ints("b", [1, 2, 3, 4]),
1395				ints("c", [-1, -2, -3, -4]),
1396			]),
1397			4,
1398		);
1399
1400		let result = evaluate(
1401			&ctx,
1402			&conditional(
1403				column("first"),
1404				column("a"),
1405				vec![(column("second"), column("b"))],
1406				Some(column("c")),
1407			),
1408		)
1409		.unwrap();
1410
1411		assert_eq!(*result.data(), ColumnBuffer::int4([10, 2, 3, -4]));
1412	}
1413
1414	#[test]
1415	fn a_row_that_matches_no_branch_becomes_none() {
1416		// Without an else branch the unmatched rows must still occupy their slot, otherwise the
1417		// result column is shorter than the input and every downstream row pairs with the wrong key.
1418		let base = EvalContext::testing();
1419		let ctx = base.with_eval(
1420			Columns::new(vec![bools("flag", [true, false, false, true]), ints("hi", [7, 8, 9, 10])]),
1421			4,
1422		);
1423
1424		let result = evaluate(&ctx, &conditional(column("flag"), column("hi"), vec![], None)).unwrap();
1425
1426		assert_eq!(result.data().len(), 4);
1427		assert_eq!(result.data().get_value(0), Value::Int4(7));
1428		assert!(matches!(result.data().get_value(1), Value::None { .. }));
1429		assert!(matches!(result.data().get_value(2), Value::None { .. }));
1430		assert_eq!(result.data().get_value(3), Value::Int4(10));
1431	}
1432
1433	#[test]
1434	fn a_branch_that_no_row_selects_is_never_evaluated() {
1435		// Hoisting a branch out of the row loop must not make it eager: a guard exists precisely to
1436		// keep a failing expression away from the rows that cannot satisfy it.
1437		let base = EvalContext::testing();
1438		let all_true = base.with_eval(
1439			Columns::new(vec![bools("flag", [true, true, true, true]), ints("hi", [1, 2, 3, 4])]),
1440			4,
1441		);
1442
1443		let skipped =
1444			evaluate(&all_true, &conditional(column("flag"), column("hi"), vec![], Some(uncastable())));
1445
1446		assert!(skipped.is_ok(), "an else branch no row selects must not be evaluated");
1447
1448		let one_false = base.with_eval(
1449			Columns::new(vec![bools("flag", [true, true, false, true]), ints("hi", [1, 2, 3, 4])]),
1450			4,
1451		);
1452
1453		let taken =
1454			evaluate(&one_false, &conditional(column("flag"), column("hi"), vec![], Some(uncastable())));
1455
1456		assert!(
1457			taken.is_err(),
1458			"the branch must really fail when a row selects it, or the case above is vacuous"
1459		);
1460	}
1461
1462	fn multi(names: [&str; 2]) -> Expression {
1463		Expression::Map(MapExpression {
1464			expressions: names.iter().map(|name| column(name)).collect(),
1465			fragment: Fragment::testing_empty(),
1466		})
1467	}
1468
1469	fn none_literal() -> Expression {
1470		Expression::Constant(ConstantExpression::None {
1471			fragment: Fragment::testing_empty(),
1472		})
1473	}
1474
1475	fn four_row_ctx(extra: Vec<ColumnWithName>) -> Columns {
1476		let mut cols = vec![bools("flag", [true, false, false, true])];
1477		cols.extend(extra);
1478		Columns::new(cols)
1479	}
1480
1481	#[test]
1482	fn branches_of_different_types_are_rejected_instead_of_aborting() {
1483		// The column buffer matches the value variant exactly and panics on anything else, so an
1484		// unvalidated mismatch takes the process down rather than failing the query.
1485		let base = EvalContext::testing();
1486		let ctx = base.with_eval(
1487			four_row_ctx(vec![
1488				ints("small", [1, 2, 3, 4]),
1489				ColumnWithName::new(Fragment::internal("wide"), ColumnBuffer::int8([5i64, 6, 7, 8])),
1490			]),
1491			4,
1492		);
1493
1494		let err = evaluate(&ctx, &conditional(column("flag"), column("small"), vec![], Some(column("wide"))))
1495			.expect_err("int4 and int8 branches must not be accepted");
1496
1497		assert_eq!(err.0.code, "RUNTIME_012");
1498	}
1499
1500	#[test]
1501	fn an_optional_branch_still_pairs_with_its_bare_type() {
1502		// Option is a wrapper over the same base type, so these branches agree; rejecting them
1503		// would break every conditional whose branches differ only in nullability.
1504		let base = EvalContext::testing();
1505		let ctx = base.with_eval(
1506			four_row_ctx(vec![
1507				ints("bare", [1, 2, 3, 4]),
1508				ColumnWithName::new(
1509					Fragment::internal("opt"),
1510					ColumnBuffer::int4_with_bitvec([9, 8, 7, 6], vec![true, false, true, true]),
1511				),
1512			]),
1513			4,
1514		);
1515
1516		let result = evaluate(&ctx, &conditional(column("flag"), column("bare"), vec![], Some(column("opt"))))
1517			.expect("a bare and an optional branch of one base type must agree");
1518
1519		assert_eq!(result.data().len(), 4);
1520	}
1521
1522	#[test]
1523	fn a_none_branch_widens_to_the_other_branch_type() {
1524		// A none literal carries Option(Any); treating Any as a concrete type would reject the
1525		// guarded-value shape that every conditional projection relies on.
1526		let base = EvalContext::testing();
1527		let ctx = base.with_eval(four_row_ctx(vec![ints("hi", [7, 8, 9, 10])]), 4);
1528
1529		let result = evaluate(&ctx, &conditional(column("flag"), column("hi"), vec![], Some(none_literal())))
1530			.expect("a none branch must widen to the other branch type");
1531
1532		assert_eq!(result.data().get_value(0), Value::Int4(7));
1533		assert!(matches!(result.data().get_value(1), Value::None { .. }));
1534	}
1535
1536	#[test]
1537	fn branches_of_different_widths_are_rejected() {
1538		// A narrower branch used to leave its unfilled columns short, silently misaligning every
1539		// value after the first switch; a wider one had its surplus columns dropped.
1540		let base = EvalContext::testing();
1541		let ctx = base.with_eval(four_row_ctx(vec![ints("a", [1, 2, 3, 4]), ints("b", [10, 20, 30, 40])]), 4);
1542
1543		let wide_then =
1544			evaluate(&ctx, &conditional(column("flag"), multi(["a", "b"]), vec![], Some(column("a"))))
1545				.expect_err("a two column branch must not pair with a one column branch");
1546		assert_eq!(wide_then.0.code, "RUNTIME_012");
1547
1548		let wide_else =
1549			evaluate(&ctx, &conditional(column("flag"), column("a"), vec![], Some(multi(["a", "b"]))))
1550				.expect_err("a one column branch must not pair with a two column branch");
1551		assert_eq!(wide_else.0.code, "RUNTIME_012");
1552	}
1553
1554	#[test]
1555	fn a_mismatched_branch_no_row_selects_is_still_not_rejected() {
1556		// Validation must read only the branches that were evaluated, otherwise it resurrects the
1557		// eager evaluation that the guard exists to prevent.
1558		let base = EvalContext::testing();
1559		let ctx = base.with_eval(
1560			Columns::new(vec![
1561				bools("flag", [true, true, true, true]),
1562				ints("small", [1, 2, 3, 4]),
1563				ColumnWithName::new(Fragment::internal("wide"), ColumnBuffer::int8([5i64, 6, 7, 8])),
1564			]),
1565			4,
1566		);
1567
1568		let result =
1569			evaluate(&ctx, &conditional(column("flag"), column("small"), vec![], Some(column("wide"))))
1570				.expect("an unselected branch must not be validated");
1571
1572		assert_eq!(result.data().len(), 4);
1573	}
1574
1575	#[test]
1576	fn a_multi_column_value_in_a_single_column_slot_is_rejected() {
1577		// Taking the first column and discarding the rest loses data with no signal; map, extend
1578		// and patch all reach a value expression through this path.
1579		let base = EvalContext::testing();
1580		let ctx = base.with_eval(four_row_ctx(vec![ints("a", [1, 2, 3, 4]), ints("b", [10, 20, 30, 40])]), 4);
1581
1582		let err = evaluate(&ctx, &multi(["a", "b"])).expect_err("a two column value must not be truncated");
1583
1584		assert_eq!(err.0.code, "RUNTIME_010");
1585	}
1586}