1use std::sync::Arc;
16
17use crate::executor::expression::evaluator::collection_operations::CollectionOperationEvaluator;
18use crate::executor::expression::evaluator::expression_evaluator::ExpressionEvaluator;
19use crate::executor::expression::evaluator::functions::FunctionEvaluator;
20use crate::executor::expression::evaluator::operations::{
21 BinaryOperationEvaluator, UnaryOperationEvaluator,
22};
23use crate::executor::expression::functions::{
24 global_registry, global_registry_ref, OwnedFunctionRef,
25};
26use crate::executor::expression::ExpressionError;
27use crate::executor::streaming::chunk::TypedColumn;
28use crate::executor::streaming::context::BorrowedRowContext;
29use crate::executor::streaming::slot::{SlotId, SlotLayout};
30use crate::executor::streaming::subquery::EvalEnv;
31use linkrs_core::types::expr::Expression;
32use linkrs_core::types::operators::{BinaryOperator, UnaryOperator};
33use linkrs_core::value::list::List;
34use linkrs_core::value::NullType;
35use linkrs_core::DataType;
36use linkrs_core::Value;
37
38static COMPILED_EVAL_ENABLED: std::sync::atomic::AtomicBool =
43 std::sync::atomic::AtomicBool::new(true);
44
45pub fn set_compiled_eval_enabled(enabled: bool) {
47 COMPILED_EVAL_ENABLED.store(enabled, std::sync::atomic::Ordering::Relaxed);
48}
49
50pub fn compiled_eval_enabled() -> bool {
52 COMPILED_EVAL_ENABLED.load(std::sync::atomic::Ordering::Relaxed)
53}
54
55#[derive(Debug, Clone)]
58pub enum ColumnarValue {
59 Const(Value),
61 Column(Vec<Value>),
63}
64
65impl ColumnarValue {
66 pub fn into_values(self, len: usize) -> Vec<Value> {
68 match self {
69 ColumnarValue::Const(v) => vec![v; len],
70 ColumnarValue::Column(col) => col,
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
81pub enum CompiledExpr {
82 Const(Value),
84 Slot(SlotId),
86 CompoundSlot(SlotId),
89 Unary {
91 op: UnaryOperator,
92 operand: Box<CompiledExpr>,
93 },
94 Binary {
96 op: BinaryOperator,
97 left: Box<CompiledExpr>,
98 right: Box<CompiledExpr>,
99 },
100 Case {
102 test: Option<Box<CompiledExpr>>,
103 conditions: Vec<(CompiledExpr, CompiledExpr)>,
104 default: Option<Box<CompiledExpr>>,
105 },
106 Function {
108 name: String,
109 func: Option<OwnedFunctionRef>,
110 args: Vec<CompiledExpr>,
111 },
112 TypeCast {
114 expr: Box<CompiledExpr>,
115 target_type: DataType,
116 },
117 List(Vec<CompiledExpr>),
119 Map(Vec<(String, CompiledExpr)>),
121 Subscript {
123 collection: Box<CompiledExpr>,
124 index: Box<CompiledExpr>,
125 },
126 Range {
128 collection: Box<CompiledExpr>,
129 start: Option<Box<CompiledExpr>>,
130 end: Option<Box<CompiledExpr>>,
131 },
132 Path(Vec<CompiledExpr>),
134 Parameter(String),
136 SessionVariable(String),
138 Fallback(Expression),
141}
142
143impl CompiledExpr {
144 pub fn compile(expression: &Expression, layout: &SlotLayout) -> Self {
151 match expression {
152 Expression::Literal(v) => CompiledExpr::Const(v.clone()),
153 Expression::Vector(d) => CompiledExpr::Const(Value::vector(d.clone())),
154
155 Expression::Variable(name) => match layout.slot_id(name) {
156 Some(slot) => CompiledExpr::Slot(slot),
157 None => CompiledExpr::Fallback(expression.clone()),
158 },
159
160 Expression::Property { object, property } => {
164 if let Expression::Variable(var_name) = object.as_ref() {
165 let compound = format!("{}.{}", var_name, property);
166 if let Some(slot) = layout.slot_id(&compound) {
167 return CompiledExpr::CompoundSlot(slot);
168 }
169 }
170 CompiledExpr::Fallback(expression.clone())
171 }
172
173 Expression::Binary { left, op, right } => {
174 let left = Self::compile(left, layout);
175 let right = Self::compile(right, layout);
176 match (left, right) {
177 (CompiledExpr::Const(l), CompiledExpr::Const(r)) => {
178 match BinaryOperationEvaluator::evaluate(&l, op, &r) {
179 Ok(v) => CompiledExpr::Const(v),
180 Err(_) => CompiledExpr::Binary {
183 op: *op,
184 left: Box::new(CompiledExpr::Const(l)),
185 right: Box::new(CompiledExpr::Const(r)),
186 },
187 }
188 }
189 (left, right) => CompiledExpr::Binary {
190 op: *op,
191 left: Box::new(left),
192 right: Box::new(right),
193 },
194 }
195 }
196
197 Expression::Unary { op, operand } => {
198 let operand = Self::compile(operand, layout);
199 match operand {
200 CompiledExpr::Const(v) => match UnaryOperationEvaluator::evaluate(op, &v) {
201 Ok(folded) => CompiledExpr::Const(folded),
202 Err(_) => CompiledExpr::Unary {
203 op: *op,
204 operand: Box::new(CompiledExpr::Const(v)),
205 },
206 },
207 operand => CompiledExpr::Unary {
208 op: *op,
209 operand: Box::new(operand),
210 },
211 }
212 }
213
214 Expression::Function { name, args } => {
215 if args
220 .iter()
221 .any(|a| matches!(a.as_expr(), Expression::Lambda { .. }))
222 {
223 return CompiledExpr::Fallback(expression.clone());
224 }
225 if global_registry_ref()
231 .get_builtin(name.as_str())
232 .is_some_and(|f| {
233 matches!(
234 f,
235 crate::executor::expression::functions::BuiltinFunction::Sequence(_)
236 )
237 })
238 {
239 return CompiledExpr::Fallback(expression.clone());
240 }
241 let args: Vec<CompiledExpr> = args
242 .iter()
243 .map(|a| Self::compile(a.as_expr(), layout))
244 .collect();
245 if args.iter().all(|a| matches!(a, CompiledExpr::Const(_)))
249 && global_registry_ref()
250 .get_builtin(name.as_str())
251 .is_some_and(|f| f.is_pure())
252 {
253 let values: Vec<Value> = args
254 .iter()
255 .map(|a| match a {
256 CompiledExpr::Const(v) => v.clone(),
257 _ => unreachable!("guarded by all-Const check"),
258 })
259 .collect();
260 if let Ok(v) = global_registry().execute(name, &values) {
261 return CompiledExpr::Const(v);
262 }
263 }
264 let func = global_registry_ref()
265 .get_builtin(name.as_str())
266 .map(|f| OwnedFunctionRef::Builtin(f.clone()))
267 .or_else(|| {
268 global_registry_ref()
269 .get_custom(name.as_str())
270 .map(|f| OwnedFunctionRef::Custom(f.clone()))
271 });
272 CompiledExpr::Function {
273 name: name.clone(),
274 func,
275 args,
276 }
277 }
278
279 Expression::Aggregate {
280 func,
281 args,
282 distinct,
283 filter,
284 } => {
285 let args: Vec<CompiledExpr> =
286 args.iter().map(|a| Self::compile(a, layout)).collect();
287 let filter = filter.as_ref().map(|f| Box::new(Self::compile(f, layout)));
288 if filter.is_none() && args.iter().all(|a| matches!(a, CompiledExpr::Const(_))) {
292 let values: Vec<Value> = args
293 .iter()
294 .map(|a| match a {
295 CompiledExpr::Const(v) => v.clone(),
296 _ => unreachable!("guarded by all-Const check"),
297 })
298 .collect();
299 if let Ok(v) =
300 FunctionEvaluator::eval_aggregate_function(func, &values, *distinct)
301 {
302 return CompiledExpr::Const(v);
303 }
304 }
305 CompiledExpr::Fallback(expression.clone())
306 }
307
308 Expression::List(items) => {
309 CompiledExpr::List(items.iter().map(|e| Self::compile(e, layout)).collect())
310 }
311
312 Expression::Map(pairs) => CompiledExpr::Map(
313 pairs
314 .iter()
315 .map(|(k, v)| (k.clone(), Self::compile(v, layout)))
316 .collect(),
317 ),
318
319 Expression::Case {
320 test_expr,
321 conditions,
322 default,
323 } => CompiledExpr::Case {
324 test: test_expr
325 .as_ref()
326 .map(|e| Box::new(Self::compile(e, layout))),
327 conditions: conditions
328 .iter()
329 .map(|(c, v)| (Self::compile(c, layout), Self::compile(v, layout)))
330 .collect(),
331 default: default.as_ref().map(|e| Box::new(Self::compile(e, layout))),
332 },
333
334 Expression::TypeCast {
335 expression,
336 target_type,
337 } => {
338 let expr = Self::compile(expression, layout);
339 match expr {
340 CompiledExpr::Const(v) => {
341 match ExpressionEvaluator::eval_type_cast(&v, target_type) {
342 Ok(folded) => CompiledExpr::Const(folded),
343 Err(_) => CompiledExpr::TypeCast {
344 expr: Box::new(CompiledExpr::Const(v)),
345 target_type: target_type.clone(),
346 },
347 }
348 }
349 expr => CompiledExpr::TypeCast {
350 expr: Box::new(expr),
351 target_type: target_type.clone(),
352 },
353 }
354 }
355
356 Expression::Subscript { collection, index } => CompiledExpr::Subscript {
357 collection: Box::new(Self::compile(collection, layout)),
358 index: Box::new(Self::compile(index, layout)),
359 },
360
361 Expression::Range {
362 collection,
363 start,
364 end,
365 } => CompiledExpr::Range {
366 collection: Box::new(Self::compile(collection, layout)),
367 start: start.as_ref().map(|e| Box::new(Self::compile(e, layout))),
368 end: end.as_ref().map(|e| Box::new(Self::compile(e, layout))),
369 },
370
371 Expression::Path(items) => {
372 CompiledExpr::Path(items.iter().map(|e| Self::compile(e, layout)).collect())
373 }
374
375 Expression::Parameter(name) => CompiledExpr::Parameter(name.clone()),
376 Expression::SessionVariable(name) => CompiledExpr::SessionVariable(name.clone()),
377
378 _ => CompiledExpr::Fallback(expression.clone()),
380 }
381 }
382
383 pub fn evaluate(
388 &self,
389 row: &[Value],
390 layout: Arc<SlotLayout>,
391 env: Option<&EvalEnv>,
392 ) -> Result<Value, ExpressionError> {
393 match self {
394 CompiledExpr::Const(v) => Ok(v.clone()),
395 CompiledExpr::Slot(slot) => row
396 .get(*slot)
397 .cloned()
398 .ok_or_else(|| ExpressionError::undefined_variable("column slot")),
399 CompiledExpr::CompoundSlot(slot) => row
400 .get(*slot)
401 .cloned()
402 .ok_or_else(|| ExpressionError::undefined_variable("column slot")),
403
404 CompiledExpr::Unary { op, operand } => {
405 let value = operand.evaluate(row, layout, env)?;
406 UnaryOperationEvaluator::evaluate(op, &value)
407 }
408
409 CompiledExpr::Binary { op, left, right } => {
410 let left_value = left.evaluate(row, layout.clone(), env)?;
411 let right_value = right.evaluate(row, layout, env)?;
412 BinaryOperationEvaluator::evaluate(&left_value, op, &right_value)
413 }
414
415 CompiledExpr::Case {
416 test,
417 conditions,
418 default,
419 } => {
420 if let Some(test_expr) = test {
421 let test_value = test_expr.evaluate(row, layout.clone(), env)?;
422 for (condition, value) in conditions {
423 let condition_result = condition.evaluate(row, layout.clone(), env)?;
424 if test_value == condition_result {
425 return value.evaluate(row, layout, env);
426 }
427 }
428 } else {
429 for (condition, value) in conditions {
430 let condition_result = condition.evaluate(row, layout.clone(), env)?;
431 match condition_result {
432 Value::Bool(true) => return value.evaluate(row, layout, env),
433 Value::Bool(false) => continue,
434 _ => {
435 return Err(ExpressionError::type_error(
436 "CASE conditions must be Boolean",
437 ))
438 }
439 }
440 }
441 }
442 match default {
443 Some(default_expression) => default_expression.evaluate(row, layout, env),
444 None => Ok(Value::Null(NullType::Null)),
445 }
446 }
447
448 CompiledExpr::Function { name, func, args } => {
449 let mut arg_values = Vec::with_capacity(args.len());
450 for arg in args {
451 arg_values.push(arg.evaluate(row, layout.clone(), env)?);
452 }
453 match func {
454 Some(f) => f.execute(&arg_values),
455 None => global_registry().execute(name, &arg_values),
456 }
457 }
458
459 CompiledExpr::TypeCast { expr, target_type } => {
460 let value = expr.evaluate(row, layout, env)?;
461 ExpressionEvaluator::eval_type_cast(&value, target_type)
462 }
463
464 CompiledExpr::List(items) => {
465 let mut values = Vec::with_capacity(items.len());
466 for item in items {
467 values.push(item.evaluate(row, layout.clone(), env)?);
468 }
469 Ok(Value::list(List::from(values)))
470 }
471
472 CompiledExpr::Map(entries) => {
473 let mut map_values = std::collections::HashMap::new();
474 for (key, value_expression) in entries {
475 let value = value_expression.evaluate(row, layout.clone(), env)?;
476 map_values.insert(Value::string(key.clone()), value);
477 }
478 Ok(Value::map(map_values))
479 }
480
481 CompiledExpr::Subscript { collection, index } => {
482 let collection_value = collection.evaluate(row, layout.clone(), env)?;
483 let index_value = index.evaluate(row, layout, env)?;
484 CollectionOperationEvaluator::eval_subscript_access(&collection_value, &index_value)
485 }
486
487 CompiledExpr::Range {
488 collection,
489 start,
490 end,
491 } => {
492 let collection_value = collection.evaluate(row, layout.clone(), env)?;
493 let start_value = start
494 .as_ref()
495 .map(|e| e.evaluate(row, layout.clone(), env))
496 .transpose()?;
497 let end_value = end
498 .as_ref()
499 .map(|e| e.evaluate(row, layout.clone(), env))
500 .transpose()?;
501 CollectionOperationEvaluator::eval_range_access(
502 &collection_value,
503 start_value.as_ref(),
504 end_value.as_ref(),
505 )
506 }
507
508 CompiledExpr::Path(items) => {
509 let mut values = Vec::with_capacity(items.len());
510 for item in items {
511 values.push(item.evaluate(row, layout.clone(), env)?);
512 }
513 Ok(Value::list(List::from(values)))
514 }
515
516 CompiledExpr::Parameter(name) => env
517 .and_then(|e| e.params.as_ref())
518 .and_then(|p| p.get(name).cloned())
519 .ok_or_else(|| ExpressionError::undefined_parameter(name)),
520
521 CompiledExpr::SessionVariable(name) => env
522 .and_then(|e| e.session_variables.as_ref())
523 .and_then(|v| v.get(name).cloned())
524 .ok_or_else(|| {
525 ExpressionError::type_error(format!(
526 "Session variable `{}` is not defined",
527 name
528 ))
529 }),
530
531 CompiledExpr::Fallback(expression) => {
532 let mut ctx = match env {
533 Some(env) => BorrowedRowContext::with_env(row, layout, env),
534 None => BorrowedRowContext::new(row, layout),
535 };
536 ExpressionEvaluator::evaluate(expression, &mut ctx)
537 }
538 }
539 }
540
541 pub fn evaluate_batch(
555 &self,
556 rows: &[Vec<Value>],
557 layout: Arc<SlotLayout>,
558 env: Option<&EvalEnv>,
559 typed_columns: Option<&[TypedColumn]>,
560 ) -> Result<ColumnarValue, ExpressionError> {
561 let len = rows.len();
562 match self {
563 CompiledExpr::Const(v) => Ok(ColumnarValue::Const(v.clone())),
564 CompiledExpr::Slot(slot) | CompiledExpr::CompoundSlot(slot) => {
565 if let Some(cols) = typed_columns {
569 if let Some(col) = cols.get(*slot) {
570 return Ok(ColumnarValue::Column(col.to_values()));
571 }
572 }
573 let mut col = Vec::with_capacity(len);
574 for row in rows {
575 col.push(
576 row.get(*slot)
577 .cloned()
578 .ok_or_else(|| ExpressionError::undefined_variable("column slot"))?,
579 );
580 }
581 Ok(ColumnarValue::Column(col))
582 }
583
584 CompiledExpr::Unary { op, operand } => {
585 let operand = operand.evaluate_batch(rows, layout, env, typed_columns)?;
586 match operand {
587 ColumnarValue::Const(v) => Ok(ColumnarValue::Const(
588 UnaryOperationEvaluator::evaluate(op, &v)?,
589 )),
590 ColumnarValue::Column(col) => {
591 let mut out = Vec::with_capacity(col.len());
592 for v in col {
593 out.push(UnaryOperationEvaluator::evaluate(op, &v)?);
594 }
595 Ok(ColumnarValue::Column(out))
596 }
597 }
598 }
599
600 CompiledExpr::Binary { op, left, right } => {
601 let left = left.evaluate_batch(rows, layout.clone(), env, typed_columns)?;
602 let right = right.evaluate_batch(rows, layout, env, typed_columns)?;
603 match (left, right) {
604 (ColumnarValue::Const(l), ColumnarValue::Const(r)) => Ok(ColumnarValue::Const(
605 BinaryOperationEvaluator::evaluate(&l, op, &r)?,
606 )),
607 (ColumnarValue::Const(l), ColumnarValue::Column(r)) => {
608 let mut out = Vec::with_capacity(r.len());
609 for v in r {
610 out.push(BinaryOperationEvaluator::evaluate(&l, op, &v)?);
611 }
612 Ok(ColumnarValue::Column(out))
613 }
614 (ColumnarValue::Column(l), ColumnarValue::Const(r)) => {
615 let mut out = Vec::with_capacity(l.len());
616 for v in l {
617 out.push(BinaryOperationEvaluator::evaluate(&v, op, &r)?);
618 }
619 Ok(ColumnarValue::Column(out))
620 }
621 (ColumnarValue::Column(l), ColumnarValue::Column(r)) => {
622 let mut out = Vec::with_capacity(l.len());
623 for (lv, rv) in l.into_iter().zip(r) {
624 out.push(BinaryOperationEvaluator::evaluate(&lv, op, &rv)?);
625 }
626 Ok(ColumnarValue::Column(out))
627 }
628 }
629 }
630
631 CompiledExpr::Parameter(name) => match env
637 .and_then(|e| e.params.as_ref())
638 .and_then(|p| p.get(name).cloned())
639 {
640 Some(value) => Ok(ColumnarValue::Const(value)),
641 None => {
642 let first = rows.first().cloned().unwrap_or_default();
643 self.evaluate(&first, layout, env)?;
644 Ok(ColumnarValue::Column(Vec::new()))
645 }
646 },
647 CompiledExpr::SessionVariable(name) => match env
648 .and_then(|e| e.session_variables.as_ref())
649 .and_then(|v| v.get(name).cloned())
650 {
651 Some(value) => Ok(ColumnarValue::Const(value)),
652 None => {
653 let first = rows.first().cloned().unwrap_or_default();
654 self.evaluate(&first, layout, env)?;
655 Ok(ColumnarValue::Column(Vec::new()))
656 }
657 },
658 _ => {
659 let mut out = Vec::with_capacity(len);
660 for row in rows {
661 out.push(self.evaluate(row, layout.clone(), env)?);
662 }
663 Ok(ColumnarValue::Column(out))
664 }
665 }
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672 use crate::executor::streaming::chunk::DataChunk;
673 use crate::executor::streaming::slot::SlotLayout;
674 use linkrs_core::types::operators::{BinaryOperator, UnaryOperator};
675
676 fn layout(names: &[&str]) -> Arc<SlotLayout> {
677 Arc::new(SlotLayout::from_names(
678 &names.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
679 ))
680 }
681
682 fn eval_once(expr: &Expression, row: Vec<Value>, names: &[&str]) -> Value {
683 let layout = layout(names);
684 let compiled = CompiledExpr::compile(expr, &layout);
685 compiled
686 .evaluate(&row, layout, None)
687 .expect("evaluation should succeed")
688 }
689
690 #[test]
691 fn literal_compiles_to_const() {
692 let expr = Expression::Literal(Value::Int(42));
693 let layout = layout(&["a"]);
694 let compiled = CompiledExpr::compile(&expr, &layout);
695 assert!(matches!(compiled, CompiledExpr::Const(_)));
696 assert_eq!(
697 eval_once(&expr, vec![Value::Int(1)], &["a"]),
698 Value::Int(42)
699 );
700 }
701
702 #[test]
703 fn variable_resolves_to_slot() {
704 let expr = Expression::Variable("a".to_string());
705 let layout = layout(&["a", "b"]);
706 let compiled = CompiledExpr::compile(&expr, &layout);
707 assert!(matches!(compiled, CompiledExpr::Slot(0)));
708 assert_eq!(
709 eval_once(&expr, vec![Value::Int(7), Value::Int(9)], &["a", "b"]),
710 Value::Int(7)
711 );
712 }
713
714 #[test]
715 fn property_resolves_to_compound_slot() {
716 let expr = Expression::Property {
717 object: Box::new(Expression::Variable("p".to_string())),
718 property: "age".to_string(),
719 };
720 let layout = layout(&["id", "p.age"]);
721 let compiled = CompiledExpr::compile(&expr, &layout);
722 assert!(matches!(compiled, CompiledExpr::CompoundSlot(1)));
723 assert_eq!(
724 eval_once(&expr, vec![Value::Int(1), Value::Int(30)], &["id", "p.age"]),
725 Value::Int(30)
726 );
727 }
728
729 #[test]
730 fn binary_folds_constants_at_compile_time() {
731 let expr = Expression::Binary {
732 left: Box::new(Expression::Literal(Value::Int(2))),
733 op: BinaryOperator::Add,
734 right: Box::new(Expression::Literal(Value::Int(3))),
735 };
736 let layout = layout(&["a"]);
737 let compiled = CompiledExpr::compile(&expr, &layout);
738 assert!(matches!(compiled, CompiledExpr::Const(Value::Int(5))));
739 assert_eq!(eval_once(&expr, vec![Value::Int(0)], &["a"]), Value::Int(5));
740 }
741
742 #[test]
743 fn binary_with_variable_stays_unfolded() {
744 let expr = Expression::Binary {
745 left: Box::new(Expression::Variable("a".to_string())),
746 op: BinaryOperator::Add,
747 right: Box::new(Expression::Literal(Value::Int(1))),
748 };
749 let layout = layout(&["a"]);
750 let compiled = CompiledExpr::compile(&expr, &layout);
751 assert!(matches!(compiled, CompiledExpr::Binary { .. }));
752 assert_eq!(
753 eval_once(&expr, vec![Value::Int(10)], &["a"]),
754 Value::Int(11)
755 );
756 }
757
758 #[test]
759 fn null_semantics_match_scalar_evaluator() {
760 let expr = Expression::Binary {
764 left: Box::new(Expression::Variable("a".to_string())),
765 op: BinaryOperator::Equal,
766 right: Box::new(Expression::Literal(Value::Int(1))),
767 };
768 let layout = layout(&["a"]);
769 let row = vec![Value::Null(NullType::Null)];
770 let compiled = CompiledExpr::compile(&expr, &layout);
771 let compiled_result = compiled
772 .evaluate(&row, layout.clone(), None)
773 .expect("evaluation should succeed");
774
775 let mut ctx = BorrowedRowContext::new(&row, layout);
776 let scalar_result =
777 ExpressionEvaluator::evaluate(&expr, &mut ctx).expect("evaluation should succeed");
778 assert_eq!(compiled_result, scalar_result);
779 }
780
781 #[test]
782 fn unary_folds_constant() {
783 let expr = Expression::Unary {
784 op: UnaryOperator::Minus,
785 operand: Box::new(Expression::Literal(Value::Int(4))),
786 };
787 let layout = layout(&["a"]);
788 let compiled = CompiledExpr::compile(&expr, &layout);
789 assert!(matches!(compiled, CompiledExpr::Const(Value::Int(-4))));
790 }
791
792 #[test]
793 fn impure_function_is_not_folded() {
794 let expr = Expression::Function {
795 name: "rand".to_string(),
796 args: vec![],
797 };
798 let layout = layout(&["a"]);
799 let compiled = CompiledExpr::compile(&expr, &layout);
800 assert!(matches!(compiled, CompiledExpr::Function { .. }));
802 }
803
804 #[test]
805 fn unknown_variable_falls_back_to_interpreter() {
806 let expr = Expression::Variable("missing".to_string());
809 let layout = layout(&["a"]);
810 let compiled = CompiledExpr::compile(&expr, &layout);
811 assert!(matches!(compiled, CompiledExpr::Fallback(_)));
812 let err = compiled
813 .evaluate(&[Value::Int(1)], layout, None)
814 .unwrap_err();
815 assert!(err.to_string().contains("missing"));
816 }
817
818 #[test]
819 fn batch_evaluation_matches_per_row() {
820 let expr = Expression::Binary {
821 left: Box::new(Expression::Variable("a".to_string())),
822 op: BinaryOperator::Add,
823 right: Box::new(Expression::Literal(Value::Int(10))),
824 };
825 let layout = layout(&["a"]);
826 let rows = vec![
827 vec![Value::Int(1)],
828 vec![Value::Int(2)],
829 vec![Value::Int(3)],
830 ];
831 let compiled = CompiledExpr::compile(&expr, &layout);
832 let batch = compiled
833 .evaluate_batch(&rows, layout.clone(), None, None)
834 .expect("batch evaluation should succeed")
835 .into_values(rows.len());
836 assert_eq!(batch, vec![Value::Int(11), Value::Int(12), Value::Int(13)]);
837 }
838
839 #[test]
840 fn batch_const_never_materializes() {
841 let expr = Expression::Literal(Value::Bool(true));
842 let layout = layout(&["a"]);
843 let rows = vec![vec![Value::Int(1)], vec![Value::Int(2)]];
844 let compiled = CompiledExpr::compile(&expr, &layout);
845 let batch = compiled
846 .evaluate_batch(&rows, layout, None, None)
847 .expect("batch evaluation should succeed");
848 assert!(matches!(batch, ColumnarValue::Const(_)));
849 }
850
851 #[test]
852 fn batch_parameter_broadcasts_const() {
853 use crate::executor::streaming::subquery::EvalEnv;
854 use std::collections::HashMap;
855 use std::sync::Arc;
856
857 let expr = Expression::Parameter("x".to_string());
858 let layout = layout(&["a"]);
859 let rows = vec![vec![Value::Int(1)], vec![Value::Int(2)]];
860 let compiled = CompiledExpr::compile(&expr, &layout);
861 let mut params = HashMap::new();
862 params.insert("x".into(), Value::Int(9));
863 let env = EvalEnv {
864 params: Some(Arc::new(params)),
865 session_variables: None,
866 subquery_executor: None,
867 sequence_manager: None,
868 };
869 let batch = compiled
870 .evaluate_batch(&rows, layout, Some(&env), None)
871 .expect("batch evaluation should succeed");
872 assert!(matches!(batch, ColumnarValue::Const(Value::Int(9))));
873 }
874
875 #[test]
876 fn batch_missing_parameter_preserves_error() {
877 let expr = Expression::Parameter("missing".to_string());
878 let layout = layout(&["a"]);
879 let rows = vec![vec![Value::Int(1)]];
880 let compiled = CompiledExpr::compile(&expr, &layout);
881 let err = compiled
882 .evaluate_batch(&rows, layout, None, None)
883 .expect_err("missing parameter must fail");
884 assert!(err.to_string().contains("missing"));
885 }
886
887 #[test]
888 fn fallback_switch_toggles_compiled_path() {
889 assert!(compiled_eval_enabled());
890 set_compiled_eval_enabled(false);
891 assert!(!compiled_eval_enabled());
892 set_compiled_eval_enabled(true);
893 assert!(compiled_eval_enabled());
894 }
895
896 #[test]
897 fn subquery_stays_fallback() {
898 let expr = Expression::Exists {
899 body: Box::new(linkrs_core::types::expr::SubqueryBody {
900 id: 0,
901 patterns: vec![],
902 where_clause: None,
903 return_expr: None,
904 }),
905 };
906 let layout = layout(&["a"]);
907 let compiled = CompiledExpr::compile(&expr, &layout);
908 assert!(matches!(compiled, CompiledExpr::Fallback(_)));
909 }
910
911 #[test]
912 fn case_short_circuits() {
913 let expr = Expression::Case {
914 test_expr: None,
915 conditions: vec![
916 (
917 Expression::Binary {
918 left: Box::new(Expression::Variable("a".to_string())),
919 op: BinaryOperator::GreaterThan,
920 right: Box::new(Expression::Literal(Value::Int(5))),
921 },
922 Expression::Literal(Value::string("big")),
923 ),
924 (
925 Expression::Literal(Value::Bool(true)),
926 Expression::Literal(Value::string("small")),
927 ),
928 ],
929 default: Some(Box::new(Expression::Literal(Value::Null(NullType::Null)))),
930 };
931 assert_eq!(
932 eval_once(&expr, vec![Value::Int(9)], &["a"]),
933 Value::string("big")
934 );
935 assert_eq!(
936 eval_once(&expr, vec![Value::Int(1)], &["a"]),
937 Value::string("small")
938 );
939 }
940
941 #[test]
942 fn batch_mixed_expression_reads_typed_slots() {
943 let expr = Expression::Binary {
947 left: Box::new(Expression::Variable("x".to_string())),
948 op: BinaryOperator::Add,
949 right: Box::new(Expression::Variable("y".to_string())),
950 };
951 let layout = layout(&["x", "y"]);
952 let rows = vec![
953 vec![Value::Int(1), Value::Int(10)],
954 vec![Value::Int(2), Value::Int(20)],
955 ];
956 let compiled = CompiledExpr::compile(&expr, &layout);
957 let baseline = compiled
958 .evaluate_batch(&rows, layout.clone(), None, None)
959 .expect("batch evaluation should succeed")
960 .into_values(rows.len());
961 let mut chunk = DataChunk::new_with_layout(rows.clone(), layout.clone());
962 chunk.build_typed_columns(true);
963 assert!(
964 chunk.typed_columns.is_some(),
965 "typed layout must be built for the test"
966 );
967 let typed = compiled
968 .evaluate_batch(&rows, layout, None, chunk.typed_columns.as_deref())
969 .expect("batch evaluation should succeed")
970 .into_values(rows.len());
971 assert_eq!(typed, baseline);
972 assert_eq!(typed, vec![Value::Int(11), Value::Int(22)]);
973 }
974}