Skip to main content

akar_function/scalar/
list.rs

1use super::comparison::double_cmp;
2use super::get_string;
3use crate::registry::*;
4use akar_common::types::Value;
5
6// ==================== List ====================
7
8pub(crate) fn evaluate_list(op: ListOp, args: &[Value]) -> Result<Value, String> {
9    match op {
10        ListOp::Creation => {
11            // list_creation just collects all args into a list
12            Ok(Value::List(args.to_vec()))
13        }
14        ListOp::Len => match &args[0] {
15            Value::List(items) => Ok(Value::Int64(items.len() as i64)),
16            _ => Err("Expected list".into()),
17        },
18        ListOp::Extract => {
19            let list = match &args[0] {
20                Value::List(items) => items,
21                _ => return Err("Expected list".into()),
22            };
23            let idx = match &args[1] {
24                Value::Int64(i) => {
25                    // Cypher uses 1-based indexing
26                    if *i < 1 {
27                        return Err("List index must be >= 1".into());
28                    }
29                    (*i - 1) as usize
30                }
31                _ => return Err("Index must be integer".into()),
32            };
33            list.get(idx)
34                .cloned()
35                .ok_or_else(|| format!("Index {idx} out of bounds"))
36        }
37        ListOp::Concat => {
38            let mut result = Vec::new();
39            for arg in args {
40                match arg {
41                    Value::List(items) => result.extend(items.clone()),
42                    _ => result.push(arg.clone()),
43                }
44            }
45            Ok(Value::List(result))
46        }
47        ListOp::Sort => {
48            let mut list = match args[0].clone() {
49                Value::List(items) => items,
50                _ => return Err("Expected list".into()),
51            };
52            // Sort using Value's PartialOrd implementation (lexicographic)
53            list.sort_by(|a, b| {
54                match compare_values_for_sort(a, b) {
55                    Ok(ord) => ord,
56                    Err(_) => std::cmp::Ordering::Equal, // fallback for incomparable types
57                }
58            });
59            Ok(Value::List(list))
60        }
61        ListOp::Contains => {
62            let list = match &args[0] {
63                Value::List(items) => items,
64                _ => return Err("Expected list".into()),
65            };
66            Ok(Value::Bool(list.contains(&args[1])))
67        }
68        ListOp::Append => {
69            let mut list = match args[0].clone() {
70                Value::List(items) => items,
71                _ => return Err("Expected list".into()),
72            };
73            list.push(args[1].clone());
74            Ok(Value::List(list))
75        }
76        ListOp::Prepend => {
77            let mut list = match args[0].clone() {
78                Value::List(items) => items,
79                _ => return Err("Expected list".into()),
80            };
81            list.insert(0, args[1].clone());
82            Ok(Value::List(list))
83        }
84        ListOp::Reverse => {
85            let mut list = match args[0].clone() {
86                Value::List(items) => items,
87                _ => return Err("Expected list".into()),
88            };
89            list.reverse();
90            Ok(Value::List(list))
91        }
92        ListOp::Slice => {
93            let list = match &args[0] {
94                Value::List(items) => items,
95                _ => return Err("Expected list".into()),
96            };
97            let start = match &args[1] {
98                Value::Int64(i) => {
99                    if *i < 1 {
100                        return Err("Slice start index must be >= 1".into());
101                    }
102                    (*i - 1) as usize
103                }
104                _ => return Err("Slice start must be integer".into()),
105            };
106            if start >= list.len() {
107                return Err("Slice start index out of bounds".into());
108            }
109            if args.len() >= 3 {
110                // Explicit end (1-based inclusive)
111                let end = match &args[2] {
112                    Value::Int64(i) => {
113                        if *i < 1 {
114                            return Err("Slice end index must be >= 1".into());
115                        }
116                        (*i - 1) as usize
117                    }
118                    _ => return Err("Slice end must be integer".into()),
119                };
120                if end >= list.len() || end < start {
121                    return Err("Slice end index out of bounds".into());
122                }
123                Ok(Value::List(list[start..=end].to_vec()))
124            } else {
125                // No end specified — slice to the end of the list
126                Ok(Value::List(list[start..].to_vec()))
127            }
128        }
129        // --- List functions (C++ port) ---
130        ListOp::Range => {
131            let step = if args.len() >= 3 {
132                match &args[2] {
133                    Value::Int64(s) => *s,
134                    _ => 1i64,
135                }
136            } else {
137                1i64
138            };
139            let (start, end) = if args.len() >= 2 {
140                match (&args[0], &args[1]) {
141                    (Value::Int64(s), Value::Int64(e)) => (*s, *e),
142                    _ => return Err("RANGE requires integer arguments".into()),
143                }
144            } else {
145                match &args[0] {
146                    Value::Int64(e) => (0i64, *e),
147                    _ => return Err("RANGE requires integer arguments".into()),
148                }
149            };
150            if step == 0 {
151                return Err("Step of range cannot be 0".into());
152            }
153            if (end - start).signum() != step.signum() && end != start {
154                Ok(Value::List(vec![]))
155            } else {
156                let size = ((end - start).unsigned_abs() / step.unsigned_abs()) + 1;
157                let items: Vec<Value> = (0..size).map(|i| Value::Int64(start + step * i as i64)).collect();
158                Ok(Value::List(items))
159            }
160        }
161        ListOp::Distinct => {
162            let list = match &args[0] {
163                Value::List(items) => items,
164                _ => return Err("Expected list".into()),
165            };
166            let mut seen = hashbrown::HashSet::new();
167            let mut result = Vec::new();
168            for item in list {
169                if !matches!(item, Value::Null) && seen.insert(format!("{:?}", item)) {
170                    result.push(item.clone());
171                }
172            }
173            Ok(Value::List(result))
174        }
175        ListOp::Unique => {
176            let list = match &args[0] {
177                Value::List(items) => items,
178                _ => return Err("Expected list".into()),
179            };
180            let mut seen = hashbrown::HashSet::new();
181            for item in list {
182                if !matches!(item, Value::Null) {
183                    seen.insert(format!("{:?}", item));
184                }
185            }
186            Ok(Value::Int64(seen.len() as i64))
187        }
188        ListOp::Count => {
189            let list = match &args[0] {
190                Value::List(items) => items,
191                _ => return Err("Expected list".into()),
192            };
193            let val = &args[1];
194            let mut count = 0;
195            for item in list {
196                if item == val {
197                    count += 1;
198                }
199            }
200            Ok(Value::Int64(count as i64))
201        }
202        ListOp::Min => {
203            let list = match &args[0] {
204                Value::List(items) => items,
205                _ => return Err("Expected list".into()),
206            };
207            if list.is_empty() {
208                return Ok(Value::Null);
209            }
210            let mut min_val = list[0].clone();
211            for item in list.iter().skip(1) {
212                if let Ok(std::cmp::Ordering::Less) = compare_values_for_sort(item, &min_val) {
213                    min_val = item.clone();
214                }
215            }
216            Ok(min_val)
217        }
218        ListOp::Max => {
219            let list = match &args[0] {
220                Value::List(items) => items,
221                _ => return Err("Expected list".into()),
222            };
223            if list.is_empty() {
224                return Ok(Value::Null);
225            }
226            let mut max_val = list[0].clone();
227            for item in list.iter().skip(1) {
228                if let Ok(std::cmp::Ordering::Greater) = compare_values_for_sort(item, &max_val) {
229                    max_val = item.clone();
230                }
231            }
232            Ok(max_val)
233        }
234        ListOp::HasAny => {
235            let list = match &args[0] {
236                Value::List(items) => items,
237                _ => return Err("Expected list".into()),
238            };
239            let search_items = match &args[1] {
240                Value::List(items) => items,
241                _ => return Err("Expected list for second argument".into()),
242            };
243            for search_item in search_items {
244                if list.contains(search_item) {
245                    return Ok(Value::Bool(true));
246                }
247            }
248            Ok(Value::Bool(false))
249        }
250        ListOp::Sum => {
251            let list = match &args[0] {
252                Value::List(items) => items,
253                _ => return Err("Expected list".into()),
254            };
255            let mut sum: f64 = 0.0;
256            let mut is_int = true;
257            for item in list {
258                match item {
259                    Value::Null => continue,
260                    Value::Int64(x) => sum += *x as f64,
261                    Value::Double(x) => {
262                        sum += x;
263                        is_int = false;
264                    }
265                    _ => return Err("LIST_SUM requires numeric list".into()),
266                }
267            }
268            if is_int {
269                Ok(Value::Int64(sum as i64))
270            } else {
271                Ok(Value::Double(sum))
272            }
273        }
274        ListOp::Product => {
275            let list = match &args[0] {
276                Value::List(items) => items,
277                _ => return Err("Expected list".into()),
278            };
279            let mut prod: f64 = 1.0;
280            let mut is_int = true;
281            for item in list {
282                match item {
283                    Value::Null => continue,
284                    Value::Int64(x) => prod *= *x as f64,
285                    Value::Double(x) => {
286                        prod *= x;
287                        is_int = false;
288                    }
289                    _ => return Err("LIST_PRODUCT requires numeric list".into()),
290                }
291            }
292            if is_int {
293                Ok(Value::Int64(prod as i64))
294            } else {
295                Ok(Value::Double(prod))
296            }
297        }
298        ListOp::AnyValue => {
299            let list = match &args[0] {
300                Value::List(items) => items,
301                _ => return Err("Expected list".into()),
302            };
303            // Return first non-null element
304            match list.iter().find(|v| !matches!(v, Value::Null)) {
305                Some(v) => Ok(v.clone()),
306                None => Ok(Value::Null),
307            }
308        }
309        ListOp::ToString => {
310            // Parameters: (delimiter: STRING, list: LIST)
311            if args.len() < 2 {
312                return Err("list_to_string requires delimiter and list arguments".into());
313            }
314            let delim = get_string(&args[0])?;
315            let list = match &args[1] {
316                Value::List(items) => items,
317                _ => return Err("Expected list".into()),
318            };
319            let mut result = String::new();
320            let mut first = true;
321            for item in list {
322                if matches!(item, Value::Null) {
323                    continue;
324                }
325                if !first {
326                    result.push_str(&delim);
327                }
328                match item {
329                    Value::String(s) => result.push_str(s),
330                    other => result.push_str(&format!("{:?}", other)),
331                }
332                first = false;
333            }
334            Ok(Value::String(result))
335        }
336        ListOp::Position => {
337            let list = match &args[0] {
338                Value::List(items) => items,
339                _ => return Err("Expected list".into()),
340            };
341            let target = &args[1];
342            // 1-based index, returns 0 if not found
343            for (i, item) in list.iter().enumerate() {
344                if item == target {
345                    return Ok(Value::Int64((i + 1) as i64));
346                }
347            }
348            Ok(Value::Int64(0))
349        }
350        ListOp::HasAll => {
351            let left = match &args[0] {
352                Value::List(items) => items,
353                _ => return Err("Expected list".into()),
354            };
355            let right = match &args[1] {
356                Value::List(items) => items,
357                _ => return Err("Expected list".into()),
358            };
359            for target in right {
360                if matches!(target, Value::Null) {
361                    continue;
362                }
363                if !left.contains(target) {
364                    return Ok(Value::Bool(false));
365                }
366            }
367            Ok(Value::Bool(true))
368        }
369        ListOp::ReverseSort => {
370            let mut list = match args[0].clone() {
371                Value::List(items) => items,
372                _ => return Err("Expected list".into()),
373            };
374            list.sort_by(|a, b| match compare_values_for_sort(a, b) {
375                Ok(ord) => ord.reverse(),
376                Err(_) => std::cmp::Ordering::Equal,
377            });
378            Ok(Value::List(list))
379        }
380        // --- List predicate functions (non-lambda) ---
381        ListOp::Any => {
382            let list = match &args[0] {
383                Value::List(items) => items,
384                _ => return Err("Expected list".into()),
385            };
386            Ok(Value::Bool(list.iter().any(is_truthy)))
387        }
388        ListOp::All => {
389            let list = match &args[0] {
390                Value::List(items) => items,
391                _ => return Err("Expected list".into()),
392            };
393            Ok(Value::Bool(!list.is_empty() && list.iter().all(is_truthy)))
394        }
395        ListOp::None => {
396            let list = match &args[0] {
397                Value::List(items) => items,
398                _ => return Err("Expected list".into()),
399            };
400            Ok(Value::Bool(list.iter().all(|v| !is_truthy(v))))
401        }
402        ListOp::Single => {
403            let list = match &args[0] {
404                Value::List(items) => items,
405                _ => return Err("Expected list".into()),
406            };
407            let count = list.iter().filter(|v| is_truthy(v)).count();
408            Ok(Value::Bool(count == 1))
409        }
410        // Lambda-based list functions — require expression evaluator path
411        ListOp::Transform => Err("list_transform requires a lambda expression — use in a query context".into()),
412        ListOp::Filter => Err("list_filter requires a lambda expression — use in a query context".into()),
413        ListOp::Reduce => Err("list_reduce requires a lambda expression — use in a query context".into()),
414    }
415}
416
417/// Check if a Value is "truthy": Bool(true) or non-zero Int64/Double.
418fn is_truthy(v: &Value) -> bool {
419    match v {
420        Value::Bool(b) => *b,
421        Value::Int64(x) => *x != 0,
422        Value::Double(x) => *x != 0.0,
423        _ => false,
424    }
425}
426
427/// Compare two Values for sorting purposes. Supports numeric, string, bool, date, timestamp.
428pub(crate) fn compare_values_for_sort(a: &Value, b: &Value) -> Result<std::cmp::Ordering, String> {
429    match (a, b) {
430        (Value::Null, Value::Null) => Ok(std::cmp::Ordering::Equal),
431        (Value::Null, _) => Ok(std::cmp::Ordering::Less),
432        (_, Value::Null) => Ok(std::cmp::Ordering::Greater),
433        (Value::Int64(x), Value::Int64(y)) => Ok(x.cmp(y)),
434        (Value::Int32(x), Value::Int32(y)) => Ok(x.cmp(y)),
435        (Value::Int16(x), Value::Int16(y)) => Ok(x.cmp(y)),
436        (Value::Int8(x), Value::Int8(y)) => Ok(x.cmp(y)),
437        (Value::UInt64(x), Value::UInt64(y)) => Ok(x.cmp(y)),
438        (Value::UInt32(x), Value::UInt32(y)) => Ok(x.cmp(y)),
439        (Value::UInt16(x), Value::UInt16(y)) => Ok(x.cmp(y)),
440        (Value::UInt8(x), Value::UInt8(y)) => Ok(x.cmp(y)),
441        (Value::Double(x), Value::Double(y)) => Ok(double_cmp(*x, *y)),
442        (Value::Float(x), Value::Float(y)) => Ok(double_cmp(*x as f64, *y as f64)),
443        (Value::String(x), Value::String(y)) => Ok(x.cmp(y)),
444        (Value::Bool(x), Value::Bool(y)) => Ok(x.cmp(y)),
445        (Value::Date(x), Value::Date(y)) => Ok(x.cmp(y)),
446        (Value::Timestamp(x), Value::Timestamp(y)) => Ok(x.cmp(y)),
447        // Cross-type numeric promotion
448        (Value::Int64(x), Value::Double(y)) => Ok(double_cmp(*x as f64, *y)),
449        (Value::Double(x), Value::Int64(y)) => Ok(double_cmp(*x, *y as f64)),
450        _ => Err("Cannot compare types for sort".into()),
451    }
452}