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// ==================== ValueRef Helper for Hashing ====================
7
8#[derive(Clone, Copy)]
9pub(crate) struct ValueRef<'a>(pub(crate) &'a Value);
10
11impl<'a> PartialEq for ValueRef<'a> {
12    #[inline]
13    fn eq(&self, other: &Self) -> bool {
14        self.0 == other.0
15    }
16}
17
18impl<'a> Eq for ValueRef<'a> {}
19
20impl<'a> std::hash::Hash for ValueRef<'a> {
21    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
22        std::mem::discriminant(self.0).hash(state);
23        match self.0 {
24            Value::Null => {}
25            Value::Bool(b) => b.hash(state),
26            Value::Int64(i) => i.hash(state),
27            Value::Int32(i) => i.hash(state),
28            Value::Int16(i) => i.hash(state),
29            Value::Int8(i) => i.hash(state),
30            Value::UInt64(u) => u.hash(state),
31            Value::UInt32(u) => u.hash(state),
32            Value::UInt16(u) => u.hash(state),
33            Value::UInt8(u) => u.hash(state),
34            Value::Int128(i) => i.hash(state),
35            Value::UInt128(u) => u.hash(state),
36            Value::Double(f) => {
37                let bits = if f.is_nan() {
38                    0x7ff8000000000000u64
39                } else if *f == 0.0 {
40                    0u64
41                } else {
42                    f.to_bits()
43                };
44                bits.hash(state);
45            }
46            Value::Float(f) => {
47                let bits = if f.is_nan() {
48                    0x7fc00000u32
49                } else if *f == 0.0 {
50                    0u32
51                } else {
52                    f.to_bits()
53                };
54                bits.hash(state);
55            }
56            Value::String(s) => s.hash(state),
57            Value::Blob(b) => b.hash(state),
58            Value::Date(d) => d.0.hash(state),
59            Value::Timestamp(t) => t.0.hash(state),
60            Value::TimestampTz(t) => t.0.hash(state),
61            Value::TimestampNs(t) => t.0.hash(state),
62            Value::TimestampMs(t) => t.0.hash(state),
63            Value::TimestampSec(t) => t.0.hash(state),
64            Value::Interval(inv) => {
65                inv.months.hash(state);
66                inv.days.hash(state);
67                inv.micros.hash(state);
68            }
69            Value::InternalID(id) => {
70                id.table_id.hash(state);
71                id.offset.hash(state);
72            }
73            Value::DTime(t) => t.hash(state),
74            Value::Union(tag, val) => {
75                tag.hash(state);
76                ValueRef(val).hash(state);
77            }
78            Value::List(items) => {
79                for item in items {
80                    ValueRef(item).hash(state);
81                }
82            }
83            Value::Map(entries) => {
84                for (k, v) in entries {
85                    ValueRef(k).hash(state);
86                    ValueRef(v).hash(state);
87                }
88            }
89            Value::Struct(fields) => {
90                for (k, v) in fields {
91                    k.hash(state);
92                    ValueRef(v).hash(state);
93                }
94            }
95            Value::Json(j) => {
96                j.to_string().hash(state);
97            }
98        }
99    }
100}
101
102// ==================== List ====================
103
104pub(crate) fn evaluate_list(op: ListOp, args: &[Value]) -> Result<Value, String> {
105    match op {
106        ListOp::Creation => {
107            // list_creation just collects all args into a list
108            Ok(Value::List(args.to_vec()))
109        }
110        ListOp::Len => match &args[0] {
111            Value::List(items) => Ok(Value::Int64(items.len() as i64)),
112            _ => Err("Expected list".into()),
113        },
114        ListOp::Extract => {
115            let list = match &args[0] {
116                Value::List(items) => items,
117                _ => return Err("Expected list".into()),
118            };
119            let idx = match &args[1] {
120                Value::Int64(i) => {
121                    // Cypher uses 1-based indexing
122                    if *i < 1 {
123                        return Err("List index must be >= 1".into());
124                    }
125                    (*i - 1) as usize
126                }
127                _ => return Err("Index must be integer".into()),
128            };
129            list.get(idx)
130                .cloned()
131                .ok_or_else(|| format!("Index {idx} out of bounds"))
132        }
133        ListOp::Concat => {
134            let mut result = Vec::new();
135            for arg in args {
136                match arg {
137                    Value::List(items) => result.extend(items.clone()),
138                    _ => result.push(arg.clone()),
139                }
140            }
141            Ok(Value::List(result))
142        }
143        ListOp::Sort => {
144            let mut list = match args[0].clone() {
145                Value::List(items) => items,
146                _ => return Err("Expected list".into()),
147            };
148            // Sort using Value's PartialOrd implementation (lexicographic)
149            list.sort_by(|a, b| {
150                match compare_values_for_sort(a, b) {
151                    Ok(ord) => ord,
152                    Err(_) => std::cmp::Ordering::Equal, // fallback for incomparable types
153                }
154            });
155            Ok(Value::List(list))
156        }
157        ListOp::Contains => {
158            let list = match &args[0] {
159                Value::List(items) => items,
160                _ => return Err("Expected list".into()),
161            };
162            Ok(Value::Bool(list.contains(&args[1])))
163        }
164        ListOp::Append => {
165            let mut list = match args[0].clone() {
166                Value::List(items) => items,
167                _ => return Err("Expected list".into()),
168            };
169            list.push(args[1].clone());
170            Ok(Value::List(list))
171        }
172        ListOp::Prepend => {
173            let mut list = match args[0].clone() {
174                Value::List(items) => items,
175                _ => return Err("Expected list".into()),
176            };
177            list.insert(0, args[1].clone());
178            Ok(Value::List(list))
179        }
180        ListOp::Reverse => {
181            let mut list = match args[0].clone() {
182                Value::List(items) => items,
183                _ => return Err("Expected list".into()),
184            };
185            list.reverse();
186            Ok(Value::List(list))
187        }
188        ListOp::Slice => {
189            let list = match &args[0] {
190                Value::List(items) => items,
191                _ => return Err("Expected list".into()),
192            };
193            let start = match &args[1] {
194                Value::Int64(i) => {
195                    if *i < 1 {
196                        return Err("Slice start index must be >= 1".into());
197                    }
198                    (*i - 1) as usize
199                }
200                _ => return Err("Slice start must be integer".into()),
201            };
202            if start >= list.len() {
203                return Err("Slice start index out of bounds".into());
204            }
205            if args.len() >= 3 {
206                // Explicit end (1-based inclusive)
207                let end = match &args[2] {
208                    Value::Int64(i) => {
209                        if *i < 1 {
210                            return Err("Slice end index must be >= 1".into());
211                        }
212                        (*i - 1) as usize
213                    }
214                    _ => return Err("Slice end must be integer".into()),
215                };
216                if end >= list.len() || end < start {
217                    return Err("Slice end index out of bounds".into());
218                }
219                Ok(Value::List(list[start..=end].to_vec()))
220            } else {
221                // No end specified — slice to the end of the list
222                Ok(Value::List(list[start..].to_vec()))
223            }
224        }
225        // --- List functions (C++ port) ---
226        ListOp::Range => {
227            let step = if args.len() >= 3 {
228                match &args[2] {
229                    Value::Int64(s) => *s,
230                    _ => 1i64,
231                }
232            } else {
233                1i64
234            };
235            let (start, end) = if args.len() >= 2 {
236                match (&args[0], &args[1]) {
237                    (Value::Int64(s), Value::Int64(e)) => (*s, *e),
238                    _ => return Err("RANGE requires integer arguments".into()),
239                }
240            } else {
241                match &args[0] {
242                    Value::Int64(e) => (0i64, *e),
243                    _ => return Err("RANGE requires integer arguments".into()),
244                }
245            };
246            if step == 0 {
247                return Err("Step of range cannot be 0".into());
248            }
249            if (end - start).signum() != step.signum() && end != start {
250                Ok(Value::List(vec![]))
251            } else {
252                let size = ((end - start).unsigned_abs() / step.unsigned_abs()) + 1;
253                let items: Vec<Value> = (0..size).map(|i| Value::Int64(start + step * i as i64)).collect();
254                Ok(Value::List(items))
255            }
256        }
257        ListOp::Distinct => {
258            let list = match &args[0] {
259                Value::List(items) => items,
260                _ => return Err("Expected list".into()),
261            };
262            let mut seen = hashbrown::HashSet::with_capacity(list.len());
263            let mut result = Vec::new();
264            for item in list {
265                if !matches!(item, Value::Null) && seen.insert(ValueRef(item)) {
266                    result.push(item.clone());
267                }
268            }
269            Ok(Value::List(result))
270        }
271        ListOp::Unique => {
272            let list = match &args[0] {
273                Value::List(items) => items,
274                _ => return Err("Expected list".into()),
275            };
276            let mut seen = hashbrown::HashSet::with_capacity(list.len());
277            for item in list {
278                if !matches!(item, Value::Null) {
279                    seen.insert(ValueRef(item));
280                }
281            }
282            Ok(Value::Int64(seen.len() as i64))
283        }
284        ListOp::Count => {
285            let list = match &args[0] {
286                Value::List(items) => items,
287                _ => return Err("Expected list".into()),
288            };
289            let val = &args[1];
290            let mut count = 0;
291            for item in list {
292                if item == val {
293                    count += 1;
294                }
295            }
296            Ok(Value::Int64(count as i64))
297        }
298        ListOp::Min => {
299            let list = match &args[0] {
300                Value::List(items) => items,
301                _ => return Err("Expected list".into()),
302            };
303            if list.is_empty() {
304                return Ok(Value::Null);
305            }
306            let mut min_val = list[0].clone();
307            for item in list.iter().skip(1) {
308                if let Ok(std::cmp::Ordering::Less) = compare_values_for_sort(item, &min_val) {
309                    min_val = item.clone();
310                }
311            }
312            Ok(min_val)
313        }
314        ListOp::Max => {
315            let list = match &args[0] {
316                Value::List(items) => items,
317                _ => return Err("Expected list".into()),
318            };
319            if list.is_empty() {
320                return Ok(Value::Null);
321            }
322            let mut max_val = list[0].clone();
323            for item in list.iter().skip(1) {
324                if let Ok(std::cmp::Ordering::Greater) = compare_values_for_sort(item, &max_val) {
325                    max_val = item.clone();
326                }
327            }
328            Ok(max_val)
329        }
330        ListOp::HasAny => {
331            let list = match &args[0] {
332                Value::List(items) => items,
333                _ => return Err("Expected list".into()),
334            };
335            let search_items = match &args[1] {
336                Value::List(items) => items,
337                _ => return Err("Expected list for second argument".into()),
338            };
339            if list.len() <= 8 && search_items.len() <= 8 {
340                for search_item in search_items {
341                    if list.contains(search_item) {
342                        return Ok(Value::Bool(true));
343                    }
344                }
345                return Ok(Value::Bool(false));
346            }
347            if list.len() <= search_items.len() {
348                let mut set = hashbrown::HashSet::with_capacity(list.len());
349                for item in list {
350                    set.insert(ValueRef(item));
351                }
352                for search_item in search_items {
353                    if set.contains(&ValueRef(search_item)) {
354                        return Ok(Value::Bool(true));
355                    }
356                }
357            } else {
358                let mut set = hashbrown::HashSet::with_capacity(search_items.len());
359                for search_item in search_items {
360                    set.insert(ValueRef(search_item));
361                }
362                for item in list {
363                    if set.contains(&ValueRef(item)) {
364                        return Ok(Value::Bool(true));
365                    }
366                }
367            }
368            Ok(Value::Bool(false))
369        }
370        ListOp::Sum => {
371            let list = match &args[0] {
372                Value::List(items) => items,
373                _ => return Err("Expected list".into()),
374            };
375            let mut sum: f64 = 0.0;
376            let mut is_int = true;
377            for item in list {
378                match item {
379                    Value::Null => continue,
380                    Value::Int64(x) => sum += *x as f64,
381                    Value::Double(x) => {
382                        sum += x;
383                        is_int = false;
384                    }
385                    _ => return Err("LIST_SUM requires numeric list".into()),
386                }
387            }
388            if is_int {
389                Ok(Value::Int64(sum as i64))
390            } else {
391                Ok(Value::Double(sum))
392            }
393        }
394        ListOp::Product => {
395            let list = match &args[0] {
396                Value::List(items) => items,
397                _ => return Err("Expected list".into()),
398            };
399            let mut prod: f64 = 1.0;
400            let mut is_int = true;
401            for item in list {
402                match item {
403                    Value::Null => continue,
404                    Value::Int64(x) => prod *= *x as f64,
405                    Value::Double(x) => {
406                        prod *= x;
407                        is_int = false;
408                    }
409                    _ => return Err("LIST_PRODUCT requires numeric list".into()),
410                }
411            }
412            if is_int {
413                Ok(Value::Int64(prod as i64))
414            } else {
415                Ok(Value::Double(prod))
416            }
417        }
418        ListOp::AnyValue => {
419            let list = match &args[0] {
420                Value::List(items) => items,
421                _ => return Err("Expected list".into()),
422            };
423            // Return first non-null element
424            match list.iter().find(|v| !matches!(v, Value::Null)) {
425                Some(v) => Ok(v.clone()),
426                None => Ok(Value::Null),
427            }
428        }
429        ListOp::ToString => {
430            // Parameters: (delimiter: STRING, list: LIST)
431            if args.len() < 2 {
432                return Err("list_to_string requires delimiter and list arguments".into());
433            }
434            let delim = get_string(&args[0])?;
435            let list = match &args[1] {
436                Value::List(items) => items,
437                _ => return Err("Expected list".into()),
438            };
439            let mut result = String::new();
440            let mut first = true;
441            for item in list {
442                if matches!(item, Value::Null) {
443                    continue;
444                }
445                if !first {
446                    result.push_str(&delim);
447                }
448                match item {
449                    Value::String(s) => result.push_str(s),
450                    other => result.push_str(&format!("{:?}", other)),
451                }
452                first = false;
453            }
454            Ok(Value::String(result))
455        }
456        ListOp::Position => {
457            let list = match &args[0] {
458                Value::List(items) => items,
459                _ => return Err("Expected list".into()),
460            };
461            let target = &args[1];
462            // 1-based index, returns 0 if not found
463            for (i, item) in list.iter().enumerate() {
464                if item == target {
465                    return Ok(Value::Int64((i + 1) as i64));
466                }
467            }
468            Ok(Value::Int64(0))
469        }
470        ListOp::HasAll => {
471            let left = match &args[0] {
472                Value::List(items) => items,
473                _ => return Err("Expected list".into()),
474            };
475            let right = match &args[1] {
476                Value::List(items) => items,
477                _ => return Err("Expected list".into()),
478            };
479            if left.len() <= 8 {
480                for target in right {
481                    if matches!(target, Value::Null) {
482                        continue;
483                    }
484                    if !left.contains(target) {
485                        return Ok(Value::Bool(false));
486                    }
487                }
488                return Ok(Value::Bool(true));
489            }
490            let mut set = hashbrown::HashSet::with_capacity(left.len());
491            for item in left {
492                set.insert(ValueRef(item));
493            }
494            for target in right {
495                if matches!(target, Value::Null) {
496                    continue;
497                }
498                if !set.contains(&ValueRef(target)) {
499                    return Ok(Value::Bool(false));
500                }
501            }
502            Ok(Value::Bool(true))
503        }
504        ListOp::ReverseSort => {
505            let mut list = match args[0].clone() {
506                Value::List(items) => items,
507                _ => return Err("Expected list".into()),
508            };
509            list.sort_by(|a, b| match compare_values_for_sort(a, b) {
510                Ok(ord) => ord.reverse(),
511                Err(_) => std::cmp::Ordering::Equal,
512            });
513            Ok(Value::List(list))
514        }
515        // --- List predicate functions (non-lambda) ---
516        ListOp::Any => {
517            let list = match &args[0] {
518                Value::List(items) => items,
519                _ => return Err("Expected list".into()),
520            };
521            Ok(Value::Bool(list.iter().any(is_truthy)))
522        }
523        ListOp::All => {
524            let list = match &args[0] {
525                Value::List(items) => items,
526                _ => return Err("Expected list".into()),
527            };
528            Ok(Value::Bool(!list.is_empty() && list.iter().all(is_truthy)))
529        }
530        ListOp::None => {
531            let list = match &args[0] {
532                Value::List(items) => items,
533                _ => return Err("Expected list".into()),
534            };
535            Ok(Value::Bool(list.iter().all(|v| !is_truthy(v))))
536        }
537        ListOp::Single => {
538            let list = match &args[0] {
539                Value::List(items) => items,
540                _ => return Err("Expected list".into()),
541            };
542            let count = list.iter().filter(|v| is_truthy(v)).count();
543            Ok(Value::Bool(count == 1))
544        }
545        // Lambda-based list functions — require expression evaluator path
546        ListOp::Transform => Err("list_transform requires a lambda expression — use in a query context".into()),
547        ListOp::Filter => Err("list_filter requires a lambda expression — use in a query context".into()),
548        ListOp::Reduce => Err("list_reduce requires a lambda expression — use in a query context".into()),
549    }
550}
551
552/// Check if a Value is "truthy": Bool(true) or non-zero Int64/Double.
553fn is_truthy(v: &Value) -> bool {
554    match v {
555        Value::Bool(b) => *b,
556        Value::Int64(x) => *x != 0,
557        Value::Double(x) => *x != 0.0,
558        _ => false,
559    }
560}
561
562/// Compare two Values for sorting purposes. Supports numeric, string, bool, date, timestamp.
563pub(crate) fn compare_values_for_sort(a: &Value, b: &Value) -> Result<std::cmp::Ordering, String> {
564    match (a, b) {
565        (Value::Null, Value::Null) => Ok(std::cmp::Ordering::Equal),
566        (Value::Null, _) => Ok(std::cmp::Ordering::Less),
567        (_, Value::Null) => Ok(std::cmp::Ordering::Greater),
568        (Value::Int64(x), Value::Int64(y)) => Ok(x.cmp(y)),
569        (Value::Int32(x), Value::Int32(y)) => Ok(x.cmp(y)),
570        (Value::Int16(x), Value::Int16(y)) => Ok(x.cmp(y)),
571        (Value::Int8(x), Value::Int8(y)) => Ok(x.cmp(y)),
572        (Value::UInt64(x), Value::UInt64(y)) => Ok(x.cmp(y)),
573        (Value::UInt32(x), Value::UInt32(y)) => Ok(x.cmp(y)),
574        (Value::UInt16(x), Value::UInt16(y)) => Ok(x.cmp(y)),
575        (Value::UInt8(x), Value::UInt8(y)) => Ok(x.cmp(y)),
576        (Value::Double(x), Value::Double(y)) => Ok(double_cmp(*x, *y)),
577        (Value::Float(x), Value::Float(y)) => Ok(double_cmp(*x as f64, *y as f64)),
578        (Value::String(x), Value::String(y)) => Ok(x.cmp(y)),
579        (Value::Bool(x), Value::Bool(y)) => Ok(x.cmp(y)),
580        (Value::Date(x), Value::Date(y)) => Ok(x.cmp(y)),
581        (Value::Timestamp(x), Value::Timestamp(y)) => Ok(x.cmp(y)),
582        // Cross-type numeric promotion
583        (Value::Int64(x), Value::Double(y)) => Ok(double_cmp(*x as f64, *y)),
584        (Value::Double(x), Value::Int64(y)) => Ok(double_cmp(*x, *y as f64)),
585        _ => Err("Cannot compare types for sort".into()),
586    }
587}