1use crate::executor::expression::evaluator::collection_operations::CollectionOperationEvaluator;
6use crate::executor::expression::evaluator::functions::FunctionEvaluator;
7use crate::executor::expression::evaluator::operations::{
8 BinaryOperationEvaluator, UnaryOperationEvaluator,
9};
10use crate::executor::expression::evaluator::traits::ExpressionContext;
11use crate::executor::expression::functions::{global_registry, global_registry_ref};
12use crate::executor::expression::ExpressionError;
13use linkrs_core::types::expr::analysis_utils::is_evaluable;
14use linkrs_core::types::expr::Expression;
15use linkrs_core::value::list::List;
16use linkrs_core::value::NullType;
17use linkrs_core::Value;
18
19#[derive(Debug)]
21pub struct ExpressionEvaluator;
22
23impl ExpressionEvaluator {
24 pub fn evaluate<C: ExpressionContext>(
26 expression: &Expression,
27 context: &mut C,
28 ) -> Result<Value, ExpressionError> {
29 Self::evaluate_recursive(expression, context)
30 }
31
32 pub fn can_evaluate(expression: &Expression) -> bool {
37 is_evaluable(expression)
38 }
39
40 fn evaluate_recursive<C: ExpressionContext>(
42 expression: &Expression,
43 context: &mut C,
44 ) -> Result<Value, ExpressionError> {
45 match expression {
46 Expression::Literal(value) => Ok(value.clone()),
48
49 Expression::Variable(name) => context
51 .get_variable(name)
52 .ok_or_else(|| ExpressionError::undefined_variable(name)),
53
54 Expression::Binary { left, op, right } => {
56 let left_value = Self::evaluate_recursive(left, context)?;
57 let right_value = Self::evaluate_recursive(right, context)?;
58 BinaryOperationEvaluator::evaluate(&left_value, op, &right_value)
59 }
60
61 Expression::Unary { op, operand } => {
63 let value = Self::evaluate_recursive(operand, context)?;
64 UnaryOperationEvaluator::evaluate(op, &value)
65 }
66
67 Expression::Function { name, args } => {
69 if let Some(value) = Self::evaluate_higher_order(name, args, context)? {
70 return Ok(value);
71 }
72 let arg_values: Result<Vec<Value>, ExpressionError> = args
73 .iter()
74 .map(|arg| Self::evaluate_recursive(arg.as_expr(), context))
75 .collect();
76 let arg_values = arg_values?;
77
78 let func_ref = context.get_function(name);
80
81 if let Some(seq) = func_ref.as_ref().and_then(|owned| match owned {
86 crate::executor::expression::functions::OwnedFunctionRef::Builtin(
87 crate::executor::expression::functions::BuiltinFunction::Sequence(seq),
88 ) => Some(*seq),
89 _ => None,
90 }) {
91 return seq.execute_with_manager(
92 &arg_values,
93 context.get_sequence_manager().as_ref(),
94 );
95 }
96
97 if let Some(func_ref) = func_ref {
98 let owned_func: crate::executor::expression::functions::OwnedFunctionRef =
100 func_ref.clone();
101
102 drop(func_ref);
104
105 if let Some(storage) = context.get_graph_storage() {
109 owned_func.execute_with_storage(&arg_values, &storage)
110 } else if context.supports_cache() {
111 match context.get_cache() {
113 Some(cache) => owned_func.execute_with_cache(&arg_values, cache),
114 None => owned_func.execute(&arg_values),
115 }
116 } else {
117 owned_func.execute(&arg_values)
118 }
119 } else {
120 if let Some(seq) = global_registry_ref()
126 .get_builtin(name)
127 .and_then(|f| match f {
128 crate::executor::expression::functions::BuiltinFunction::Sequence(
129 seq,
130 ) => Some(*seq),
131 _ => None,
132 })
133 {
134 return seq.execute_with_manager(
135 &arg_values,
136 context.get_sequence_manager().as_ref(),
137 );
138 }
139 if let Some(storage) = context.get_graph_storage() {
141 global_registry().execute_with_storage(name, &arg_values, &storage)
142 } else {
143 global_registry().execute(name, &arg_values)
144 }
145 }
146 }
147
148 Expression::Aggregate {
150 func,
151 args,
152 distinct,
153 filter,
154 } => {
155 let arg_values: Vec<Value> = args
156 .iter()
157 .map(|a| Self::evaluate_recursive(a, context))
158 .collect::<Result<Vec<_>, _>>()?;
159 if let Some(filter_expr) = &filter {
160 let filter_result = Self::evaluate_recursive(filter_expr, context)?;
161 let is_true = matches!(filter_result, Value::Bool(true));
162 if !is_true {
163 return Ok(Value::Null(linkrs_core::NullType::Null));
164 }
165 }
166 FunctionEvaluator::eval_aggregate_function(func, &arg_values, *distinct)
167 }
168
169 Expression::Case {
171 test_expr,
172 conditions,
173 default,
174 } => {
175 if let Some(expr) = test_expr {
176 let test_value = Self::evaluate_recursive(expr, context)?;
177 for (condition, value) in conditions {
178 let condition_result = Self::evaluate_recursive(condition, context)?;
179 if test_value == condition_result {
180 return Self::evaluate_recursive(value, context);
181 }
182 }
183 } else {
184 for (condition, value) in conditions {
185 let condition_result = Self::evaluate_recursive(condition, context)?;
186 match condition_result {
187 Value::Bool(true) => return Self::evaluate_recursive(value, context),
188 Value::Bool(false) => continue,
189 _ => {
190 return Err(ExpressionError::type_error(
191 "CASE conditions must be Boolean",
192 ))
193 }
194 }
195 }
196 }
197 match default {
198 Some(default_expression) => {
199 Self::evaluate_recursive(default_expression, context)
200 }
201 None => Ok(Value::Null(NullType::Null)),
202 }
203 }
204
205 Expression::List(elements) => {
207 let element_values: Result<Vec<Value>, ExpressionError> = elements
208 .iter()
209 .map(|elem| Self::evaluate_recursive(elem, context))
210 .collect();
211 element_values.map(|vals| Value::list(List::from(vals)))
212 }
213
214 Expression::Vector(data) => Ok(Value::vector(data.clone())),
216 Expression::WindowFunction { .. } => Err(ExpressionError::type_error(
217 "Window functions require a runtime window context",
218 )),
219
220 Expression::Map(entries) => {
222 let mut map_values = std::collections::HashMap::new();
223 for (key, value_expression) in entries {
224 let value = Self::evaluate_recursive(value_expression, context)?;
225 map_values.insert(Value::string(key.clone()), value);
226 }
227 Ok(Value::map(map_values))
228 }
229
230 Expression::Subscript { collection, index } => {
232 let collection_value = Self::evaluate_recursive(collection, context)?;
233 let index_value = Self::evaluate_recursive(index, context)?;
234 CollectionOperationEvaluator::eval_subscript_access(&collection_value, &index_value)
235 }
236
237 Expression::Range {
239 collection,
240 start,
241 end,
242 } => {
243 let collection_value = Self::evaluate_recursive(collection, context)?;
244 let start_value = start
245 .as_ref()
246 .map(|e| Self::evaluate_recursive(e, context))
247 .transpose()?;
248 let end_value = end
249 .as_ref()
250 .map(|e| Self::evaluate_recursive(e, context))
251 .transpose()?;
252 CollectionOperationEvaluator::eval_range_access(
253 &collection_value,
254 start_value.as_ref(),
255 end_value.as_ref(),
256 )
257 }
258
259 Expression::Path(elements) => {
261 let element_values: Result<Vec<Value>, ExpressionError> = elements
262 .iter()
263 .map(|elem| Self::evaluate_recursive(elem, context))
264 .collect();
265 element_values.map(|vals| Value::list(List::from(vals)))
266 }
267
268 Expression::Property { object, property } => {
272 if let Expression::Variable(var_name) = object.as_ref() {
273 let compound = format!("{}.{}", var_name, property);
274 if let Some(val) = context.get_variable(&compound) {
275 return Ok(val);
276 }
277 }
278 let object_value = Self::evaluate_recursive(object, context)?;
279 CollectionOperationEvaluator::eval_property_access(&object_value, property)
280 }
281
282 Expression::StructField { base, field } => {
284 let base_value = Self::evaluate_recursive(base, context)?;
285 CollectionOperationEvaluator::eval_struct_field_access(&base_value, field)
286 }
287
288 Expression::TypeCast {
290 expression,
291 target_type,
292 } => {
293 let value = Self::evaluate_recursive(expression, context)?;
294 Self::eval_type_cast(&value, target_type)
295 }
296
297 Expression::EdgeProperty {
299 edge_name,
300 property,
301 } => {
302 let compound = format!("{}.{}", edge_name, property);
303 if let Some(val) = context.get_variable(&compound) {
304 return Ok(val);
305 }
306 let edge_value = context
307 .get_variable(edge_name)
308 .ok_or_else(|| ExpressionError::undefined_variable(edge_name))?;
309 CollectionOperationEvaluator::eval_property_access(&edge_value, property)
310 }
311
312 Expression::Label(name) => context.evaluate_label(name),
316 Expression::ListComprehension {
317 variable,
318 source,
319 filter,
320 map,
321 } => context.evaluate_list_comprehension(
322 variable,
323 source,
324 filter.as_deref(),
325 map.as_deref(),
326 ),
327 Expression::LabelTagProperty { tag, property } => {
328 context.evaluate_label_tag_property(tag, property)
329 }
330 Expression::TagProperty { tag_name, property } => {
331 let compound = format!("{}.{}", tag_name, property);
332 if let Some(val) = context.get_variable(&compound) {
333 return Ok(val);
334 }
335 let tag_value = context
336 .get_variable(tag_name)
337 .ok_or_else(|| ExpressionError::undefined_variable(tag_name))?;
338 CollectionOperationEvaluator::eval_property_access(&tag_value, property)
339 }
340 Expression::Predicate { func, args } => context.evaluate_predicate(func, args),
341 Expression::Reduce {
342 accumulator,
343 initial,
344 variable,
345 source,
346 mapping,
347 } => context.evaluate_reduce(accumulator, initial, variable, source, mapping),
348 Expression::PathBuild(items) => context.evaluate_path_build(items),
349 Expression::Parameter(name) => context
350 .get_parameter(name)
351 .ok_or_else(|| ExpressionError::undefined_parameter(name)),
352 Expression::SessionVariable(name) => context.get_session_variable(name),
353 Expression::Exists { body } => {
354 let exists = context.execute_exists(body)?;
355 Ok(Value::Bool(exists))
356 }
357 Expression::In {
358 expr,
359 subquery,
360 negated,
361 } => {
362 let value = Self::evaluate_recursive(expr, context)?;
363 let found = matches!(
364 context.contains_subquery(subquery, &value)?,
365 Value::Bool(true)
366 );
367 Ok(Value::Bool(if *negated { !found } else { found }))
368 }
369 Expression::CountSubquery { body } => {
370 let results = context.execute_subquery(body)?;
371 Ok(Value::Int(results.len() as i32))
372 }
373 Expression::ScalarSubquery { body } => {
374 context.execute_scalar_subquery(body)
376 }
377 Expression::Lambda { .. } => Err(ExpressionError::type_error(
378 "Lambda expression cannot be evaluated directly; \
379 use it as an argument to a higher-order function like list_transform or list_filter",
380 )),
381 }
382 }
383
384 fn evaluate_higher_order<C: ExpressionContext>(
385 name: &str,
386 args: &[linkrs_core::types::expr::FunctionArg],
387 context: &mut C,
388 ) -> Result<Option<Value>, ExpressionError> {
389 let lower = name.to_ascii_lowercase();
390 if !matches!(
391 lower.as_str(),
392 "list_filter"
393 | "list_transform"
394 | "list_any"
395 | "list_all"
396 | "list_single"
397 | "list_reduce"
398 ) {
399 return Ok(None);
400 }
401 let lambda_pos = args
406 .iter()
407 .position(|a| matches!(a.as_expr(), Expression::Lambda { .. }));
408 let Some(lambda_index) = lambda_pos else {
409 return Ok(None);
410 };
411 if lambda_index != 1 {
416 return Err(ExpressionError::type_error(format!(
417 "{name} expects the lambda as the second argument"
418 )));
419 }
420 let Some(source_expr) = args.first().map(|a| a.as_expr()) else {
421 return Err(ExpressionError::type_error(format!(
422 "{name} requires a list as first argument"
423 )));
424 };
425 let source = Self::evaluate_recursive(source_expr, context)?;
426 if matches!(source, Value::Null(_)) {
428 return Ok(Some(Value::Null(NullType::Null)));
429 }
430 let Value::List(list) = source else {
431 return Err(ExpressionError::type_error(format!(
432 "{name} requires a list as first argument"
433 )));
434 };
435 let Expression::Lambda { params, body } = args[lambda_index].as_expr() else {
436 return Ok(None);
437 };
438
439 if lower == "list_reduce" {
440 if args.len() != 2 && args.len() != 3 {
441 return Err(ExpressionError::type_error(
442 "list_reduce requires (source, lambda, initial)".to_string(),
443 ));
444 }
445 if params.len() != 2 {
446 return Err(ExpressionError::type_error(
447 "list_reduce lambda requires two parameters (acc, item)".to_string(),
448 ));
449 }
450 let initial = args
451 .get(2)
452 .map(|a| Self::evaluate_recursive(a.as_expr(), context))
453 .transpose()?
454 .unwrap_or(Value::Null(NullType::Null));
455 let mut acc = initial;
456 for item in list.values {
457 context.set_variable(params[0].clone(), acc.clone());
458 context.set_variable(params[1].clone(), item);
459 acc = Self::evaluate_recursive(body, context)?;
460 }
461 return Ok(Some(acc));
462 }
463
464 if args.len() != 2 {
465 return Err(ExpressionError::type_error(format!(
466 "{name} requires (source, lambda)"
467 )));
468 }
469 if params.is_empty() {
470 return Err(ExpressionError::type_error(format!(
471 "{name} lambda requires a parameter"
472 )));
473 }
474 let param = params[0].clone();
475 match lower.as_str() {
476 "list_filter" => {
477 let mut kept = Vec::new();
478 for item in list.values {
479 context.set_variable(param.clone(), item.clone());
480 match Self::evaluate_recursive(body, context)? {
481 Value::Bool(true) => kept.push(item),
482 Value::Bool(false) | Value::Null(_) => {}
483 other => {
484 return Err(ExpressionError::type_error(format!(
485 "list_filter lambda must return boolean, got {:?}",
486 other.get_type()
487 )))
488 }
489 }
490 }
491 Ok(Some(Value::list(List::from(kept))))
492 }
493 "list_transform" => {
494 let mut out = Vec::with_capacity(list.values.len());
495 for item in list.values {
496 context.set_variable(param.clone(), item);
497 out.push(Self::evaluate_recursive(body, context)?);
498 }
499 Ok(Some(Value::list(List::from(out))))
500 }
501 "list_any" | "list_all" | "list_single" => {
502 let mut matched = 0usize;
503 let total = list.values.len();
504 for item in list.values {
505 context.set_variable(param.clone(), item);
506 match Self::evaluate_recursive(body, context)? {
507 Value::Bool(true) => matched += 1,
508 Value::Bool(false) | Value::Null(_) => {}
509 other => {
510 return Err(ExpressionError::type_error(format!(
511 "{name} lambda must return boolean, got {:?}",
512 other.get_type()
513 )))
514 }
515 }
516 }
517 let result = match lower.as_str() {
518 "list_any" => matched > 0,
519 "list_all" => matched == total,
520 _ => matched == 1,
521 };
522 Ok(Some(Value::Bool(result)))
523 }
524 _ => Ok(None),
525 }
526 }
527
528 pub fn eval_type_cast(
530 value: &Value,
531 target_type: &linkrs_core::types::DataType,
532 ) -> Result<Value, ExpressionError> {
533 use linkrs_core::types::DataType;
534
535 let result = match target_type {
536 DataType::Bool => value.to_bool(),
537 DataType::SmallInt => match value.to_int32() {
538 Value::Int(i) => Value::SmallInt(i as i16),
539 v => v,
540 },
541 DataType::Int => value.to_int(),
542 DataType::BigInt => {
543 let int_val = value.to_int();
544 match int_val {
545 Value::Int(i) => Value::BigInt(i as i64),
546 Value::Null(_) => Value::Null(NullType::Null),
547 _ => Value::Null(NullType::BadData),
548 }
549 }
550 DataType::Float => value.to_float(),
551 DataType::Double => {
552 let float_val = value.to_float();
553 match float_val {
554 Value::Float(f) => Value::Double(f as f64),
555 Value::Null(_) => Value::Null(NullType::Null),
556 _ => Value::Null(NullType::BadData),
557 }
558 }
559 DataType::String => {
560 return value
561 .to_string()
562 .map(Value::string)
563 .map_err(ExpressionError::type_error);
564 }
565 DataType::List(_) => value.to_list(),
566 DataType::Map(_) => value.to_map(),
567 DataType::Json => match value {
568 Value::String(s) => {
569 let j = linkrs_core::value::json::Json::parse(s)
570 .map_err(|e| ExpressionError::type_error(format!("Invalid JSON: {}", e)))?;
571 Value::Json(Box::new(j))
572 }
573 Value::Json(_) => value.clone(),
574 Value::JsonB(jb) => {
575 let j = jb.to_json();
576 Value::Json(Box::new(j))
577 }
578 Value::Null(_) => Value::Null(NullType::Null),
579 _ => {
580 return Err(ExpressionError::type_error(format!(
581 "Cannot convert {:?} to JSON",
582 value.get_type()
583 )))
584 }
585 },
586 DataType::JsonB => match value {
587 Value::String(s) => {
588 let jb = linkrs_core::value::json::JsonB::parse(s)
589 .map_err(|e| ExpressionError::type_error(format!("Invalid JSON: {}", e)))?;
590 Value::JsonB(Box::new(jb))
591 }
592 Value::JsonB(_) => value.clone(),
593 Value::Json(j) => {
594 let jb = j
595 .to_jsonb()
596 .map_err(|e| ExpressionError::type_error(format!("Invalid JSON: {}", e)))?;
597 Value::JsonB(Box::new(jb))
598 }
599 Value::Null(_) => Value::Null(NullType::Null),
600 _ => {
601 return Err(ExpressionError::type_error(format!(
602 "Cannot convert {:?} to JSONB",
603 value.get_type()
604 )))
605 }
606 },
607 _ => {
608 return Err(ExpressionError::type_error(format!(
609 "Unsupported type conversion: {:?}",
610 target_type
611 )))
612 }
613 };
614
615 if let Value::Null(NullType::BadData) = result {
617 Err(ExpressionError::type_error(format!(
618 "Unable to convert {:?} to {:?}.",
619 value, target_type
620 )))
621 } else {
622 Ok(result)
623 }
624 }
625}
626
627#[cfg(test)]
628mod higher_order_tests {
629 use super::*;
630 use crate::executor::expression::evaluation_context::DefaultExpressionContext;
631 use linkrs_core::types::expr::FunctionArg;
632 use linkrs_core::types::operators::BinaryOperator;
633
634 fn int_list(values: Vec<i32>) -> Expression {
635 Expression::List(values.into_iter().map(Expression::int).collect())
636 }
637
638 fn lambda_two_body() -> Expression {
639 Expression::lambda(
640 vec!["acc".to_string(), "x".to_string()],
641 Expression::binary(
642 Expression::variable("acc"),
643 BinaryOperator::Add,
644 Expression::variable("x"),
645 ),
646 )
647 }
648
649 fn eval_function(name: &str, args: Vec<FunctionArg>) -> Result<Value, ExpressionError> {
650 let expr = Expression::Function {
651 name: name.to_string(),
652 args,
653 };
654 let mut ctx = DefaultExpressionContext::new();
655 ExpressionEvaluator::evaluate(&expr, &mut ctx)
656 }
657
658 #[test]
659 fn list_reduce_sums_ints() {
660 let result = eval_function(
661 "list_reduce",
662 vec![
663 FunctionArg::positional(int_list(vec![1, 2, 3])),
664 FunctionArg::positional(lambda_two_body()),
665 FunctionArg::positional(Expression::int(0)),
666 ],
667 )
668 .expect("reduce ints");
669 assert_eq!(result, Value::Int(6));
670 }
671
672 #[test]
673 fn list_reduce_concatenates_strings() {
674 let source = Expression::List(vec![Expression::string("a"), Expression::string("b")]);
675 let result = eval_function(
676 "list_reduce",
677 vec![
678 FunctionArg::positional(source),
679 FunctionArg::positional(lambda_two_body()),
680 FunctionArg::positional(Expression::string("")),
681 ],
682 )
683 .expect("reduce strings");
684 assert_eq!(result, Value::string("ab"));
685 }
686
687 #[test]
688 fn list_reduce_adds_floats() {
689 let source = Expression::List(vec![Expression::int(1), Expression::int(2)]);
690 let result = eval_function(
691 "list_reduce",
692 vec![
693 FunctionArg::positional(source),
694 FunctionArg::positional(lambda_two_body()),
695 FunctionArg::positional(Expression::float(0.0)),
696 ],
697 )
698 .expect("reduce floats");
699 assert_eq!(result, Value::Float(3.0));
700 }
701
702 #[test]
703 fn list_reduce_empty_returns_initial() {
704 let result = eval_function(
705 "list_reduce",
706 vec![
707 FunctionArg::positional(Expression::List(vec![])),
708 FunctionArg::positional(lambda_two_body()),
709 FunctionArg::positional(Expression::int(42)),
710 ],
711 )
712 .expect("empty reduce");
713 assert_eq!(result, Value::Int(42));
714 }
715
716 #[test]
717 fn list_reduce_null_source_returns_null() {
718 let result = eval_function(
719 "list_reduce",
720 vec![
721 FunctionArg::positional(Expression::null()),
722 FunctionArg::positional(lambda_two_body()),
723 FunctionArg::positional(Expression::int(0)),
724 ],
725 )
726 .expect("null reduce");
727 assert!(matches!(result, Value::Null(_)));
728 }
729
730 #[test]
731 fn list_reduce_requires_two_lambda_params() {
732 let bad = Expression::lambda(vec!["x".to_string()], Expression::variable("x"));
733 let err = eval_function(
734 "list_reduce",
735 vec![
736 FunctionArg::positional(int_list(vec![1])),
737 FunctionArg::positional(bad),
738 FunctionArg::positional(Expression::int(0)),
739 ],
740 )
741 .expect_err("single-param reduce must fail");
742 assert!(err.message.contains("two parameters"), "{}", err.message);
743 }
744
745 #[test]
746 fn list_filter_applies_lambda_per_element() {
747 let lambda = Expression::lambda(
748 vec!["x".to_string()],
749 Expression::binary(
750 Expression::variable("x"),
751 BinaryOperator::GreaterThan,
752 Expression::int(1),
753 ),
754 );
755 let result = eval_function(
756 "list_filter",
757 vec![
758 FunctionArg::positional(int_list(vec![1, 2, 3])),
759 FunctionArg::positional(lambda),
760 ],
761 )
762 .expect("filter");
763 assert_eq!(
764 result,
765 Value::list(List::from(vec![Value::Int(2), Value::Int(3)]))
766 );
767 }
768
769 #[test]
770 fn list_transform_applies_lambda_per_element() {
771 let lambda = Expression::lambda(
772 vec!["x".to_string()],
773 Expression::binary(
774 Expression::variable("x"),
775 BinaryOperator::Multiply,
776 Expression::int(2),
777 ),
778 );
779 let result = eval_function(
780 "list_transform",
781 vec![
782 FunctionArg::positional(int_list(vec![1, 2])),
783 FunctionArg::positional(lambda),
784 ],
785 )
786 .expect("transform");
787 assert_eq!(
788 result,
789 Value::list(List::from(vec![Value::Int(2), Value::Int(4)]))
790 );
791 }
792
793 #[test]
794 fn list_any_all_single_use_lambda_truth() {
795 let gt_one = || {
796 Expression::lambda(
797 vec!["x".to_string()],
798 Expression::binary(
799 Expression::variable("x"),
800 BinaryOperator::GreaterThan,
801 Expression::int(1),
802 ),
803 )
804 };
805 let source = || int_list(vec![1, 2, 3]);
806 let any = eval_function(
807 "list_any",
808 vec![
809 FunctionArg::positional(source()),
810 FunctionArg::positional(gt_one()),
811 ],
812 )
813 .expect("any");
814 assert_eq!(any, Value::Bool(true));
815 let all = eval_function(
816 "list_all",
817 vec![
818 FunctionArg::positional(source()),
819 FunctionArg::positional(gt_one()),
820 ],
821 )
822 .expect("all");
823 assert_eq!(all, Value::Bool(false));
824 let gt_two = Expression::lambda(
825 vec!["x".to_string()],
826 Expression::binary(
827 Expression::variable("x"),
828 BinaryOperator::GreaterThan,
829 Expression::int(2),
830 ),
831 );
832 let single = eval_function(
833 "list_single",
834 vec![
835 FunctionArg::positional(source()),
836 FunctionArg::positional(gt_two),
837 ],
838 )
839 .expect("single");
840 assert_eq!(single, Value::Bool(true));
841 }
842
843 #[test]
844 fn list_reduce_supports_smallint_with_wrapping_overflow() {
845 let source = Expression::List(vec![
846 Expression::Literal(Value::SmallInt(1)),
847 Expression::Literal(Value::SmallInt(2)),
848 ]);
849 let result = eval_function(
850 "list_reduce",
851 vec![
852 FunctionArg::positional(source),
853 FunctionArg::positional(lambda_two_body()),
854 FunctionArg::positional(Expression::Literal(Value::SmallInt(0))),
855 ],
856 )
857 .expect("reduce smallint");
858 assert_eq!(result, Value::SmallInt(3));
859 let overflowing = Expression::List(vec![Expression::Literal(Value::Int(i32::MAX))]);
861 let wrapped = eval_function(
862 "list_reduce",
863 vec![
864 FunctionArg::positional(overflowing),
865 FunctionArg::positional(lambda_two_body()),
866 FunctionArg::positional(Expression::int(1)),
867 ],
868 )
869 .expect("wrapping overflow");
870 assert_eq!(wrapped, Value::Int(i32::MIN));
871 }
872
873 #[test]
874 fn list_reduce_supports_decimal() {
875 use linkrs_core::value::Decimal128Value;
876 let dec = |n: i64| Expression::Literal(Value::Decimal128(Decimal128Value::from_i64(n)));
877 let source = Expression::List(vec![dec(1), dec(2)]);
878 let result = eval_function(
879 "list_reduce",
880 vec![
881 FunctionArg::positional(source),
882 FunctionArg::positional(lambda_two_body()),
883 FunctionArg::positional(dec(0)),
884 ],
885 )
886 .expect("reduce decimal");
887 assert_eq!(result, Value::Decimal128(Decimal128Value::from_i64(3)));
888 }
889
890 #[test]
891 fn list_reduce_null_element_surfaces_type_error() {
892 let source = Expression::List(vec![Expression::int(1), Expression::null()]);
895 let err = eval_function(
896 "list_reduce",
897 vec![
898 FunctionArg::positional(source),
899 FunctionArg::positional(lambda_two_body()),
900 FunctionArg::positional(Expression::int(0)),
901 ],
902 )
903 .expect_err("null element must error");
904 assert!(
905 err.message.contains("Cannot perform addition"),
906 "{}",
907 err.message
908 );
909 }
910}