Skip to main content

linkrs_query/executor/expression/evaluator/
expression_evaluator.rs

1//! Implementation of an expression evaluator
2//!
3//! Provide a function for evaluating specific expressions, implemented using direct recursive matching to avoid unnecessary abstract overhead.
4
5use 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/// Implementation of an expression evaluator (unit struct, zero overhead)
20#[derive(Debug)]
21pub struct ExpressionEvaluator;
22
23impl ExpressionEvaluator {
24    /// Evaluate the expression in the given context.
25    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    /// Check whether the expression can be evaluated.
33    ///
34    /// Check whether the expression can be evaluated without any runtime context.
35    /// In other words, the expression contains only constants and does not include any variables or accesses to attributes.
36    pub fn can_evaluate(expression: &Expression) -> bool {
37        is_evaluable(expression)
38    }
39
40    /// Recursive evaluation expressions
41    fn evaluate_recursive<C: ExpressionContext>(
42        expression: &Expression,
43        context: &mut C,
44    ) -> Result<Value, ExpressionError> {
45        match expression {
46            // Literal values – return the value directly.
47            Expression::Literal(value) => Ok(value.clone()),
48
49            // Variable – Obtained from the context
50            Expression::Variable(name) => context
51                .get_variable(name)
52                .ok_or_else(|| ExpressionError::undefined_variable(name)),
53
54            // Binary operations – Recursive evaluation of the left and right operands
55            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            // One-element operation – Recursive evaluation of the operand
62            Expression::Unary { op, operand } => {
63                let value = Self::evaluate_recursive(operand, context)?;
64                UnaryOperationEvaluator::evaluate(op, &value)
65            }
66
67            // Function calls – Parameter evaluation in batch
68            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                // First, obtain the function (as an immutable borrowing).
79                let func_ref = context.get_function(name);
80
81                // Sequence functions resolve against the context's sequence
82                // catalog instead of the stateless builtin path: `next_val`
83                // mutates catalog state and `curr_val` reads it, so both
84                // need the manager even when every other facility is absent.
85                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                    // Convert to a function reference with ownership to avoid borrowing issues.
99                    let owned_func: crate::executor::expression::functions::OwnedFunctionRef =
100                        func_ref.clone();
101
102                    // Explicitly releasing the borrow of func_ref
103                    drop(func_ref);
104
105                    // Storage-backed execution takes precedence: the cache
106                    // path cannot supply graph storage to the builtins that
107                    // need it (e.g. startnode/endnode label resolution).
108                    if let Some(storage) = context.get_graph_storage() {
109                        owned_func.execute_with_storage(&arg_values, &storage)
110                    } else if context.supports_cache() {
111                        // Retrieve the cache (variable borrowing).
112                        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 it is not available in the context, use the global registry.
121                    // Sequence functions still resolve against the context's
122                    // manager on this path: row contexts return `None` from
123                    // `get_function` and would otherwise always report a
124                    // missing context.
125                    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                    // Check if graph storage is available
140                    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            // Aggregate functions – Direct evaluation
149            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            // CASE expressions – Short-circuit evaluation
170            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            // List – Batch evaluation
206            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            // Vector literal – Direct evaluation
215            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            // Mapping – Batch evaluation
221            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            // Subscript access
231            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            // Range access
238            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            // Path expression
260            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            // Attribute access — fast path: when the object is a simple
269            // Variable, try `var.prop` as a direct column lookup before
270            // falling back to Vertex/Map extraction.
271            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            // STRUCT field access (e.g. `addr.city`)
283            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            // Type conversion
289            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            // Edge attribute access - look up edge variable and access property
298            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            // Expressions that may require runtime context – delegated to the
313            // context, which either resolves them against the row binding or
314            // reports a precise per-expression error.
315            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                // First result value, or NULL when the subquery is empty.
375                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        // Only intercept the lambda form. Legacy mask calls such as
402        // `list_filter(list, mask)` carry no Lambda and must fall through
403        // to the normal eager path so context-local functions, storage
404        // and named-argument handling stay intact.
405        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        // Unified signature places the lambda at index 1:
412        // `list_filter(source, lambda)`, `list_reduce(source, lambda, initial)`.
413        // Named arguments still preserve positions, so enforce the position
414        // instead of silently accepting permuted orders.
415        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        // NULL source propagates NULL, matching REDUCE and comprehension semantics.
427        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    /// Type conversion for evaluation
529    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        // Check if conversion result is Null(BadData)
616        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        // Overflow wraps rather than erroring, matching integer arithmetic.
860        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        // NULL has no addition rule, so the lambda body error propagates
893        // instead of being silently skipped.
894        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}