Skip to main content

jpx_core/extensions/
expression.rs

1//! Expression functions.
2//!
3//! Higher-order functions that use expression references (exprefs) to apply
4//! transformations, filters, and reductions over arrays and objects.
5
6use std::collections::HashSet;
7
8use serde_json::{Map, Number, Value};
9
10use crate::ast::Ast;
11use crate::functions::{Function, custom_error, number_value};
12use crate::interpreter::{SearchResult, interpret};
13use crate::registry::register_if_enabled;
14use crate::value_ext::ValueExt;
15use crate::{Context, Runtime, arg, defn, get_expref_id};
16
17/// Helper to extract an expref AST from a function argument.
18fn get_expref_ast<'a>(value: &Value, ctx: &'a Context<'_>) -> Option<&'a Ast> {
19    get_expref_id(value).and_then(|id| ctx.get_expref(id))
20}
21
22/// Convert a Value to a string key for grouping/deduplication.
23fn value_to_string(value: &Value) -> String {
24    match value {
25        Value::String(s) => s.clone(),
26        Value::Number(n) => n.to_string(),
27        Value::Bool(b) => b.to_string(),
28        Value::Null => "null".to_string(),
29        _ => serde_json::to_string(value).unwrap_or_default(),
30    }
31}
32
33/// Compare two values for sorting purposes.
34fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
35    use std::cmp::Ordering;
36    match (a, b) {
37        (Value::Number(an), Value::Number(bn)) => {
38            let a_f = an.as_f64().unwrap_or(0.0);
39            let b_f = bn.as_f64().unwrap_or(0.0);
40            a_f.partial_cmp(&b_f).unwrap_or(Ordering::Equal)
41        }
42        (Value::String(a_s), Value::String(b_s)) => a_s.cmp(b_s),
43        (Value::Null, Value::Null) => Ordering::Equal,
44        (Value::Null, _) => Ordering::Less,
45        (_, Value::Null) => Ordering::Greater,
46        _ => Ordering::Equal,
47    }
48}
49
50/// Register only the expression functions that are in the enabled set.
51pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
52    register_if_enabled(runtime, "map_expr", enabled, Box::new(MapExprFn::new()));
53    register_if_enabled(
54        runtime,
55        "filter_expr",
56        enabled,
57        Box::new(FilterExprFn::new()),
58    );
59    register_if_enabled(runtime, "any_expr", enabled, Box::new(AnyExprFn::new()));
60    register_if_enabled(runtime, "all_expr", enabled, Box::new(AllExprFn::new()));
61    register_if_enabled(runtime, "find_expr", enabled, Box::new(FindExprFn::new()));
62    register_if_enabled(
63        runtime,
64        "find_index_expr",
65        enabled,
66        Box::new(FindIndexExprFn::new()),
67    );
68    register_if_enabled(runtime, "count_expr", enabled, Box::new(CountExprFn::new()));
69    register_if_enabled(
70        runtime,
71        "sort_by_expr",
72        enabled,
73        Box::new(SortByExprFn::new()),
74    );
75    register_if_enabled(
76        runtime,
77        "group_by_expr",
78        enabled,
79        Box::new(GroupByExprFn::new()),
80    );
81    register_if_enabled(
82        runtime,
83        "partition_expr",
84        enabled,
85        Box::new(PartitionExprFn::new()),
86    );
87    register_if_enabled(
88        runtime,
89        "min_by_expr",
90        enabled,
91        Box::new(MinByExprFn::new()),
92    );
93    register_if_enabled(
94        runtime,
95        "max_by_expr",
96        enabled,
97        Box::new(MaxByExprFn::new()),
98    );
99    register_if_enabled(
100        runtime,
101        "unique_by_expr",
102        enabled,
103        Box::new(UniqueByExprFn::new()),
104    );
105    register_if_enabled(
106        runtime,
107        "flat_map_expr",
108        enabled,
109        Box::new(FlatMapExprFn::new()),
110    );
111
112    // Clojure-style alias for flat_map_expr
113    register_if_enabled(runtime, "mapcat", enabled, Box::new(FlatMapExprFn::new()));
114
115    // Lodash-style aliases
116    register_if_enabled(runtime, "some", enabled, Box::new(AnyExprFn::new()));
117    register_if_enabled(runtime, "every", enabled, Box::new(AllExprFn::new()));
118    register_if_enabled(runtime, "reject", enabled, Box::new(RejectFn::new()));
119    register_if_enabled(runtime, "map_keys", enabled, Box::new(MapKeysFn::new()));
120    register_if_enabled(runtime, "map_values", enabled, Box::new(MapValuesFn::new()));
121    register_if_enabled(runtime, "order_by", enabled, Box::new(OrderByFn::new()));
122    register_if_enabled(
123        runtime,
124        "reduce_expr",
125        enabled,
126        Box::new(ReduceExprFn::new()),
127    );
128    register_if_enabled(runtime, "scan_expr", enabled, Box::new(ScanExprFn::new()));
129    // Alias for reduce_expr (lodash-style)
130    register_if_enabled(runtime, "fold", enabled, Box::new(ReduceExprFn::new()));
131    // Clojure-style alias for scan_expr
132    register_if_enabled(runtime, "reductions", enabled, Box::new(ScanExprFn::new()));
133    // none - opposite of any_expr/some
134    register_if_enabled(runtime, "none", enabled, Box::new(NoneFn::new()));
135    register_if_enabled(runtime, "count_by", enabled, Box::new(CountByFn::new()));
136
137    // Partial application functions
138    register_if_enabled(runtime, "partial", enabled, Box::new(PartialFn::new()));
139    register_if_enabled(runtime, "apply", enabled, Box::new(ApplyFn::new()));
140
141    // Functional array operations
142    register_if_enabled(runtime, "take_while", enabled, Box::new(TakeWhileFn::new()));
143    register_if_enabled(runtime, "drop_while", enabled, Box::new(DropWhileFn::new()));
144    register_if_enabled(runtime, "zip_with", enabled, Box::new(ZipWithFn::new()));
145
146    // Recursive transformation
147    register_if_enabled(runtime, "walk", enabled, Box::new(WalkFn::new()));
148
149    // Recursive descent (jq parity)
150    register_if_enabled(runtime, "recurse", enabled, Box::new(RecurseFn::new()));
151    register_if_enabled(
152        runtime,
153        "recurse_with",
154        enabled,
155        Box::new(RecurseWithFn::new()),
156    );
157
158    // Loop functions (jq parity)
159    register_if_enabled(runtime, "while_expr", enabled, Box::new(WhileExprFn::new()));
160    register_if_enabled(runtime, "until_expr", enabled, Box::new(UntilExprFn::new()));
161
162    // Window/ranking functions
163    register_if_enabled(runtime, "rank", enabled, Box::new(RankFn::new()));
164    register_if_enabled(runtime, "dense_rank", enabled, Box::new(DenseRankFn::new()));
165
166    // Data reshaping
167    register_if_enabled(runtime, "pivot", enabled, Box::new(PivotFn::new()));
168    register_if_enabled(runtime, "unpivot", enabled, Box::new(UnpivotFn::new()));
169}
170
171// =============================================================================
172// map_expr(expr, array) -> array
173// =============================================================================
174
175defn!(MapExprFn, vec![arg!(expref), arg!(array)], None);
176
177impl Function for MapExprFn {
178    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
179        self.signature.validate(args, ctx)?;
180
181        let ast = get_expref_ast(&args[0], ctx)
182            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
183            .clone();
184        let arr = args[1].as_array().unwrap();
185
186        let mut results = Vec::with_capacity(arr.len());
187        for item in arr {
188            results.push(interpret(item, &ast, ctx)?);
189        }
190
191        Ok(Value::Array(results))
192    }
193}
194
195// =============================================================================
196// filter_expr(expr, array) -> array
197// =============================================================================
198
199defn!(FilterExprFn, vec![arg!(expref), arg!(array)], None);
200
201impl Function for FilterExprFn {
202    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
203        self.signature.validate(args, ctx)?;
204
205        let ast = get_expref_ast(&args[0], ctx)
206            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
207            .clone();
208        let arr = args[1].as_array().unwrap();
209
210        let mut results = Vec::new();
211        for item in arr {
212            let result = interpret(item, &ast, ctx)?;
213            if result.is_truthy() {
214                results.push(item.clone());
215            }
216        }
217
218        Ok(Value::Array(results))
219    }
220}
221
222// =============================================================================
223// any_expr(expr, array) -> bool
224// =============================================================================
225
226defn!(AnyExprFn, vec![arg!(expref), arg!(array)], None);
227
228impl Function for AnyExprFn {
229    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
230        self.signature.validate(args, ctx)?;
231
232        let ast = get_expref_ast(&args[0], ctx)
233            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
234            .clone();
235        let arr = args[1].as_array().unwrap();
236
237        for item in arr {
238            let result = interpret(item, &ast, ctx)?;
239            if result.is_truthy() {
240                return Ok(Value::Bool(true));
241            }
242        }
243
244        Ok(Value::Bool(false))
245    }
246}
247
248// =============================================================================
249// none(expr, array) -> bool (opposite of any_expr/some)
250// =============================================================================
251
252defn!(NoneFn, vec![arg!(expref), arg!(array)], None);
253
254impl Function for NoneFn {
255    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
256        self.signature.validate(args, ctx)?;
257
258        let ast = get_expref_ast(&args[0], ctx)
259            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
260            .clone();
261        let arr = args[1].as_array().unwrap();
262
263        // Empty array returns true (vacuously, no elements satisfy the predicate)
264        if arr.is_empty() {
265            return Ok(Value::Bool(true));
266        }
267
268        for item in arr {
269            let result = interpret(item, &ast, ctx)?;
270            if result.is_truthy() {
271                return Ok(Value::Bool(false));
272            }
273        }
274
275        Ok(Value::Bool(true))
276    }
277}
278
279// =============================================================================
280// all_expr(expr, array) -> bool
281// =============================================================================
282
283defn!(AllExprFn, vec![arg!(expref), arg!(array)], None);
284
285impl Function for AllExprFn {
286    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
287        self.signature.validate(args, ctx)?;
288
289        let ast = get_expref_ast(&args[0], ctx)
290            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
291            .clone();
292        let arr = args[1].as_array().unwrap();
293
294        // Empty array returns true (vacuous truth)
295        if arr.is_empty() {
296            return Ok(Value::Bool(true));
297        }
298
299        for item in arr {
300            let result = interpret(item, &ast, ctx)?;
301            if !result.is_truthy() {
302                return Ok(Value::Bool(false));
303            }
304        }
305
306        Ok(Value::Bool(true))
307    }
308}
309
310// =============================================================================
311// find_expr(expr, array) -> element | null
312// =============================================================================
313
314defn!(FindExprFn, vec![arg!(expref), arg!(array)], None);
315
316impl Function for FindExprFn {
317    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
318        self.signature.validate(args, ctx)?;
319
320        let ast = get_expref_ast(&args[0], ctx)
321            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
322            .clone();
323        let arr = args[1].as_array().unwrap();
324
325        for item in arr {
326            let result = interpret(item, &ast, ctx)?;
327            if result.is_truthy() {
328                return Ok(item.clone());
329            }
330        }
331
332        Ok(Value::Null)
333    }
334}
335
336// =============================================================================
337// find_index_expr(expr, array) -> number | null
338// =============================================================================
339
340defn!(FindIndexExprFn, vec![arg!(expref), arg!(array)], None);
341
342impl Function for FindIndexExprFn {
343    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
344        self.signature.validate(args, ctx)?;
345
346        let ast = get_expref_ast(&args[0], ctx)
347            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
348            .clone();
349        let arr = args[1].as_array().unwrap();
350
351        for (i, item) in arr.iter().enumerate() {
352            let result = interpret(item, &ast, ctx)?;
353            if result.is_truthy() {
354                return Ok(number_value(i as f64));
355            }
356        }
357
358        Ok(number_value(-1.0))
359    }
360}
361
362// =============================================================================
363// count_expr(expr, array) -> number
364// =============================================================================
365
366defn!(CountExprFn, vec![arg!(expref), arg!(array)], None);
367
368impl Function for CountExprFn {
369    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
370        self.signature.validate(args, ctx)?;
371
372        let ast = get_expref_ast(&args[0], ctx)
373            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
374            .clone();
375        let arr = args[1].as_array().unwrap();
376
377        let mut count = 0i64;
378        for item in arr {
379            let result = interpret(item, &ast, ctx)?;
380            if result.is_truthy() {
381                count += 1;
382            }
383        }
384
385        Ok(Value::Number(Number::from(count)))
386    }
387}
388
389// =============================================================================
390// sort_by_expr(expr, array) -> array
391// =============================================================================
392
393defn!(SortByExprFn, vec![arg!(expref), arg!(array)], None);
394
395impl Function for SortByExprFn {
396    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
397        self.signature.validate(args, ctx)?;
398
399        let ast = get_expref_ast(&args[0], ctx)
400            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
401            .clone();
402        let arr = args[1].as_array().unwrap();
403
404        if arr.is_empty() {
405            return Ok(Value::Array(vec![]));
406        }
407
408        // Compute sort keys for each element
409        let mut keyed: Vec<(Value, Value)> = Vec::with_capacity(arr.len());
410        for item in arr {
411            let key = interpret(item, &ast, ctx)?;
412            keyed.push((item.clone(), key));
413        }
414
415        // Sort by key
416        keyed.sort_by(|a, b| compare_values(&a.1, &b.1));
417
418        let results: Vec<Value> = keyed.into_iter().map(|(item, _)| item).collect();
419        Ok(Value::Array(results))
420    }
421}
422
423// =============================================================================
424// group_by_expr(expr, array) -> object
425// =============================================================================
426
427defn!(GroupByExprFn, vec![arg!(expref), arg!(array)], None);
428
429impl Function for GroupByExprFn {
430    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
431        self.signature.validate(args, ctx)?;
432
433        let ast = get_expref_ast(&args[0], ctx)
434            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
435            .clone();
436        let arr = args[1].as_array().unwrap();
437
438        // Use an index-based approach to preserve insertion order
439        let mut group_keys: Vec<String> = Vec::new();
440        let mut group_map: std::collections::HashMap<String, Vec<Value>> =
441            std::collections::HashMap::new();
442
443        for item in arr {
444            let key_val = interpret(item, &ast, ctx)?;
445            let key = value_to_string(&key_val);
446            if !group_map.contains_key(&key) {
447                group_keys.push(key.clone());
448            }
449            group_map.entry(key).or_default().push(item.clone());
450        }
451
452        let mut result = Map::new();
453        for key in group_keys {
454            if let Some(items) = group_map.remove(&key) {
455                result.insert(key, Value::Array(items));
456            }
457        }
458
459        Ok(Value::Object(result))
460    }
461}
462
463// =============================================================================
464// count_by(expr, array) -> object
465// =============================================================================
466
467defn!(CountByFn, vec![arg!(expref), arg!(array)], None);
468
469impl Function for CountByFn {
470    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
471        self.signature.validate(args, ctx)?;
472
473        let ast = get_expref_ast(&args[0], ctx)
474            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
475            .clone();
476        let arr = args[1].as_array().unwrap();
477
478        let mut count_keys: Vec<String> = Vec::new();
479        let mut counts: std::collections::HashMap<String, i64> = std::collections::HashMap::new();
480
481        for item in arr {
482            let key_val = interpret(item, &ast, ctx)?;
483            let key = value_to_string(&key_val);
484            if !counts.contains_key(&key) {
485                count_keys.push(key.clone());
486            }
487            *counts.entry(key).or_insert(0) += 1;
488        }
489
490        let mut result = Map::new();
491        for key in count_keys {
492            if let Some(&count) = counts.get(&key) {
493                result.insert(key, Value::Number(Number::from(count)));
494            }
495        }
496
497        Ok(Value::Object(result))
498    }
499}
500
501// =============================================================================
502// partition_expr(expr, array) -> [matches, non_matches]
503// =============================================================================
504
505defn!(PartitionExprFn, vec![arg!(expref), arg!(array)], None);
506
507impl Function for PartitionExprFn {
508    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
509        self.signature.validate(args, ctx)?;
510
511        let ast = get_expref_ast(&args[0], ctx)
512            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
513            .clone();
514        let arr = args[1].as_array().unwrap();
515
516        let mut matches = Vec::new();
517        let mut non_matches = Vec::new();
518
519        for item in arr {
520            let result = interpret(item, &ast, ctx)?;
521            if result.is_truthy() {
522                matches.push(item.clone());
523            } else {
524                non_matches.push(item.clone());
525            }
526        }
527
528        Ok(Value::Array(vec![
529            Value::Array(matches),
530            Value::Array(non_matches),
531        ]))
532    }
533}
534
535// =============================================================================
536// min_by_expr(expr, array) -> element | null
537// =============================================================================
538
539defn!(MinByExprFn, vec![arg!(expref), arg!(array)], None);
540
541impl Function for MinByExprFn {
542    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
543        self.signature.validate(args, ctx)?;
544
545        let ast = get_expref_ast(&args[0], ctx)
546            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
547            .clone();
548        let arr = args[1].as_array().unwrap();
549
550        if arr.is_empty() {
551            return Ok(Value::Null);
552        }
553
554        let mut min_item = arr[0].clone();
555        let mut min_key = interpret(&arr[0], &ast, ctx)?;
556
557        for item in arr.iter().skip(1) {
558            let key = interpret(item, &ast, ctx)?;
559            if compare_values(&key, &min_key) == std::cmp::Ordering::Less {
560                min_item = item.clone();
561                min_key = key;
562            }
563        }
564
565        Ok(min_item)
566    }
567}
568
569// =============================================================================
570// max_by_expr(expr, array) -> element | null
571// =============================================================================
572
573defn!(MaxByExprFn, vec![arg!(expref), arg!(array)], None);
574
575impl Function for MaxByExprFn {
576    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
577        self.signature.validate(args, ctx)?;
578
579        let ast = get_expref_ast(&args[0], ctx)
580            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
581            .clone();
582        let arr = args[1].as_array().unwrap();
583
584        if arr.is_empty() {
585            return Ok(Value::Null);
586        }
587
588        let mut max_item = arr[0].clone();
589        let mut max_key = interpret(&arr[0], &ast, ctx)?;
590
591        for item in arr.iter().skip(1) {
592            let key = interpret(item, &ast, ctx)?;
593            if compare_values(&key, &max_key) == std::cmp::Ordering::Greater {
594                max_item = item.clone();
595                max_key = key;
596            }
597        }
598
599        Ok(max_item)
600    }
601}
602
603// =============================================================================
604// unique_by_expr(expr, array) -> array
605// =============================================================================
606
607defn!(UniqueByExprFn, vec![arg!(expref), arg!(array)], None);
608
609impl Function for UniqueByExprFn {
610    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
611        self.signature.validate(args, ctx)?;
612
613        let ast = get_expref_ast(&args[0], ctx)
614            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
615            .clone();
616        let arr = args[1].as_array().unwrap();
617
618        let mut seen: HashSet<String> = HashSet::new();
619        let mut results = Vec::new();
620
621        for item in arr {
622            let key_val = interpret(item, &ast, ctx)?;
623            let key = value_to_string(&key_val);
624            if seen.insert(key) {
625                results.push(item.clone());
626            }
627        }
628
629        Ok(Value::Array(results))
630    }
631}
632
633// =============================================================================
634// flat_map_expr(expr, array) -> array
635// =============================================================================
636
637defn!(FlatMapExprFn, vec![arg!(expref), arg!(array)], None);
638
639impl Function for FlatMapExprFn {
640    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
641        self.signature.validate(args, ctx)?;
642
643        let ast = get_expref_ast(&args[0], ctx)
644            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
645            .clone();
646        let arr = args[1].as_array().unwrap();
647
648        let mut results = Vec::new();
649        for item in arr {
650            let result = interpret(item, &ast, ctx)?;
651            match result {
652                Value::Array(inner) => {
653                    results.extend(inner);
654                }
655                Value::Null => {
656                    // Skip nulls
657                }
658                _ => {
659                    results.push(result);
660                }
661            }
662        }
663
664        Ok(Value::Array(results))
665    }
666}
667
668// =============================================================================
669// reject(expr, array) -> array (inverse of filter_expr)
670// =============================================================================
671
672defn!(RejectFn, vec![arg!(expref), arg!(array)], None);
673
674impl Function for RejectFn {
675    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
676        self.signature.validate(args, ctx)?;
677
678        let ast = get_expref_ast(&args[0], ctx)
679            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
680            .clone();
681        let arr = args[1].as_array().unwrap();
682
683        let mut results = Vec::new();
684        for item in arr {
685            let result = interpret(item, &ast, ctx)?;
686            // Keep items where expression is falsy (inverse of filter)
687            if !result.is_truthy() {
688                results.push(item.clone());
689            }
690        }
691
692        Ok(Value::Array(results))
693    }
694}
695
696// =============================================================================
697// map_keys(expr, object) -> object
698// =============================================================================
699
700defn!(MapKeysFn, vec![arg!(expref), arg!(object)], None);
701
702impl Function for MapKeysFn {
703    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
704        self.signature.validate(args, ctx)?;
705
706        let ast = get_expref_ast(&args[0], ctx)
707            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
708            .clone();
709        let obj = args[1].as_object().unwrap();
710
711        let mut result = Map::new();
712        for (key, value) in obj.iter() {
713            // Apply expression to the key
714            let key_val = Value::String(key.clone());
715            let new_key_val = interpret(&key_val, &ast, ctx)?;
716
717            let new_key_str = match &new_key_val {
718                Value::String(s) => s.clone(),
719                Value::Number(n) => n.to_string(),
720                _ => key.clone(), // Keep original if result is not a string/number
721            };
722
723            result.insert(new_key_str, value.clone());
724        }
725
726        Ok(Value::Object(result))
727    }
728}
729
730// =============================================================================
731// map_values(expr, object) -> object
732// =============================================================================
733
734defn!(MapValuesFn, vec![arg!(expref), arg!(object)], None);
735
736impl Function for MapValuesFn {
737    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
738        self.signature.validate(args, ctx)?;
739
740        let ast = get_expref_ast(&args[0], ctx)
741            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
742            .clone();
743        let obj = args[1].as_object().unwrap();
744
745        let mut result = Map::new();
746        for (key, value) in obj.iter() {
747            let new_value = interpret(value, &ast, ctx)?;
748            result.insert(key.clone(), new_value);
749        }
750
751        Ok(Value::Object(result))
752    }
753}
754
755// =============================================================================
756// order_by(array, criteria) -> array
757// =============================================================================
758
759defn!(OrderByFn, vec![arg!(array), arg!(array)], None);
760
761impl Function for OrderByFn {
762    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
763        self.signature.validate(args, ctx)?;
764
765        let arr = args[0].as_array().unwrap();
766        let criteria = args[1].as_array().unwrap();
767
768        if arr.is_empty() {
769            return Ok(Value::Array(vec![]));
770        }
771
772        // Parse criteria: each element should be [field, direction]
773        let mut sort_specs: Vec<(String, bool)> = Vec::new(); // (field, ascending)
774        for criterion in criteria {
775            let crit_arr = criterion.as_array().ok_or_else(|| {
776                custom_error(ctx, "Each criterion must be an array [field, direction]")
777            })?;
778
779            if crit_arr.len() < 2 {
780                return Err(custom_error(
781                    ctx,
782                    "Each criterion must have [field, direction]",
783                ));
784            }
785
786            let field = crit_arr[0]
787                .as_str()
788                .ok_or_else(|| custom_error(ctx, "Field name must be a string"))?;
789
790            let direction = crit_arr[1]
791                .as_str()
792                .ok_or_else(|| custom_error(ctx, "Direction must be 'asc' or 'desc'"))?;
793
794            let ascending = match direction.to_lowercase().as_str() {
795                "asc" | "ascending" => true,
796                "desc" | "descending" => false,
797                _ => {
798                    return Err(custom_error(ctx, "Direction must be 'asc' or 'desc'"));
799                }
800            };
801
802            sort_specs.push((field.to_string(), ascending));
803        }
804
805        // Clone and sort the array
806        let mut result: Vec<Value> = arr.clone();
807        result.sort_by(|a, b| {
808            for (field, ascending) in &sort_specs {
809                let a_val = a
810                    .as_object()
811                    .and_then(|o| o.get(field.as_str()))
812                    .unwrap_or(&Value::Null);
813                let b_val = b
814                    .as_object()
815                    .and_then(|o| o.get(field.as_str()))
816                    .unwrap_or(&Value::Null);
817
818                let cmp = compare_values(a_val, b_val);
819                if cmp != std::cmp::Ordering::Equal {
820                    return if *ascending { cmp } else { cmp.reverse() };
821                }
822            }
823            std::cmp::Ordering::Equal
824        });
825
826        Ok(Value::Array(result))
827    }
828}
829
830// =============================================================================
831// reduce_expr(expr, array, initial) -> any
832// =============================================================================
833
834defn!(
835    ReduceExprFn,
836    vec![arg!(expref), arg!(array), arg!(any)],
837    None
838);
839
840impl Function for ReduceExprFn {
841    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
842        self.signature.validate(args, ctx)?;
843
844        let ast = get_expref_ast(&args[0], ctx)
845            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
846            .clone();
847        let arr = args[1].as_array().unwrap();
848        let initial = args[2].clone();
849
850        if arr.is_empty() {
851            return Ok(initial);
852        }
853
854        let mut accumulator = initial;
855
856        for (idx, item) in arr.iter().enumerate() {
857            // Create context object with accumulator, current, and index
858            let mut context_map = Map::new();
859            context_map.insert("accumulator".to_string(), accumulator.clone());
860            context_map.insert("current".to_string(), item.clone());
861            context_map.insert("index".to_string(), Value::Number(Number::from(idx as i64)));
862            let context_val = Value::Object(context_map);
863
864            accumulator = interpret(&context_val, &ast, ctx)?;
865        }
866
867        Ok(accumulator)
868    }
869}
870
871// =============================================================================
872// scan_expr(expr, array, initial) -> array
873// =============================================================================
874
875defn!(ScanExprFn, vec![arg!(expref), arg!(array), arg!(any)], None);
876
877impl Function for ScanExprFn {
878    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
879        self.signature.validate(args, ctx)?;
880
881        let ast = get_expref_ast(&args[0], ctx)
882            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
883            .clone();
884        let arr = args[1].as_array().unwrap();
885        let initial = args[2].clone();
886
887        if arr.is_empty() {
888            return Ok(Value::Array(vec![]));
889        }
890
891        let mut accumulator = initial;
892        let mut results: Vec<Value> = Vec::with_capacity(arr.len());
893
894        for (idx, item) in arr.iter().enumerate() {
895            // Create context object with accumulator, current, and index
896            let mut context_map = Map::new();
897            context_map.insert("accumulator".to_string(), accumulator.clone());
898            context_map.insert("current".to_string(), item.clone());
899            context_map.insert("index".to_string(), Value::Number(Number::from(idx as i64)));
900            let context_val = Value::Object(context_map);
901
902            accumulator = interpret(&context_val, &ast, ctx)?;
903            results.push(accumulator.clone());
904        }
905
906        Ok(Value::Array(results))
907    }
908}
909
910// =============================================================================
911// partial(fn_name, ...args) -> partial object
912// =============================================================================
913
914defn!(PartialFn, vec![arg!(string)], Some(arg!(any)));
915
916impl Function for PartialFn {
917    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
918        self.signature.validate(args, ctx)?;
919
920        let fn_name = args[0].as_str().ok_or_else(|| {
921            custom_error(
922                ctx,
923                "partial() first argument must be a function name string",
924            )
925        })?;
926
927        // Collect the pre-filled arguments
928        let prefilled_args: Vec<Value> = args[1..].to_vec();
929
930        // Create the partial object
931        let mut partial_obj = Map::new();
932        partial_obj.insert("__partial__".to_string(), Value::Bool(true));
933        partial_obj.insert("fn".to_string(), Value::String(fn_name.to_string()));
934        partial_obj.insert("args".to_string(), Value::Array(prefilled_args));
935
936        Ok(Value::Object(partial_obj))
937    }
938}
939
940// =============================================================================
941// apply(partial_or_fn, ...args) -> result
942// =============================================================================
943
944defn!(ApplyFn, vec![arg!(any)], Some(arg!(any)));
945
946impl Function for ApplyFn {
947    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
948        self.signature.validate(args, ctx)?;
949
950        let first_arg = &args[0];
951        let additional_args = &args[1..];
952
953        // Check if it's a partial object
954        if let Some(obj) = first_arg.as_object()
955            && obj.get("__partial__").and_then(|v| v.as_bool()) == Some(true)
956        {
957            // It's a partial - extract fn name and pre-filled args
958            let fn_name = obj
959                .get("fn")
960                .and_then(|v| v.as_str())
961                .ok_or_else(|| custom_error(ctx, "Invalid partial object: missing 'fn' field"))?;
962
963            let prefilled = obj
964                .get("args")
965                .and_then(|v| v.as_array())
966                .ok_or_else(|| custom_error(ctx, "Invalid partial object: missing 'args' field"))?;
967
968            return invoke_function(fn_name, prefilled, additional_args, ctx);
969        }
970
971        // If it's a string, treat as function name
972        if let Some(fn_name) = first_arg.as_str() {
973            return invoke_function(fn_name, &[], additional_args, ctx);
974        }
975
976        Err(custom_error(
977            ctx,
978            "apply() first argument must be a partial object or function name string",
979        ))
980    }
981}
982
983/// Helper to invoke a function by name with pre-filled and additional arguments.
984fn invoke_function(
985    fn_name: &str,
986    prefilled: &[Value],
987    additional: &[Value],
988    ctx: &mut Context<'_>,
989) -> SearchResult {
990    // Build the argument list for the expression
991    let mut all_args_json: Vec<String> = Vec::new();
992
993    // Add pre-filled args as literals
994    for a in prefilled {
995        all_args_json.push(format!("`{}`", serde_json::to_string(a).unwrap()));
996    }
997
998    // Add additional args as literals
999    for a in additional {
1000        all_args_json.push(format!("`{}`", serde_json::to_string(a).unwrap()));
1001    }
1002
1003    // Build and execute the expression
1004    let expr_str = format!("{}({})", fn_name, all_args_json.join(", "));
1005
1006    let compiled = ctx.runtime.compile(&expr_str).map_err(|_| {
1007        custom_error(
1008            ctx,
1009            &format!("Failed to compile function call '{}'", expr_str),
1010        )
1011    })?;
1012
1013    compiled
1014        .search(&Value::Null)
1015        .map_err(|_| custom_error(ctx, &format!("Failed to execute '{}'", fn_name)))
1016}
1017
1018// =============================================================================
1019// take_while(expr, array) -> array
1020// =============================================================================
1021
1022defn!(TakeWhileFn, vec![arg!(expref), arg!(array)], None);
1023
1024impl Function for TakeWhileFn {
1025    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1026        self.signature.validate(args, ctx)?;
1027
1028        let ast = get_expref_ast(&args[0], ctx)
1029            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
1030            .clone();
1031        let arr = args[1].as_array().unwrap();
1032
1033        let mut results = Vec::new();
1034        for item in arr {
1035            let result = interpret(item, &ast, ctx)?;
1036            if result.is_truthy() {
1037                results.push(item.clone());
1038            } else {
1039                break;
1040            }
1041        }
1042
1043        Ok(Value::Array(results))
1044    }
1045}
1046
1047// =============================================================================
1048// drop_while(expr, array) -> array
1049// =============================================================================
1050
1051defn!(DropWhileFn, vec![arg!(expref), arg!(array)], None);
1052
1053impl Function for DropWhileFn {
1054    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1055        self.signature.validate(args, ctx)?;
1056
1057        let ast = get_expref_ast(&args[0], ctx)
1058            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
1059            .clone();
1060        let arr = args[1].as_array().unwrap();
1061
1062        let mut dropping = true;
1063        let mut results = Vec::new();
1064        for item in arr {
1065            if dropping {
1066                let result = interpret(item, &ast, ctx)?;
1067                if !result.is_truthy() {
1068                    dropping = false;
1069                    results.push(item.clone());
1070                }
1071            } else {
1072                results.push(item.clone());
1073            }
1074        }
1075
1076        Ok(Value::Array(results))
1077    }
1078}
1079
1080// =============================================================================
1081// zip_with(expr, array1, array2) -> array
1082// =============================================================================
1083
1084defn!(
1085    ZipWithFn,
1086    vec![arg!(expref), arg!(array), arg!(array)],
1087    None
1088);
1089
1090impl Function for ZipWithFn {
1091    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1092        self.signature.validate(args, ctx)?;
1093
1094        let ast = get_expref_ast(&args[0], ctx)
1095            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
1096            .clone();
1097        let arr1 = args[1].as_array().unwrap();
1098        let arr2 = args[2].as_array().unwrap();
1099
1100        let min_len = arr1.len().min(arr2.len());
1101        let mut results = Vec::with_capacity(min_len);
1102
1103        for i in 0..min_len {
1104            // Create a pair array [element1, element2] as input to the expression
1105            let pair = Value::Array(vec![arr1[i].clone(), arr2[i].clone()]);
1106            let result = interpret(&pair, &ast, ctx)?;
1107            results.push(result);
1108        }
1109
1110        Ok(Value::Array(results))
1111    }
1112}
1113
1114// =============================================================================
1115// walk(expr, value) -> value (recursive transformation)
1116// =============================================================================
1117
1118defn!(WalkFn, vec![arg!(expref), arg!(any)], None);
1119
1120/// Recursively walk a value, applying the expression bottom-up.
1121fn walk_value(value: &Value, ast: &Ast, ctx: &mut Context<'_>) -> SearchResult {
1122    match value {
1123        Value::Array(arr) => {
1124            // First, recursively walk all elements
1125            let walked_elements: Result<Vec<Value>, _> =
1126                arr.iter().map(|elem| walk_value(elem, ast, ctx)).collect();
1127            let new_array = Value::Array(walked_elements?);
1128            // Then apply the expression to the array itself
1129            interpret(&new_array, ast, ctx)
1130        }
1131        Value::Object(obj) => {
1132            // First, recursively walk all values
1133            let mut walked_obj = Map::new();
1134            for (k, v) in obj.iter() {
1135                walked_obj.insert(k.clone(), walk_value(v, ast, ctx)?);
1136            }
1137            let new_object = Value::Object(walked_obj);
1138            // Then apply the expression to the object itself
1139            interpret(&new_object, ast, ctx)
1140        }
1141        // For scalars (string, number, bool, null), just apply the expression
1142        _ => interpret(value, ast, ctx),
1143    }
1144}
1145
1146impl Function for WalkFn {
1147    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1148        self.signature.validate(args, ctx)?;
1149
1150        let ast = get_expref_ast(&args[0], ctx)
1151            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
1152            .clone();
1153
1154        walk_value(&args[1], &ast, ctx)
1155    }
1156}
1157
1158// =============================================================================
1159// recurse(value) -> array (collect all nested values, jq parity)
1160// =============================================================================
1161
1162defn!(RecurseFn, vec![arg!(any)], None);
1163
1164/// Helper to collect all values recursively.
1165fn collect_recursive(value: &Value, results: &mut Vec<Value>) {
1166    results.push(value.clone());
1167    match value {
1168        Value::Array(arr) => {
1169            for elem in arr {
1170                collect_recursive(elem, results);
1171            }
1172        }
1173        Value::Object(obj) => {
1174            for (_, v) in obj.iter() {
1175                collect_recursive(v, results);
1176            }
1177        }
1178        _ => {}
1179    }
1180}
1181
1182impl Function for RecurseFn {
1183    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1184        self.signature.validate(args, ctx)?;
1185
1186        let mut results = Vec::new();
1187        collect_recursive(&args[0], &mut results);
1188
1189        Ok(Value::Array(results))
1190    }
1191}
1192
1193// =============================================================================
1194// recurse_with(value, expr) -> array (recursive descent with filter, jq parity)
1195// =============================================================================
1196
1197defn!(RecurseWithFn, vec![arg!(any), arg!(expref)], None);
1198
1199impl Function for RecurseWithFn {
1200    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1201        self.signature.validate(args, ctx)?;
1202
1203        let ast = get_expref_ast(&args[1], ctx)
1204            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
1205            .clone();
1206
1207        let mut results = Vec::new();
1208        let mut queue = vec![args[0].clone()];
1209        let max_iterations = 10000; // Safety limit
1210        let mut iterations = 0;
1211
1212        while let Some(current) = queue.pop() {
1213            if iterations >= max_iterations {
1214                return Err(custom_error(
1215                    ctx,
1216                    "recurse_with exceeded maximum iterations",
1217                ));
1218            }
1219            iterations += 1;
1220
1221            // Skip null values
1222            if current.is_null() {
1223                continue;
1224            }
1225
1226            results.push(current.clone());
1227
1228            // Apply expression to get next values
1229            let next = interpret(&current, &ast, ctx)?;
1230
1231            match next {
1232                Value::Null => {}
1233                Value::Array(arr) => {
1234                    // Add non-null elements to queue (in reverse to maintain order)
1235                    for elem in arr.into_iter().rev() {
1236                        if !elem.is_null() {
1237                            queue.push(elem);
1238                        }
1239                    }
1240                }
1241                _ => {
1242                    queue.push(next);
1243                }
1244            }
1245        }
1246
1247        Ok(Value::Array(results))
1248    }
1249}
1250
1251// =============================================================================
1252// while_expr(init, condition, update) -> value
1253// =============================================================================
1254
1255defn!(
1256    WhileExprFn,
1257    vec![arg!(any), arg!(expref), arg!(expref)],
1258    None
1259);
1260
1261impl Function for WhileExprFn {
1262    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1263        self.signature.validate(args, ctx)?;
1264
1265        let cond_ast = get_expref_ast(&args[1], ctx)
1266            .ok_or_else(|| custom_error(ctx, "Expected expref for condition"))?
1267            .clone();
1268        let update_ast = get_expref_ast(&args[2], ctx)
1269            .ok_or_else(|| custom_error(ctx, "Expected expref for update"))?
1270            .clone();
1271
1272        let mut current = args[0].clone();
1273        let max_iterations = 100000; // Safety limit
1274        let mut iterations = 0;
1275
1276        loop {
1277            if iterations >= max_iterations {
1278                return Err(custom_error(ctx, "while_expr exceeded maximum iterations"));
1279            }
1280            iterations += 1;
1281
1282            // Check condition
1283            let cond_result = interpret(&current, &cond_ast, ctx)?;
1284            if !cond_result.is_truthy() {
1285                break;
1286            }
1287
1288            // Apply update
1289            current = interpret(&current, &update_ast, ctx)?;
1290        }
1291
1292        Ok(current)
1293    }
1294}
1295
1296// =============================================================================
1297// until_expr(init, condition, update) -> value
1298// =============================================================================
1299
1300defn!(
1301    UntilExprFn,
1302    vec![arg!(any), arg!(expref), arg!(expref)],
1303    None
1304);
1305
1306impl Function for UntilExprFn {
1307    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1308        self.signature.validate(args, ctx)?;
1309
1310        let cond_ast = get_expref_ast(&args[1], ctx)
1311            .ok_or_else(|| custom_error(ctx, "Expected expref for condition"))?
1312            .clone();
1313        let update_ast = get_expref_ast(&args[2], ctx)
1314            .ok_or_else(|| custom_error(ctx, "Expected expref for update"))?
1315            .clone();
1316
1317        let mut current = args[0].clone();
1318        let max_iterations = 100000; // Safety limit
1319        let mut iterations = 0;
1320
1321        loop {
1322            if iterations >= max_iterations {
1323                return Err(custom_error(ctx, "until_expr exceeded maximum iterations"));
1324            }
1325            iterations += 1;
1326
1327            // Check condition (stop when true, opposite of while)
1328            let cond_result = interpret(&current, &cond_ast, ctx)?;
1329            if cond_result.is_truthy() {
1330                break;
1331            }
1332
1333            // Apply update
1334            current = interpret(&current, &update_ast, ctx)?;
1335        }
1336
1337        Ok(current)
1338    }
1339}
1340
1341// =============================================================================
1342// rank(expref, array) -> array
1343// =============================================================================
1344
1345defn!(RankFn, vec![arg!(expref), arg!(array)], None);
1346
1347impl Function for RankFn {
1348    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1349        self.signature.validate(args, ctx)?;
1350        let ast = get_expref_ast(&args[0], ctx)
1351            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
1352            .clone();
1353        let arr = args[1].as_array().unwrap();
1354
1355        // Extract sort keys
1356        let mut keyed: Vec<(usize, Value)> = Vec::with_capacity(arr.len());
1357        for (i, item) in arr.iter().enumerate() {
1358            let key = interpret(item, &ast, ctx)?;
1359            keyed.push((i, key));
1360        }
1361
1362        // Sort by key descending (highest rank = 1)
1363        keyed.sort_by(|a, b| compare_values(&b.1, &a.1));
1364
1365        let mut ranks = vec![0usize; arr.len()];
1366        let mut current_rank = 1;
1367        for i in 0..keyed.len() {
1368            if i > 0 && compare_values(&keyed[i].1, &keyed[i - 1].1) != std::cmp::Ordering::Equal {
1369                current_rank = i + 1;
1370            }
1371            ranks[keyed[i].0] = current_rank;
1372        }
1373
1374        Ok(Value::Array(
1375            ranks
1376                .into_iter()
1377                .map(|r| Value::Number(Number::from(r)))
1378                .collect(),
1379        ))
1380    }
1381}
1382
1383// =============================================================================
1384// pivot(array, key_expr, value_expr) -> object
1385// =============================================================================
1386
1387defn!(PivotFn, vec![arg!(array), arg!(expref), arg!(expref)], None);
1388
1389impl Function for PivotFn {
1390    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1391        self.signature.validate(args, ctx)?;
1392
1393        let arr = args[0].as_array().unwrap();
1394        let key_ast = get_expref_ast(&args[1], ctx)
1395            .ok_or_else(|| custom_error(ctx, "Expected expref for key"))?
1396            .clone();
1397        let val_ast = get_expref_ast(&args[2], ctx)
1398            .ok_or_else(|| custom_error(ctx, "Expected expref for value"))?
1399            .clone();
1400
1401        let mut result = Map::new();
1402        for item in arr {
1403            let key = interpret(item, &key_ast, ctx)?;
1404            let key_str = match &key {
1405                Value::String(s) => s.clone(),
1406                _ => serde_json::to_string(&key).unwrap_or_default(),
1407            };
1408            let val = interpret(item, &val_ast, ctx)?;
1409            result.insert(key_str, val);
1410        }
1411
1412        Ok(Value::Object(result))
1413    }
1414}
1415
1416// =============================================================================
1417// dense_rank(expref, array) -> array
1418// =============================================================================
1419
1420defn!(DenseRankFn, vec![arg!(expref), arg!(array)], None);
1421
1422impl Function for DenseRankFn {
1423    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1424        self.signature.validate(args, ctx)?;
1425        let ast = get_expref_ast(&args[0], ctx)
1426            .ok_or_else(|| custom_error(ctx, "Expected expref"))?
1427            .clone();
1428        let arr = args[1].as_array().unwrap();
1429
1430        let mut keyed: Vec<(usize, Value)> = Vec::with_capacity(arr.len());
1431        for (i, item) in arr.iter().enumerate() {
1432            let key = interpret(item, &ast, ctx)?;
1433            keyed.push((i, key));
1434        }
1435
1436        keyed.sort_by(|a, b| compare_values(&b.1, &a.1));
1437
1438        let mut ranks = vec![0usize; arr.len()];
1439        let mut current_rank = 1;
1440        for i in 0..keyed.len() {
1441            if i > 0 && compare_values(&keyed[i].1, &keyed[i - 1].1) != std::cmp::Ordering::Equal {
1442                current_rank += 1;
1443            }
1444            ranks[keyed[i].0] = current_rank;
1445        }
1446
1447        Ok(Value::Array(
1448            ranks
1449                .into_iter()
1450                .map(|r| Value::Number(Number::from(r)))
1451                .collect(),
1452        ))
1453    }
1454}
1455
1456// =============================================================================
1457// unpivot(object) -> array
1458// =============================================================================
1459
1460defn!(UnpivotFn, vec![arg!(object)], None);
1461
1462impl Function for UnpivotFn {
1463    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1464        self.signature.validate(args, ctx)?;
1465
1466        let obj = args[0].as_object().unwrap();
1467        let result: Vec<Value> = obj
1468            .iter()
1469            .map(|(k, v)| {
1470                let mut map = Map::new();
1471                map.insert("key".to_string(), Value::String(k.clone()));
1472                map.insert("value".to_string(), v.clone());
1473                Value::Object(map)
1474            })
1475            .collect();
1476
1477        Ok(Value::Array(result))
1478    }
1479}
1480
1481#[cfg(test)]
1482mod tests {
1483    use crate::Runtime;
1484    use serde_json::json;
1485
1486    fn setup_runtime() -> Runtime {
1487        Runtime::builder()
1488            .with_standard()
1489            .with_all_extensions()
1490            .build()
1491    }
1492
1493    #[test]
1494    fn test_map_expr_field() {
1495        let runtime = setup_runtime();
1496        let data = json!([{"name": "Alice"}, {"name": "Bob"}]);
1497        let expr = runtime.compile("map_expr(&name, @)").unwrap();
1498        let result = expr.search(&data).unwrap();
1499        let arr = result.as_array().unwrap();
1500        assert_eq!(arr.len(), 2);
1501        assert_eq!(arr[0].as_str().unwrap(), "Alice");
1502        assert_eq!(arr[1].as_str().unwrap(), "Bob");
1503    }
1504
1505    #[test]
1506    fn test_map_expr_transform() {
1507        let runtime = setup_runtime();
1508        let data = json!(["hello", "world"]);
1509        let expr = runtime.compile("map_expr(&length(@), @)").unwrap();
1510        let result = expr.search(&data).unwrap();
1511        let arr = result.as_array().unwrap();
1512        assert_eq!(arr[0].as_f64().unwrap(), 5.0);
1513        assert_eq!(arr[1].as_f64().unwrap(), 5.0);
1514    }
1515
1516    #[test]
1517    fn test_filter_expr() {
1518        let runtime = setup_runtime();
1519        let data = json!([{"age": 25}, {"age": 17}, {"age": 30}]);
1520        let expr = runtime.compile("filter_expr(&(age >= `18`), @)").unwrap();
1521        let result = expr.search(&data).unwrap();
1522        let arr = result.as_array().unwrap();
1523        assert_eq!(arr.len(), 2);
1524    }
1525
1526    #[test]
1527    fn test_filter_expr_empty() {
1528        let runtime = setup_runtime();
1529        let data = json!([1, 2, 3]);
1530        let expr = runtime.compile("filter_expr(&(@ > `10`), @)").unwrap();
1531        let result = expr.search(&data).unwrap();
1532        let arr = result.as_array().unwrap();
1533        assert_eq!(arr.len(), 0);
1534    }
1535
1536    #[test]
1537    fn test_any_expr_true() {
1538        let runtime = setup_runtime();
1539        let data = json!([{"active": false}, {"active": true}]);
1540        let expr = runtime.compile("any_expr(&active, @)").unwrap();
1541        let result = expr.search(&data).unwrap();
1542        assert!(result.as_bool().unwrap());
1543    }
1544
1545    #[test]
1546    fn test_any_expr_false() {
1547        let runtime = setup_runtime();
1548        let data = json!([{"active": false}, {"active": false}]);
1549        let expr = runtime.compile("any_expr(&active, @)").unwrap();
1550        let result = expr.search(&data).unwrap();
1551        assert!(!result.as_bool().unwrap());
1552    }
1553
1554    #[test]
1555    fn test_all_expr_true() {
1556        let runtime = setup_runtime();
1557        let data = json!([{"active": true}, {"active": true}]);
1558        let expr = runtime.compile("all_expr(&active, @)").unwrap();
1559        let result = expr.search(&data).unwrap();
1560        assert!(result.as_bool().unwrap());
1561    }
1562
1563    #[test]
1564    fn test_all_expr_false() {
1565        let runtime = setup_runtime();
1566        let data = json!([{"active": true}, {"active": false}]);
1567        let expr = runtime.compile("all_expr(&active, @)").unwrap();
1568        let result = expr.search(&data).unwrap();
1569        assert!(!result.as_bool().unwrap());
1570    }
1571
1572    #[test]
1573    fn test_all_expr_empty() {
1574        let runtime = setup_runtime();
1575        let data = json!([]);
1576        let expr = runtime.compile("all_expr(&active, @)").unwrap();
1577        let result = expr.search(&data).unwrap();
1578        assert!(result.as_bool().unwrap()); // vacuous truth
1579    }
1580
1581    #[test]
1582    fn test_find_expr_found() {
1583        let runtime = setup_runtime();
1584        let data = json!([{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]);
1585        let expr = runtime.compile("find_expr(&(id == `2`), @)").unwrap();
1586        let result = expr.search(&data).unwrap();
1587        let obj = result.as_object().unwrap();
1588        assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "Bob");
1589    }
1590
1591    #[test]
1592    fn test_find_expr_not_found() {
1593        let runtime = setup_runtime();
1594        let data = json!([{"id": 1}, {"id": 2}]);
1595        let expr = runtime.compile("find_expr(&(id == `99`), @)").unwrap();
1596        let result = expr.search(&data).unwrap();
1597        assert!(result.is_null());
1598    }
1599
1600    #[test]
1601    fn test_sort_by_expr_numbers() {
1602        let runtime = setup_runtime();
1603        let data = json!([{"val": 3}, {"val": 1}, {"val": 2}]);
1604        let expr = runtime.compile("sort_by_expr(&val, @)").unwrap();
1605        let result = expr.search(&data).unwrap();
1606        let arr = result.as_array().unwrap();
1607        assert_eq!(
1608            arr[0]
1609                .as_object()
1610                .unwrap()
1611                .get("val")
1612                .unwrap()
1613                .as_f64()
1614                .unwrap(),
1615            1.0
1616        );
1617        assert_eq!(
1618            arr[1]
1619                .as_object()
1620                .unwrap()
1621                .get("val")
1622                .unwrap()
1623                .as_f64()
1624                .unwrap(),
1625            2.0
1626        );
1627        assert_eq!(
1628            arr[2]
1629                .as_object()
1630                .unwrap()
1631                .get("val")
1632                .unwrap()
1633                .as_f64()
1634                .unwrap(),
1635            3.0
1636        );
1637    }
1638
1639    #[test]
1640    fn test_sort_by_expr_strings() {
1641        let runtime = setup_runtime();
1642        let data = json!([{"name": "Charlie"}, {"name": "Alice"}, {"name": "Bob"}]);
1643        let expr = runtime.compile("sort_by_expr(&name, @)").unwrap();
1644        let result = expr.search(&data).unwrap();
1645        let arr = result.as_array().unwrap();
1646        assert_eq!(
1647            arr[0]
1648                .as_object()
1649                .unwrap()
1650                .get("name")
1651                .unwrap()
1652                .as_str()
1653                .unwrap(),
1654            "Alice"
1655        );
1656        assert_eq!(
1657            arr[1]
1658                .as_object()
1659                .unwrap()
1660                .get("name")
1661                .unwrap()
1662                .as_str()
1663                .unwrap(),
1664            "Bob"
1665        );
1666        assert_eq!(
1667            arr[2]
1668                .as_object()
1669                .unwrap()
1670                .get("name")
1671                .unwrap()
1672                .as_str()
1673                .unwrap(),
1674            "Charlie"
1675        );
1676    }
1677
1678    #[test]
1679    fn test_find_index_expr_found() {
1680        let runtime = setup_runtime();
1681        let data = json!([{"id": 1}, {"id": 2}, {"id": 3}]);
1682        let expr = runtime.compile("find_index_expr(&(id == `2`), @)").unwrap();
1683        let result = expr.search(&data).unwrap();
1684        assert_eq!(result.as_f64().unwrap(), 1.0);
1685    }
1686
1687    #[test]
1688    fn test_find_index_expr_not_found() {
1689        let runtime = setup_runtime();
1690        let data = json!([{"id": 1}, {"id": 2}]);
1691        let expr = runtime
1692            .compile("find_index_expr(&(id == `99`), @)")
1693            .unwrap();
1694        let result = expr.search(&data).unwrap();
1695        assert_eq!(result.as_f64().unwrap(), -1.0);
1696    }
1697
1698    #[test]
1699    fn test_count_expr() {
1700        let runtime = setup_runtime();
1701        let data = json!([{"active": true}, {"active": false}, {"active": true}]);
1702        let expr = runtime.compile("count_expr(&active, @)").unwrap();
1703        let result = expr.search(&data).unwrap();
1704        assert_eq!(result.as_f64().unwrap(), 2.0);
1705    }
1706
1707    #[test]
1708    fn test_count_expr_none() {
1709        let runtime = setup_runtime();
1710        let data = json!([1, 2, 3]);
1711        let expr = runtime.compile("count_expr(&(@ > `10`), @)").unwrap();
1712        let result = expr.search(&data).unwrap();
1713        assert_eq!(result.as_f64().unwrap(), 0.0);
1714    }
1715
1716    #[test]
1717    fn test_group_by_expr() {
1718        let runtime = setup_runtime();
1719        let data = json!([
1720            {"type": "a", "val": 1},
1721            {"type": "b", "val": 2},
1722            {"type": "a", "val": 3}
1723        ]);
1724        let expr = runtime.compile("group_by_expr(&type, @)").unwrap();
1725        let result = expr.search(&data).unwrap();
1726        let obj = result.as_object().unwrap();
1727        assert_eq!(obj.get("a").unwrap().as_array().unwrap().len(), 2);
1728        assert_eq!(obj.get("b").unwrap().as_array().unwrap().len(), 1);
1729    }
1730
1731    #[test]
1732    fn test_partition_expr() {
1733        let runtime = setup_runtime();
1734        let data = json!([1, 2, 3, 4, 5]);
1735        let expr = runtime.compile("partition_expr(&(@ > `3`), @)").unwrap();
1736        let result = expr.search(&data).unwrap();
1737        let arr = result.as_array().unwrap();
1738        let matches = arr[0].as_array().unwrap();
1739        let non_matches = arr[1].as_array().unwrap();
1740        assert_eq!(matches.len(), 2); // 4, 5
1741        assert_eq!(non_matches.len(), 3); // 1, 2, 3
1742    }
1743
1744    #[test]
1745    fn test_min_by_expr() {
1746        let runtime = setup_runtime();
1747        let data = json!([{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]);
1748        let expr = runtime.compile("min_by_expr(&age, @)").unwrap();
1749        let result = expr.search(&data).unwrap();
1750        let obj = result.as_object().unwrap();
1751        assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "Bob");
1752    }
1753
1754    #[test]
1755    fn test_min_by_expr_empty() {
1756        let runtime = setup_runtime();
1757        let data = json!([]);
1758        let expr = runtime.compile("min_by_expr(&age, @)").unwrap();
1759        let result = expr.search(&data).unwrap();
1760        assert!(result.is_null());
1761    }
1762
1763    #[test]
1764    fn test_max_by_expr() {
1765        let runtime = setup_runtime();
1766        let data = json!([{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]);
1767        let expr = runtime.compile("max_by_expr(&age, @)").unwrap();
1768        let result = expr.search(&data).unwrap();
1769        let obj = result.as_object().unwrap();
1770        assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "Alice");
1771    }
1772
1773    #[test]
1774    fn test_unique_by_expr() {
1775        let runtime = setup_runtime();
1776        let data = json!([
1777            {"type": "a", "val": 1},
1778            {"type": "b", "val": 2},
1779            {"type": "a", "val": 3}
1780        ]);
1781        let expr = runtime.compile("unique_by_expr(&type, @)").unwrap();
1782        let result = expr.search(&data).unwrap();
1783        let arr = result.as_array().unwrap();
1784        assert_eq!(arr.len(), 2); // First "a" and first "b"
1785        assert_eq!(
1786            arr[0]
1787                .as_object()
1788                .unwrap()
1789                .get("val")
1790                .unwrap()
1791                .as_f64()
1792                .unwrap(),
1793            1.0
1794        );
1795    }
1796
1797    #[test]
1798    fn test_flat_map_expr() {
1799        let runtime = setup_runtime();
1800        let data = json!([
1801            {"tags": ["a", "b"]},
1802            {"tags": ["c"]},
1803            {"tags": ["d", "e"]}
1804        ]);
1805        let expr = runtime.compile("flat_map_expr(&tags, @)").unwrap();
1806        let result = expr.search(&data).unwrap();
1807        let arr = result.as_array().unwrap();
1808        assert_eq!(arr.len(), 5);
1809        assert_eq!(arr[0].as_str().unwrap(), "a");
1810        assert_eq!(arr[4].as_str().unwrap(), "e");
1811    }
1812
1813    #[test]
1814    fn test_flat_map_expr_non_array() {
1815        let runtime = setup_runtime();
1816        let data = json!([{"name": "Alice"}, {"name": "Bob"}]);
1817        let expr = runtime.compile("flat_map_expr(&name, @)").unwrap();
1818        let result = expr.search(&data).unwrap();
1819        let arr = result.as_array().unwrap();
1820        assert_eq!(arr.len(), 2);
1821        assert_eq!(arr[0].as_str().unwrap(), "Alice");
1822    }
1823
1824    #[test]
1825    fn test_some_alias() {
1826        let runtime = setup_runtime();
1827        let data = json!([1, 2, 3, 4, 5]);
1828        let expr = runtime.compile("some(&(@ > `3`), @)").unwrap();
1829        let result = expr.search(&data).unwrap();
1830        assert!(result.as_bool().unwrap());
1831    }
1832
1833    #[test]
1834    fn test_every_alias() {
1835        let runtime = setup_runtime();
1836        let data = json!([2, 4, 6]);
1837        let expr = runtime.compile("every(&(@ > `0`), @)").unwrap();
1838        let result = expr.search(&data).unwrap();
1839        assert!(result.as_bool().unwrap());
1840    }
1841
1842    #[test]
1843    fn test_none_true() {
1844        let runtime = setup_runtime();
1845        let data = json!([1, 2, 3]);
1846        let expr = runtime.compile("none(&(@ > `5`), @)").unwrap();
1847        let result = expr.search(&data).unwrap();
1848        assert!(result.as_bool().unwrap()); // No element > 5
1849    }
1850
1851    #[test]
1852    fn test_none_false() {
1853        let runtime = setup_runtime();
1854        let data = json!([1, 2, 10]);
1855        let expr = runtime.compile("none(&(@ > `5`), @)").unwrap();
1856        let result = expr.search(&data).unwrap();
1857        assert!(!result.as_bool().unwrap()); // 10 > 5
1858    }
1859
1860    #[test]
1861    fn test_none_empty() {
1862        let runtime = setup_runtime();
1863        let data = json!([]);
1864        let expr = runtime.compile("none(&(@ > `0`), @)").unwrap();
1865        let result = expr.search(&data).unwrap();
1866        assert!(result.as_bool().unwrap()); // Vacuously true
1867    }
1868
1869    #[test]
1870    fn test_none_objects() {
1871        let runtime = setup_runtime();
1872        let data = json!([{"active": false}, {"active": false}]);
1873        let expr = runtime.compile("none(&active, @)").unwrap();
1874        let result = expr.search(&data).unwrap();
1875        assert!(result.as_bool().unwrap()); // No active elements
1876    }
1877
1878    #[test]
1879    fn test_mapcat_alias() {
1880        let runtime = setup_runtime();
1881        let data = json!([
1882            {"tags": ["a", "b"]},
1883            {"tags": ["c"]},
1884            {"tags": ["d", "e"]}
1885        ]);
1886        let expr = runtime.compile("mapcat(&tags, @)").unwrap();
1887        let result = expr.search(&data).unwrap();
1888        let arr = result.as_array().unwrap();
1889        assert_eq!(arr.len(), 5);
1890        assert_eq!(arr[0].as_str().unwrap(), "a");
1891        assert_eq!(arr[4].as_str().unwrap(), "e");
1892    }
1893
1894    #[test]
1895    fn test_reductions_alias() {
1896        let runtime = setup_runtime();
1897        let data = json!([1, 2, 3, 4]);
1898        let expr = runtime
1899            .compile("reductions(&sum([accumulator, current]), @, `0`)")
1900            .unwrap();
1901        let result = expr.search(&data).unwrap();
1902        let arr = result.as_array().unwrap();
1903        // Running sum: [1, 3, 6, 10]
1904        assert_eq!(arr.len(), 4);
1905        assert_eq!(arr[0].as_f64().unwrap(), 1.0);
1906        assert_eq!(arr[1].as_f64().unwrap(), 3.0);
1907        assert_eq!(arr[2].as_f64().unwrap(), 6.0);
1908        assert_eq!(arr[3].as_f64().unwrap(), 10.0);
1909    }
1910
1911    #[test]
1912    fn test_reject() {
1913        let runtime = setup_runtime();
1914        let data = json!([1, 2, 3, 4, 5]);
1915        let expr = runtime.compile("reject(&(@ > `2`), @)").unwrap();
1916        let result = expr.search(&data).unwrap();
1917        let arr = result.as_array().unwrap();
1918        assert_eq!(arr.len(), 2); // 1, 2
1919        assert_eq!(arr[0].as_f64().unwrap(), 1.0);
1920        assert_eq!(arr[1].as_f64().unwrap(), 2.0);
1921    }
1922
1923    #[test]
1924    fn test_reject_objects() {
1925        let runtime = setup_runtime();
1926        let data = json!([{"active": true}, {"active": false}, {"active": true}]);
1927        let expr = runtime.compile("reject(&active, @)").unwrap();
1928        let result = expr.search(&data).unwrap();
1929        let arr = result.as_array().unwrap();
1930        assert_eq!(arr.len(), 1); // Only the inactive one
1931    }
1932
1933    #[test]
1934    fn test_map_keys() {
1935        let runtime = setup_runtime();
1936        // Use length to transform key to its length (as string)
1937        let data = json!({"abc": 1, "de": 2});
1938        let expr = runtime.compile("map_keys(&length(@), @)").unwrap();
1939        let result = expr.search(&data).unwrap();
1940        let obj = result.as_object().unwrap();
1941        // "abc" -> 3, "de" -> 2 (converted to string keys)
1942        assert!(obj.contains_key("3") || obj.contains_key("2"));
1943    }
1944
1945    #[test]
1946    fn test_map_values_add() {
1947        let runtime = setup_runtime();
1948        // Use sum to get sum of a literal array
1949        let data = json!({"a": 1, "b": 2, "c": 3});
1950        let expr = runtime.compile("map_values(&sum(`[1]`), @)").unwrap();
1951        let result = expr.search(&data).unwrap();
1952        let obj = result.as_object().unwrap();
1953        // Each value becomes 1 (sum of [1])
1954        assert_eq!(obj.get("a").unwrap().as_f64().unwrap(), 1.0);
1955    }
1956
1957    #[test]
1958    fn test_map_values_length() {
1959        let runtime = setup_runtime();
1960        let data = json!({"name": "alice", "city": "boston"});
1961        let expr = runtime.compile("map_values(&length(@), @)").unwrap();
1962        let result = expr.search(&data).unwrap();
1963        let obj = result.as_object().unwrap();
1964        assert_eq!(obj.get("name").unwrap().as_f64().unwrap(), 5.0); // "alice" = 5 chars
1965        assert_eq!(obj.get("city").unwrap().as_f64().unwrap(), 6.0); // "boston" = 6 chars
1966    }
1967
1968    #[test]
1969    fn test_map_values_with_string_fns() {
1970        let runtime = setup_runtime();
1971        let data = json!({"name": "alice", "city": "boston"});
1972        let expr = runtime.compile("map_values(&upper(@), @)").unwrap();
1973        let result = expr.search(&data).unwrap();
1974        let obj = result.as_object().unwrap();
1975        assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "ALICE");
1976        assert_eq!(obj.get("city").unwrap().as_str().unwrap(), "BOSTON");
1977    }
1978
1979    #[test]
1980    fn test_map_keys_with_string_fns() {
1981        let runtime = setup_runtime();
1982        let data = json!({"hello": 1, "world": 2});
1983        let expr = runtime.compile("map_keys(&upper(@), @)").unwrap();
1984        let result = expr.search(&data).unwrap();
1985        let obj = result.as_object().unwrap();
1986        assert!(obj.contains_key("HELLO"));
1987        assert!(obj.contains_key("WORLD"));
1988    }
1989
1990    #[test]
1991    fn test_order_by_single_field_asc() {
1992        let runtime = setup_runtime();
1993        let data = json!([
1994            {"name": "Charlie", "age": 30},
1995            {"name": "Alice", "age": 25},
1996            {"name": "Bob", "age": 35}
1997        ]);
1998        let expr = runtime
1999            .compile(r#"order_by(@, `[["name", "asc"]]`)"#)
2000            .unwrap();
2001        let result = expr.search(&data).unwrap();
2002        let arr = result.as_array().unwrap();
2003        assert_eq!(
2004            arr[0]
2005                .as_object()
2006                .unwrap()
2007                .get("name")
2008                .unwrap()
2009                .as_str()
2010                .unwrap(),
2011            "Alice"
2012        );
2013        assert_eq!(
2014            arr[1]
2015                .as_object()
2016                .unwrap()
2017                .get("name")
2018                .unwrap()
2019                .as_str()
2020                .unwrap(),
2021            "Bob"
2022        );
2023        assert_eq!(
2024            arr[2]
2025                .as_object()
2026                .unwrap()
2027                .get("name")
2028                .unwrap()
2029                .as_str()
2030                .unwrap(),
2031            "Charlie"
2032        );
2033    }
2034
2035    #[test]
2036    fn test_order_by_single_field_desc() {
2037        let runtime = setup_runtime();
2038        let data = json!([
2039            {"name": "Alice", "age": 25},
2040            {"name": "Bob", "age": 35},
2041            {"name": "Charlie", "age": 30}
2042        ]);
2043        let expr = runtime
2044            .compile(r#"order_by(@, `[["age", "desc"]]`)"#)
2045            .unwrap();
2046        let result = expr.search(&data).unwrap();
2047        let arr = result.as_array().unwrap();
2048        assert_eq!(
2049            arr[0]
2050                .as_object()
2051                .unwrap()
2052                .get("age")
2053                .unwrap()
2054                .as_f64()
2055                .unwrap(),
2056            35.0
2057        );
2058        assert_eq!(
2059            arr[1]
2060                .as_object()
2061                .unwrap()
2062                .get("age")
2063                .unwrap()
2064                .as_f64()
2065                .unwrap(),
2066            30.0
2067        );
2068        assert_eq!(
2069            arr[2]
2070                .as_object()
2071                .unwrap()
2072                .get("age")
2073                .unwrap()
2074                .as_f64()
2075                .unwrap(),
2076            25.0
2077        );
2078    }
2079
2080    #[test]
2081    fn test_order_by_multiple_fields() {
2082        let runtime = setup_runtime();
2083        let data = json!([
2084            {"dept": "sales", "name": "Bob"},
2085            {"dept": "eng", "name": "Alice"},
2086            {"dept": "sales", "name": "Alice"}
2087        ]);
2088        let expr = runtime
2089            .compile(r#"order_by(@, `[["dept", "asc"], ["name", "asc"]]`)"#)
2090            .unwrap();
2091        let result = expr.search(&data).unwrap();
2092        let arr = result.as_array().unwrap();
2093        // eng comes first, then sales (sorted by dept)
2094        assert_eq!(
2095            arr[0]
2096                .as_object()
2097                .unwrap()
2098                .get("dept")
2099                .unwrap()
2100                .as_str()
2101                .unwrap(),
2102            "eng"
2103        );
2104        // Within sales, Alice comes before Bob
2105        assert_eq!(
2106            arr[1]
2107                .as_object()
2108                .unwrap()
2109                .get("name")
2110                .unwrap()
2111                .as_str()
2112                .unwrap(),
2113            "Alice"
2114        );
2115        assert_eq!(
2116            arr[2]
2117                .as_object()
2118                .unwrap()
2119                .get("name")
2120                .unwrap()
2121                .as_str()
2122                .unwrap(),
2123            "Bob"
2124        );
2125    }
2126
2127    #[test]
2128    fn test_reduce_expr_sum() {
2129        let runtime = setup_runtime();
2130        let data = json!([1, 2, 3, 4, 5]);
2131        let expr = runtime
2132            .compile("reduce_expr(&sum([accumulator, current]), @, `0`)")
2133            .unwrap();
2134        let result = expr.search(&data).unwrap();
2135        assert_eq!(result.as_f64().unwrap(), 15.0);
2136    }
2137
2138    #[test]
2139    fn test_reduce_expr_max() {
2140        let runtime = setup_runtime();
2141        let data = json!([3, 1, 4, 1, 5, 9, 2, 6]);
2142        let expr = runtime
2143            .compile("reduce_expr(&max([accumulator, current]), @, `0`)")
2144            .unwrap();
2145        let result = expr.search(&data).unwrap();
2146        assert_eq!(result.as_f64().unwrap(), 9.0);
2147    }
2148
2149    #[test]
2150    fn test_reduce_expr_empty() {
2151        let runtime = setup_runtime();
2152        let data = json!([]);
2153        let expr = runtime
2154            .compile("reduce_expr(&sum([accumulator, current]), @, `42`)")
2155            .unwrap();
2156        let result = expr.search(&data).unwrap();
2157        assert_eq!(result.as_f64().unwrap(), 42.0); // Returns initial value
2158    }
2159
2160    #[test]
2161    fn test_fold_alias() {
2162        let runtime = setup_runtime();
2163        let data = json!([1, 2, 3]);
2164        let expr = runtime
2165            .compile("fold(&sum([accumulator, current]), @, `0`)")
2166            .unwrap();
2167        let result = expr.search(&data).unwrap();
2168        assert_eq!(result.as_f64().unwrap(), 6.0);
2169    }
2170
2171    #[test]
2172    fn test_scan_expr_running_sum() {
2173        let runtime = setup_runtime();
2174        let data = json!([1, 2, 3, 4]);
2175        let expr = runtime
2176            .compile("scan_expr(&sum([accumulator, current]), @, `0`)")
2177            .unwrap();
2178        let result = expr.search(&data).unwrap();
2179        let arr = result.as_array().unwrap();
2180        // Running sum: [1, 3, 6, 10]
2181        assert_eq!(arr.len(), 4);
2182        assert_eq!(arr[0].as_f64().unwrap(), 1.0);
2183        assert_eq!(arr[1].as_f64().unwrap(), 3.0);
2184        assert_eq!(arr[2].as_f64().unwrap(), 6.0);
2185        assert_eq!(arr[3].as_f64().unwrap(), 10.0);
2186    }
2187
2188    #[test]
2189    fn test_scan_expr_empty() {
2190        let runtime = setup_runtime();
2191        let data = json!([]);
2192        let expr = runtime
2193            .compile("scan_expr(&sum([accumulator, current]), @, `0`)")
2194            .unwrap();
2195        let result = expr.search(&data).unwrap();
2196        let arr = result.as_array().unwrap();
2197        assert_eq!(arr.len(), 0);
2198    }
2199
2200    #[test]
2201    fn test_reduce_expr_with_index() {
2202        let runtime = setup_runtime();
2203        // Access the index in the reduce expression
2204        let data = json!([10, 20, 30]);
2205        let expr = runtime
2206            .compile("reduce_expr(&sum([accumulator, index]), @, `0`)")
2207            .unwrap();
2208        let result = expr.search(&data).unwrap();
2209        // 0 + 1 + 2 = 3
2210        assert_eq!(result.as_f64().unwrap(), 3.0);
2211    }
2212
2213    #[test]
2214    fn test_count_by_objects() {
2215        let runtime = setup_runtime();
2216        let data = json!([
2217            {"type": "a"},
2218            {"type": "b"},
2219            {"type": "a"},
2220            {"type": "a"}
2221        ]);
2222        let expr = runtime.compile("count_by(&type, @)").unwrap();
2223        let result = expr.search(&data).unwrap();
2224        let obj = result.as_object().unwrap();
2225        assert_eq!(obj.get("a").unwrap().as_f64().unwrap(), 3.0);
2226        assert_eq!(obj.get("b").unwrap().as_f64().unwrap(), 1.0);
2227    }
2228
2229    #[test]
2230    fn test_count_by_strings() {
2231        let runtime = setup_runtime();
2232        let data = json!(["a", "b", "a", "c", "a"]);
2233        let expr = runtime.compile("count_by(&@, @)").unwrap();
2234        let result = expr.search(&data).unwrap();
2235        let obj = result.as_object().unwrap();
2236        assert_eq!(obj.get("a").unwrap().as_f64().unwrap(), 3.0);
2237        assert_eq!(obj.get("b").unwrap().as_f64().unwrap(), 1.0);
2238        assert_eq!(obj.get("c").unwrap().as_f64().unwrap(), 1.0);
2239    }
2240
2241    #[test]
2242    fn test_count_by_empty() {
2243        let runtime = setup_runtime();
2244        let data = json!([]);
2245        let expr = runtime.compile("count_by(&type, @)").unwrap();
2246        let result = expr.search(&data).unwrap();
2247        let obj = result.as_object().unwrap();
2248        assert!(obj.is_empty());
2249    }
2250
2251    #[test]
2252    fn test_count_by_numbers() {
2253        let runtime = setup_runtime();
2254        let data = json!([1, 2, 1, 3, 1, 2]);
2255        let expr = runtime.compile("count_by(&@, @)").unwrap();
2256        let result = expr.search(&data).unwrap();
2257        let obj = result.as_object().unwrap();
2258        assert_eq!(obj.get("1").unwrap().as_f64().unwrap(), 3.0);
2259        assert_eq!(obj.get("2").unwrap().as_f64().unwrap(), 2.0);
2260        assert_eq!(obj.get("3").unwrap().as_f64().unwrap(), 1.0);
2261    }
2262
2263    // =============================================================================
2264    // Partial application tests
2265    // =============================================================================
2266
2267    #[test]
2268    fn test_partial_creates_object() {
2269        let runtime = setup_runtime();
2270        let data = json!(null);
2271        let expr = runtime.compile("partial('length')").unwrap();
2272        let result = expr.search(&data).unwrap();
2273        let obj = result.as_object().unwrap();
2274        assert!(obj.get("__partial__").unwrap().as_bool().unwrap());
2275        assert_eq!(obj.get("fn").unwrap().as_str().unwrap(), "length");
2276        assert!(obj.get("args").unwrap().as_array().unwrap().is_empty());
2277    }
2278
2279    #[test]
2280    fn test_partial_with_args() {
2281        let runtime = setup_runtime();
2282        let data = json!(null);
2283        let expr = runtime
2284            .compile("partial('contains', `\"hello world\"`)")
2285            .unwrap();
2286        let result = expr.search(&data).unwrap();
2287        let obj = result.as_object().unwrap();
2288        assert!(obj.get("__partial__").unwrap().as_bool().unwrap());
2289        assert_eq!(obj.get("fn").unwrap().as_str().unwrap(), "contains");
2290        let args = obj.get("args").unwrap().as_array().unwrap();
2291        assert_eq!(args.len(), 1);
2292        assert_eq!(args[0].as_str().unwrap(), "hello world");
2293    }
2294
2295    #[test]
2296    fn test_apply_with_fn_name() {
2297        let runtime = setup_runtime();
2298        let data = json!(null);
2299        let expr = runtime.compile("apply('length', `\"hello\"`)").unwrap();
2300        let result = expr.search(&data).unwrap();
2301        assert_eq!(result.as_f64().unwrap(), 5.0);
2302    }
2303
2304    #[test]
2305    fn test_apply_with_partial() {
2306        let runtime = setup_runtime();
2307        let data = json!(null);
2308        // Create partial with first arg, then apply with second arg
2309        let expr = runtime
2310            .compile("apply(partial('contains', `\"hello world\"`), `\"world\"`)")
2311            .unwrap();
2312        let result = expr.search(&data).unwrap();
2313        assert!(result.as_bool().unwrap());
2314    }
2315
2316    #[test]
2317    fn test_apply_partial_not_found() {
2318        let runtime = setup_runtime();
2319        let data = json!(null);
2320        let expr = runtime
2321            .compile("apply(partial('contains', `\"hello world\"`), `\"xyz\"`)")
2322            .unwrap();
2323        let result = expr.search(&data).unwrap();
2324        assert!(!result.as_bool().unwrap());
2325    }
2326
2327    #[test]
2328    fn test_partial_with_multiple_prefilled_args() {
2329        let runtime = setup_runtime();
2330        let data = json!(null);
2331        // partial with 2 args pre-filled
2332        let expr = runtime.compile("partial('join', `\"-\"`)").unwrap();
2333        let result = expr.search(&data).unwrap();
2334        let obj = result.as_object().unwrap();
2335        let args = obj.get("args").unwrap().as_array().unwrap();
2336        assert_eq!(args.len(), 1);
2337        assert_eq!(args[0].as_str().unwrap(), "-");
2338    }
2339
2340    #[test]
2341    fn test_apply_partial_join() {
2342        let runtime = setup_runtime();
2343        let data = json!(null);
2344        // Create a join with "-" separator, then apply to array
2345        let expr = runtime
2346            .compile("apply(partial('join', `\"-\"`), `[\"a\", \"b\", \"c\"]`)")
2347            .unwrap();
2348        let result = expr.search(&data).unwrap();
2349        assert_eq!(result.as_str().unwrap(), "a-b-c");
2350    }
2351
2352    // =========================================================================
2353    // Pipeline pattern tests
2354    // =========================================================================
2355
2356    #[test]
2357    fn test_pipeline_filter_sort_products() {
2358        let runtime = setup_runtime();
2359        let data = json!({
2360            "products": [
2361                {"name": "A", "price": 30, "in_stock": true},
2362                {"name": "B", "price": 10, "in_stock": true},
2363                {"name": "C", "price": 20, "in_stock": false},
2364                {"name": "D", "price": 5, "in_stock": true}
2365            ]
2366        });
2367        let expr = runtime
2368            .compile("products | filter_expr(&in_stock, @) | sort_by_expr(&price, @)")
2369            .unwrap();
2370        let result = expr.search(&data).unwrap();
2371        let arr = result.as_array().unwrap();
2372        assert_eq!(arr.len(), 3);
2373        assert_eq!(
2374            arr[0]
2375                .as_object()
2376                .unwrap()
2377                .get("name")
2378                .unwrap()
2379                .as_str()
2380                .unwrap(),
2381            "D"
2382        ); // $5
2383        assert_eq!(
2384            arr[1]
2385                .as_object()
2386                .unwrap()
2387                .get("name")
2388                .unwrap()
2389                .as_str()
2390                .unwrap(),
2391            "B"
2392        ); // $10
2393    }
2394
2395    #[test]
2396    fn test_pipeline_funnel_errors() {
2397        let runtime = setup_runtime();
2398        let data = json!({
2399            "events": [
2400                {"level": "error", "timestamp": 1704067300, "message": "Disk full"},
2401                {"level": "info", "timestamp": 1704067200, "message": "Started"},
2402                {"level": "error", "timestamp": 1704067400, "message": "Connection lost"},
2403                {"level": "warn", "timestamp": 1704067350, "message": "High memory"}
2404            ]
2405        });
2406        let expr = runtime
2407            .compile(
2408                r#"events | filter_expr(&(level == `"error"`), @) | sort_by_expr(&timestamp, @)"#,
2409            )
2410            .unwrap();
2411        let result = expr.search(&data).unwrap();
2412        let arr = result.as_array().unwrap();
2413        assert_eq!(arr.len(), 2);
2414        // Sorted by timestamp ascending
2415        assert_eq!(
2416            arr[0]
2417                .as_object()
2418                .unwrap()
2419                .get("message")
2420                .unwrap()
2421                .as_str()
2422                .unwrap(),
2423            "Disk full"
2424        );
2425    }
2426
2427    #[test]
2428    fn test_pipeline_transactions_completed() {
2429        let runtime = setup_runtime();
2430        let data = json!({
2431            "transactions": [
2432                {"amount": 100, "status": "completed"},
2433                {"amount": 50, "status": "completed"},
2434                {"amount": 75, "status": "pending"},
2435                {"amount": 200, "status": "completed"}
2436            ]
2437        });
2438        let expr = runtime
2439            .compile(
2440                r#"transactions | filter_expr(&(status == `"completed"`), @) | map_expr(&amount, @)"#,
2441            )
2442            .unwrap();
2443        let result = expr.search(&data).unwrap();
2444        let arr = result.as_array().unwrap();
2445        assert_eq!(arr.len(), 3);
2446        assert_eq!(arr[0].as_f64().unwrap(), 100.0);
2447        assert_eq!(arr[1].as_f64().unwrap(), 50.0);
2448        assert_eq!(arr[2].as_f64().unwrap(), 200.0);
2449    }
2450
2451    #[test]
2452    fn test_pipeline_fork_join() {
2453        let runtime = setup_runtime();
2454        let data = json!({
2455            "items": [
2456                {"name": "A", "price": 150},
2457                {"name": "B", "price": 50},
2458                {"name": "C", "price": 200},
2459                {"name": "D", "price": 25}
2460            ]
2461        });
2462        let expr = runtime
2463            .compile(
2464                r#"@.{
2465                    expensive: items | filter_expr(&(price > `100`), @),
2466                    cheap: items | filter_expr(&(price <= `100`), @)
2467                }"#,
2468            )
2469            .unwrap();
2470        let result = expr.search(&data).unwrap();
2471        let obj = result.as_object().unwrap();
2472        assert_eq!(obj.get("expensive").unwrap().as_array().unwrap().len(), 2);
2473        assert_eq!(obj.get("cheap").unwrap().as_array().unwrap().len(), 2);
2474    }
2475
2476    #[test]
2477    fn test_pipeline_nested_users() {
2478        let runtime = setup_runtime();
2479        let data = json!({
2480            "users": [
2481                {"name": "Alice", "orders": [{"total": 100}, {"total": 50}]},
2482                {"name": "Bob", "orders": [{"total": 200}]},
2483                {"name": "Carol", "orders": []}
2484            ]
2485        });
2486        // Filter users with orders, then map to get names
2487        let expr = runtime
2488            .compile("users | filter_expr(&(length(orders) > `0`), @) | map_expr(&name, @)")
2489            .unwrap();
2490        let result = expr.search(&data).unwrap();
2491        let arr = result.as_array().unwrap();
2492        assert_eq!(arr.len(), 2);
2493        assert_eq!(arr[0].as_str().unwrap(), "Alice");
2494        assert_eq!(arr[1].as_str().unwrap(), "Bob");
2495    }
2496
2497    #[test]
2498    fn test_pipeline_rag_chunks() {
2499        let runtime = setup_runtime();
2500        let data = json!({
2501            "chunks": [
2502                {"content": "Redis is fast", "score": 0.9},
2503                {"content": "Redis is in-memory", "score": 0.85},
2504                {"content": "Unrelated content", "score": 0.5},
2505                {"content": "Redis supports modules", "score": 0.75}
2506            ]
2507        });
2508        let expr = runtime
2509            .compile("chunks | filter_expr(&(score > `0.7`), @) | sort_by_expr(&score, @)")
2510            .unwrap();
2511        let result = expr.search(&data).unwrap();
2512        let arr = result.as_array().unwrap();
2513        assert_eq!(arr.len(), 3);
2514        // Sorted ascending by score
2515        assert_eq!(
2516            arr[0]
2517                .as_object()
2518                .unwrap()
2519                .get("score")
2520                .unwrap()
2521                .as_f64()
2522                .unwrap(),
2523            0.75
2524        );
2525    }
2526
2527    // =========================================================================
2528    // Additional reduce_expr/scan_expr tests
2529    // =========================================================================
2530
2531    #[test]
2532    fn test_reduce_expr_product() {
2533        let runtime = setup_runtime();
2534        // Test reduce with min (similar to existing max test but finds minimum)
2535        let data = json!([5, 3, 8, 1, 9]);
2536        let expr = runtime
2537            .compile("reduce_expr(&min([accumulator, current]), @, `100`)")
2538            .unwrap();
2539        let result = expr.search(&data).unwrap();
2540        assert_eq!(result.as_f64().unwrap(), 1.0);
2541    }
2542
2543    #[test]
2544    fn test_scan_expr_running_balance() {
2545        let runtime = setup_runtime();
2546        // Test scan with running max - shows progressive maximum
2547        let data = json!([3, 1, 4, 1, 5, 9]);
2548        let expr = runtime
2549            .compile("scan_expr(&max([accumulator, current]), @, `0`)")
2550            .unwrap();
2551        let result = expr.search(&data).unwrap();
2552        let arr = result.as_array().unwrap();
2553        // Running max: 3, 3, 4, 4, 5, 9
2554        assert_eq!(arr[0].as_f64().unwrap(), 3.0);
2555        assert_eq!(arr[1].as_f64().unwrap(), 3.0);
2556        assert_eq!(arr[2].as_f64().unwrap(), 4.0);
2557        assert_eq!(arr[3].as_f64().unwrap(), 4.0);
2558        assert_eq!(arr[4].as_f64().unwrap(), 5.0);
2559        assert_eq!(arr[5].as_f64().unwrap(), 9.0);
2560    }
2561
2562    // =========================================================================
2563    // Additional order_by tests
2564    // =========================================================================
2565
2566    #[test]
2567    fn test_order_by_three_fields() {
2568        let runtime = setup_runtime();
2569        let data = json!([
2570            {"dept": "Engineering", "level": "senior", "name": "Charlie"},
2571            {"dept": "Engineering", "level": "junior", "name": "Alice"},
2572            {"dept": "Engineering", "level": "senior", "name": "Bob"},
2573            {"dept": "Sales", "level": "senior", "name": "David"}
2574        ]);
2575        let expr = runtime
2576            .compile(r#"order_by(@, `[["dept", "asc"], ["level", "desc"], ["name", "asc"]]`)"#)
2577            .unwrap();
2578        let result = expr.search(&data).unwrap();
2579        let arr = result.as_array().unwrap();
2580        // Engineering seniors first (alphabetical), then Engineering juniors, then Sales
2581        assert_eq!(
2582            arr[0]
2583                .as_object()
2584                .unwrap()
2585                .get("name")
2586                .unwrap()
2587                .as_str()
2588                .unwrap(),
2589            "Bob"
2590        );
2591        assert_eq!(
2592            arr[1]
2593                .as_object()
2594                .unwrap()
2595                .get("name")
2596                .unwrap()
2597                .as_str()
2598                .unwrap(),
2599            "Charlie"
2600        );
2601    }
2602
2603    #[test]
2604    fn test_order_by_empty() {
2605        let runtime = setup_runtime();
2606        let data = json!([]);
2607        let expr = runtime
2608            .compile(r#"order_by(@, `[["name", "asc"]]`)"#)
2609            .unwrap();
2610        let result = expr.search(&data).unwrap();
2611        let arr = result.as_array().unwrap();
2612        assert!(arr.is_empty());
2613    }
2614
2615    // =========================================================================
2616    // Additional partition_expr tests
2617    // =========================================================================
2618
2619    #[test]
2620    fn test_partition_expr_scores() {
2621        let runtime = setup_runtime();
2622        let data = json!([85, 42, 91, 67, 55, 78, 33, 99]);
2623        let expr = runtime.compile("partition_expr(&(@ >= `60`), @)").unwrap();
2624        let result = expr.search(&data).unwrap();
2625        let arr = result.as_array().unwrap();
2626        let passing = arr[0].as_array().unwrap();
2627        let failing = arr[1].as_array().unwrap();
2628        assert_eq!(passing.len(), 5); // 85, 91, 67, 78, 99
2629        assert_eq!(failing.len(), 3); // 42, 55, 33
2630    }
2631
2632    #[test]
2633    fn test_partition_expr_active() {
2634        let runtime = setup_runtime();
2635        let data = json!([{"active": true}, {"active": false}, {"active": true}]);
2636        let expr = runtime.compile("partition_expr(&active, @)").unwrap();
2637        let result = expr.search(&data).unwrap();
2638        let arr = result.as_array().unwrap();
2639        assert_eq!(arr[0].as_array().unwrap().len(), 2);
2640        assert_eq!(arr[1].as_array().unwrap().len(), 1);
2641    }
2642
2643    // =========================================================================
2644    // Additional map_values/map_keys tests
2645    // =========================================================================
2646
2647    #[test]
2648    fn test_map_values_discount() {
2649        let runtime = setup_runtime();
2650        // Test with string transformation since nested expressions don't have extension math functions
2651        let data = json!({"apple": "FRUIT", "banana": "ITEM"});
2652        let expr = runtime.compile("map_values(&length(@), @)").unwrap();
2653        let result = expr.search(&data).unwrap();
2654        let obj = result.as_object().unwrap();
2655        assert_eq!(obj.get("apple").unwrap().as_f64().unwrap(), 5.0);
2656        assert_eq!(obj.get("banana").unwrap().as_f64().unwrap(), 4.0);
2657    }
2658
2659    // =========================================================================
2660    // Additional group_by_expr tests
2661    // =========================================================================
2662
2663    #[test]
2664    fn test_group_by_expr_type() {
2665        let runtime = setup_runtime();
2666        let data = json!([
2667            {"type": "fruit", "name": "apple"},
2668            {"type": "vegetable", "name": "carrot"},
2669            {"type": "fruit", "name": "banana"}
2670        ]);
2671        let expr = runtime.compile("group_by_expr(&type, @)").unwrap();
2672        let result = expr.search(&data).unwrap();
2673        let obj = result.as_object().unwrap();
2674        assert_eq!(obj.get("fruit").unwrap().as_array().unwrap().len(), 2);
2675        assert_eq!(obj.get("vegetable").unwrap().as_array().unwrap().len(), 1);
2676    }
2677
2678    #[test]
2679    fn test_group_by_expr_computed() {
2680        let runtime = setup_runtime();
2681        // Group strings by their length using built-in length function
2682        let data = json!(["a", "bb", "ccc", "dd", "eee", "f"]);
2683        let expr = runtime
2684            .compile("group_by_expr(&to_string(length(@)), @)")
2685            .unwrap();
2686        let result = expr.search(&data).unwrap();
2687        let obj = result.as_object().unwrap();
2688        assert!(obj.contains_key("1")); // "a", "f"
2689        assert!(obj.contains_key("2")); // "bb", "dd"
2690        assert!(obj.contains_key("3")); // "ccc", "eee"
2691        assert_eq!(obj.get("1").unwrap().as_array().unwrap().len(), 2);
2692        assert_eq!(obj.get("2").unwrap().as_array().unwrap().len(), 2);
2693        assert_eq!(obj.get("3").unwrap().as_array().unwrap().len(), 2);
2694    }
2695
2696    // =========================================================================
2697    // Additional unique_by_expr tests
2698    // =========================================================================
2699
2700    #[test]
2701    fn test_unique_by_expr_id() {
2702        let runtime = setup_runtime();
2703        let data = json!([
2704            {"id": 1, "v": "a"},
2705            {"id": 2, "v": "b"},
2706            {"id": 1, "v": "c"}
2707        ]);
2708        let expr = runtime.compile("unique_by_expr(&id, @)").unwrap();
2709        let result = expr.search(&data).unwrap();
2710        let arr = result.as_array().unwrap();
2711        assert_eq!(arr.len(), 2);
2712        // Keeps first occurrence
2713        assert_eq!(
2714            arr[0]
2715                .as_object()
2716                .unwrap()
2717                .get("v")
2718                .unwrap()
2719                .as_str()
2720                .unwrap(),
2721            "a"
2722        );
2723    }
2724
2725    // =========================================================================
2726    // Edge case tests
2727    // =========================================================================
2728
2729    #[test]
2730    fn test_any_expr_empty() {
2731        let runtime = setup_runtime();
2732        let data = json!([]);
2733        let expr = runtime.compile("any_expr(&(@ > `0`), @)").unwrap();
2734        let result = expr.search(&data).unwrap();
2735        assert!(!result.as_bool().unwrap());
2736    }
2737
2738    #[test]
2739    fn test_max_by_expr_empty() {
2740        let runtime = setup_runtime();
2741        let data = json!([]);
2742        let expr = runtime.compile("max_by_expr(&age, @)").unwrap();
2743        let result = expr.search(&data).unwrap();
2744        assert!(result.is_null());
2745    }
2746
2747    #[test]
2748    fn test_flat_map_expr_duplicate() {
2749        let runtime = setup_runtime();
2750        let data = json!([1, 2, 3]);
2751        // Duplicate each element
2752        let expr = runtime.compile("flat_map_expr(&[@, @], @)").unwrap();
2753        let result = expr.search(&data).unwrap();
2754        let arr = result.as_array().unwrap();
2755        assert_eq!(arr.len(), 6);
2756    }
2757
2758    #[test]
2759    fn test_reject_greater_than() {
2760        let runtime = setup_runtime();
2761        let data = json!([1, 2, 3, 4, 5, 6]);
2762        let expr = runtime.compile("reject(&(@ > `3`), @)").unwrap();
2763        let result = expr.search(&data).unwrap();
2764        let arr = result.as_array().unwrap();
2765        assert_eq!(arr.len(), 3); // 1, 2, 3
2766    }
2767
2768    #[test]
2769    fn test_every_false_case() {
2770        let runtime = setup_runtime();
2771        let data = json!([1, -1, 3]);
2772        let expr = runtime.compile("every(&(@ > `0`), @)").unwrap();
2773        let result = expr.search(&data).unwrap();
2774        assert!(!result.as_bool().unwrap());
2775    }
2776
2777    #[test]
2778    fn test_count_expr_all_match() {
2779        let runtime = setup_runtime();
2780        let data = json!([5, 10, 15, 20]);
2781        let expr = runtime.compile("count_expr(&(@ > `0`), @)").unwrap();
2782        let result = expr.search(&data).unwrap();
2783        assert_eq!(result.as_f64().unwrap(), 4.0);
2784    }
2785
2786    #[test]
2787    fn test_find_expr_first_match() {
2788        let runtime = setup_runtime();
2789        let data = json!([1, 5, 10, 15]);
2790        let expr = runtime.compile("find_expr(&(@ > `3`), @)").unwrap();
2791        let result = expr.search(&data).unwrap();
2792        assert_eq!(result.as_f64().unwrap(), 5.0);
2793    }
2794
2795    #[test]
2796    fn test_find_index_expr_first_match() {
2797        let runtime = setup_runtime();
2798        let data = json!([1, 5, 10, 15]);
2799        let expr = runtime.compile("find_index_expr(&(@ > `3`), @)").unwrap();
2800        let result = expr.search(&data).unwrap();
2801        assert_eq!(result.as_f64().unwrap(), 1.0);
2802    }
2803
2804    #[test]
2805    fn test_take_while_basic() {
2806        let runtime = setup_runtime();
2807        let data = json!([1, 2, 3, 5, 1, 2]);
2808        let expr = runtime.compile("take_while(&(@ < `4`), @)").unwrap();
2809        let result = expr.search(&data).unwrap();
2810        let arr = result.as_array().unwrap();
2811        assert_eq!(arr.len(), 3);
2812        assert_eq!(arr[0].as_f64().unwrap(), 1.0);
2813        assert_eq!(arr[1].as_f64().unwrap(), 2.0);
2814        assert_eq!(arr[2].as_f64().unwrap(), 3.0);
2815    }
2816
2817    #[test]
2818    fn test_take_while_all_match() {
2819        let runtime = setup_runtime();
2820        let data = json!([1, 2, 3]);
2821        let expr = runtime.compile("take_while(&(@ < `10`), @)").unwrap();
2822        let result = expr.search(&data).unwrap();
2823        let arr = result.as_array().unwrap();
2824        assert_eq!(arr.len(), 3);
2825    }
2826
2827    #[test]
2828    fn test_take_while_none_match() {
2829        let runtime = setup_runtime();
2830        let data = json!([5, 6, 7]);
2831        let expr = runtime.compile("take_while(&(@ < `4`), @)").unwrap();
2832        let result = expr.search(&data).unwrap();
2833        let arr = result.as_array().unwrap();
2834        assert_eq!(arr.len(), 0);
2835    }
2836
2837    #[test]
2838    fn test_drop_while_basic() {
2839        let runtime = setup_runtime();
2840        let data = json!([1, 2, 3, 5, 1, 2]);
2841        let expr = runtime.compile("drop_while(&(@ < `4`), @)").unwrap();
2842        let result = expr.search(&data).unwrap();
2843        let arr = result.as_array().unwrap();
2844        assert_eq!(arr.len(), 3);
2845        assert_eq!(arr[0].as_f64().unwrap(), 5.0);
2846        assert_eq!(arr[1].as_f64().unwrap(), 1.0);
2847        assert_eq!(arr[2].as_f64().unwrap(), 2.0);
2848    }
2849
2850    #[test]
2851    fn test_drop_while_all_match() {
2852        let runtime = setup_runtime();
2853        let data = json!([1, 2, 3]);
2854        let expr = runtime.compile("drop_while(&(@ < `10`), @)").unwrap();
2855        let result = expr.search(&data).unwrap();
2856        let arr = result.as_array().unwrap();
2857        assert_eq!(arr.len(), 0);
2858    }
2859
2860    #[test]
2861    fn test_drop_while_none_match() {
2862        let runtime = setup_runtime();
2863        let data = json!([5, 6, 7]);
2864        let expr = runtime.compile("drop_while(&(@ < `4`), @)").unwrap();
2865        let result = expr.search(&data).unwrap();
2866        let arr = result.as_array().unwrap();
2867        assert_eq!(arr.len(), 3);
2868    }
2869
2870    #[test]
2871    fn test_zip_with_add() {
2872        let runtime = setup_runtime();
2873        let data = json!(null);
2874        let expr = runtime
2875            .compile("zip_with(&add([0], [1]), `[1, 2, 3]`, `[10, 20, 30]`)")
2876            .unwrap();
2877        let result = expr.search(&data).unwrap();
2878        let arr = result.as_array().unwrap();
2879        assert_eq!(arr.len(), 3);
2880        assert_eq!(arr[0].as_f64().unwrap(), 11.0);
2881        assert_eq!(arr[1].as_f64().unwrap(), 22.0);
2882        assert_eq!(arr[2].as_f64().unwrap(), 33.0);
2883    }
2884
2885    #[test]
2886    fn test_zip_with_unequal_lengths() {
2887        let runtime = setup_runtime();
2888        let data = json!(null);
2889        let expr = runtime
2890            .compile("zip_with(&add([0], [1]), `[1, 2, 3, 4, 5]`, `[10, 20]`)")
2891            .unwrap();
2892        let result = expr.search(&data).unwrap();
2893        let arr = result.as_array().unwrap();
2894        assert_eq!(arr.len(), 2);
2895        assert_eq!(arr[0].as_f64().unwrap(), 11.0);
2896        assert_eq!(arr[1].as_f64().unwrap(), 22.0);
2897    }
2898
2899    #[test]
2900    fn test_zip_with_multiply() {
2901        let runtime = setup_runtime();
2902        let data = json!(null);
2903        let expr = runtime
2904            .compile("zip_with(&multiply([0], [1]), `[2, 3, 4]`, `[5, 6, 7]`)")
2905            .unwrap();
2906        let result = expr.search(&data).unwrap();
2907        let arr = result.as_array().unwrap();
2908        assert_eq!(arr.len(), 3);
2909        assert_eq!(arr[0].as_f64().unwrap(), 10.0);
2910        assert_eq!(arr[1].as_f64().unwrap(), 18.0);
2911        assert_eq!(arr[2].as_f64().unwrap(), 28.0);
2912    }
2913
2914    // =========================================================================
2915    // walk tests
2916    // =========================================================================
2917
2918    #[test]
2919    fn test_walk_identity() {
2920        let runtime = setup_runtime();
2921        let data = json!({"a": [1, 2, 3], "b": {"c": 4}});
2922        let expr = runtime.compile("walk(&@, @)").unwrap();
2923        let result = expr.search(&data).unwrap();
2924        // Identity should return the same structure
2925        assert!(result.is_object());
2926        let obj = result.as_object().unwrap();
2927        assert!(obj.contains_key("a"));
2928        assert!(obj.contains_key("b"));
2929    }
2930
2931    #[test]
2932    fn test_walk_type_of_all() {
2933        let runtime = setup_runtime();
2934        let data = json!({"a": 5, "b": [1, 2]});
2935        // type() works on everything - shows bottom-up processing
2936        let expr = runtime.compile("walk(&type(@), @)").unwrap();
2937        let result = expr.search(&data).unwrap();
2938        // After walking, everything becomes its type string, and the final result
2939        // is type of the top-level result
2940        assert_eq!(result.as_str().unwrap(), "object");
2941    }
2942
2943    #[test]
2944    fn test_walk_nested_arrays() {
2945        let runtime = setup_runtime();
2946        // Use only arrays (no scalars inside) so length works at every level
2947        let data = json!([[[], []], [[]]]);
2948        // length works on arrays - get lengths at each level
2949        let expr = runtime.compile("walk(&length(@), @)").unwrap();
2950        let result = expr.search(&data).unwrap();
2951        // Inner [] -> 0, outer arrays get lengths, top level has 2 elements
2952        assert_eq!(result.as_f64().unwrap(), 2.0);
2953    }
2954
2955    #[test]
2956    fn test_walk_scalar() {
2957        let runtime = setup_runtime();
2958        let data = json!(5);
2959        // Double the number
2960        let expr = runtime.compile("walk(&multiply(@, `2`), @)").unwrap();
2961        let result = expr.search(&data).unwrap();
2962        assert_eq!(result.as_f64().unwrap(), 10.0);
2963    }
2964
2965    #[test]
2966    fn test_walk_length_all() {
2967        let runtime = setup_runtime();
2968        let data = json!({"items": ["a", "bb", "ccc"]});
2969        // Get length of everything (works for strings, arrays, objects)
2970        let expr = runtime.compile("walk(&length(@), @)").unwrap();
2971        let result = expr.search(&data).unwrap();
2972        // Top level object has 1 key
2973        assert_eq!(result.as_f64().unwrap(), 1.0);
2974    }
2975
2976    #[test]
2977    fn test_walk_preserves_structure() {
2978        let runtime = setup_runtime();
2979        let data = json!({"a": [1, {"b": 2}], "c": "hello"});
2980        // Identity transform - should preserve structure
2981        let expr = runtime.compile("walk(&@, @)").unwrap();
2982        let result = expr.search(&data).unwrap();
2983
2984        let obj = result.as_object().unwrap();
2985        assert!(obj.contains_key("a"));
2986        assert!(obj.contains_key("c"));
2987        let arr = obj.get("a").unwrap().as_array().unwrap();
2988        assert_eq!(arr.len(), 2);
2989    }
2990
2991    #[test]
2992    fn test_walk_empty_structures() {
2993        let runtime = setup_runtime();
2994
2995        // Empty array
2996        let data = json!([]);
2997        let expr = runtime.compile("walk(&@, @)").unwrap();
2998        let result = expr.search(&data).unwrap();
2999        assert!(result.as_array().unwrap().is_empty());
3000
3001        // Empty object
3002        let data = json!({});
3003        let result = expr.search(&data).unwrap();
3004        assert!(result.as_object().unwrap().is_empty());
3005    }
3006
3007    // =========================================================================
3008    // recurse tests
3009    // =========================================================================
3010
3011    #[test]
3012    fn test_recurse_nested_object() {
3013        let runtime = setup_runtime();
3014        let data = json!({"a": {"b": 1}});
3015        let expr = runtime.compile("recurse(@)").unwrap();
3016        let result = expr.search(&data).unwrap();
3017        let arr = result.as_array().unwrap();
3018        // Should have: {a: {b: 1}}, {b: 1}, 1
3019        assert_eq!(arr.len(), 3);
3020    }
3021
3022    #[test]
3023    fn test_recurse_nested_array() {
3024        let runtime = setup_runtime();
3025        let data = json!([1, [2, 3]]);
3026        let expr = runtime.compile("recurse(@)").unwrap();
3027        let result = expr.search(&data).unwrap();
3028        let arr = result.as_array().unwrap();
3029        // Should have: [1, [2, 3]], 1, [2, 3], 2, 3
3030        assert_eq!(arr.len(), 5);
3031    }
3032
3033    #[test]
3034    fn test_recurse_scalar() {
3035        let runtime = setup_runtime();
3036        let data = json!(42);
3037        let expr = runtime.compile("recurse(@)").unwrap();
3038        let result = expr.search(&data).unwrap();
3039        let arr = result.as_array().unwrap();
3040        // Just the scalar itself
3041        assert_eq!(arr.len(), 1);
3042        assert_eq!(arr[0].as_f64().unwrap(), 42.0);
3043    }
3044
3045    #[test]
3046    fn test_recurse_with_field() {
3047        let runtime = setup_runtime();
3048        let data = json!({"a": {"a": {"a": null}}});
3049        let expr = runtime.compile("recurse_with(@, &a)").unwrap();
3050        let result = expr.search(&data).unwrap();
3051        let arr = result.as_array().unwrap();
3052        // Should have: {a: {a: {a: null}}}, {a: {a: null}}, {a: null}
3053        assert_eq!(arr.len(), 3);
3054    }
3055
3056    #[test]
3057    fn test_recurse_with_children() {
3058        let runtime = setup_runtime();
3059        let data = json!({
3060            "name": "root",
3061            "children": [
3062                {"name": "child1", "children": []},
3063                {"name": "child2", "children": []}
3064            ]
3065        });
3066        let expr = runtime.compile("recurse_with(@, &children[])").unwrap();
3067        let result = expr.search(&data).unwrap();
3068        let arr = result.as_array().unwrap();
3069        // Should have: root, child1, child2
3070        assert_eq!(arr.len(), 3);
3071    }
3072
3073    // =========================================================================
3074    // while_expr tests
3075    // =========================================================================
3076
3077    #[test]
3078    fn test_while_expr_doubling() {
3079        let runtime = setup_runtime();
3080        let data = json!(null);
3081        let expr = runtime
3082            .compile("while_expr(`1`, &(@ < `100`), &multiply(@, `2`))")
3083            .unwrap();
3084        let result = expr.search(&data).unwrap();
3085        // 1 -> 2 -> 4 -> 8 -> 16 -> 32 -> 64 -> 128 (stops because 128 >= 100)
3086        assert_eq!(result.as_f64().unwrap(), 128.0);
3087    }
3088
3089    #[test]
3090    fn test_while_expr_counter() {
3091        let runtime = setup_runtime();
3092        let data = json!(null);
3093        let expr = runtime
3094            .compile("while_expr(`0`, &(@ < `5`), &add(@, `1`))")
3095            .unwrap();
3096        let result = expr.search(&data).unwrap();
3097        // 0 -> 1 -> 2 -> 3 -> 4 -> 5 (stops because 5 >= 5)
3098        assert_eq!(result.as_f64().unwrap(), 5.0);
3099    }
3100
3101    #[test]
3102    fn test_while_expr_immediate_false() {
3103        let runtime = setup_runtime();
3104        let data = json!(null);
3105        let expr = runtime
3106            .compile("while_expr(`100`, &(@ < `10`), &add(@, `1`))")
3107            .unwrap();
3108        let result = expr.search(&data).unwrap();
3109        // Condition is false immediately, return initial value
3110        assert_eq!(result.as_f64().unwrap(), 100.0);
3111    }
3112
3113    // =========================================================================
3114    // until_expr tests
3115    // =========================================================================
3116
3117    #[test]
3118    fn test_until_expr_doubling() {
3119        let runtime = setup_runtime();
3120        let data = json!(null);
3121        let expr = runtime
3122            .compile("until_expr(`1`, &(@ >= `100`), &multiply(@, `2`))")
3123            .unwrap();
3124        let result = expr.search(&data).unwrap();
3125        // 1 -> 2 -> 4 -> 8 -> 16 -> 32 -> 64 -> 128 (stops because 128 >= 100)
3126        assert_eq!(result.as_f64().unwrap(), 128.0);
3127    }
3128
3129    #[test]
3130    fn test_until_expr_counter() {
3131        let runtime = setup_runtime();
3132        let data = json!(null);
3133        let expr = runtime
3134            .compile("until_expr(`0`, &(@ == `5`), &add(@, `1`))")
3135            .unwrap();
3136        let result = expr.search(&data).unwrap();
3137        // 0 -> 1 -> 2 -> 3 -> 4 -> 5 (stops because 5 == 5)
3138        assert_eq!(result.as_f64().unwrap(), 5.0);
3139    }
3140
3141    #[test]
3142    fn test_until_expr_immediate_true() {
3143        let runtime = setup_runtime();
3144        let data = json!(null);
3145        let expr = runtime
3146            .compile("until_expr(`100`, &(@ > `10`), &add(@, `1`))")
3147            .unwrap();
3148        let result = expr.search(&data).unwrap();
3149        // Condition is true immediately, return initial value
3150        assert_eq!(result.as_f64().unwrap(), 100.0);
3151    }
3152
3153    #[test]
3154    fn test_rank_with_ties() {
3155        let runtime = setup_runtime();
3156        let data = json!([{"score": 90}, {"score": 85}, {"score": 90}]);
3157        let expr = runtime.compile("rank(&score, @)").unwrap();
3158        let result = expr.search(&data).unwrap();
3159        // 90 ties at rank 1, 85 is rank 3 (gap)
3160        assert_eq!(result, json!([1, 3, 1]));
3161    }
3162
3163    #[test]
3164    fn test_rank_no_ties() {
3165        let runtime = setup_runtime();
3166        let data = json!([{"score": 90}, {"score": 85}, {"score": 80}]);
3167        let expr = runtime.compile("rank(&score, @)").unwrap();
3168        let result = expr.search(&data).unwrap();
3169        assert_eq!(result, json!([1, 2, 3]));
3170    }
3171
3172    #[test]
3173    fn test_rank_empty_array() {
3174        let runtime = setup_runtime();
3175        let data = json!([]);
3176        let expr = runtime.compile("rank(&@, @)").unwrap();
3177        let result = expr.search(&data).unwrap();
3178        assert_eq!(result, json!([]));
3179    }
3180
3181    #[test]
3182    fn test_rank_all_same() {
3183        let runtime = setup_runtime();
3184        let data = json!([{"v": 5}, {"v": 5}, {"v": 5}]);
3185        let expr = runtime.compile("rank(&v, @)").unwrap();
3186        let result = expr.search(&data).unwrap();
3187        assert_eq!(result, json!([1, 1, 1]));
3188    }
3189
3190    #[test]
3191    fn test_rank_numbers() {
3192        let runtime = setup_runtime();
3193        let data = json!([3, 1, 2, 1]);
3194        let expr = runtime.compile("rank(&@, @)").unwrap();
3195        let result = expr.search(&data).unwrap();
3196        assert_eq!(result, json!([1, 3, 2, 3]));
3197    }
3198
3199    #[test]
3200    fn test_dense_rank_with_ties() {
3201        let runtime = setup_runtime();
3202        let data = json!([{"score": 90}, {"score": 85}, {"score": 90}]);
3203        let expr = runtime.compile("dense_rank(&score, @)").unwrap();
3204        let result = expr.search(&data).unwrap();
3205        // 90 ties at rank 1, 85 is rank 2 (no gap)
3206        assert_eq!(result, json!([1, 2, 1]));
3207    }
3208
3209    #[test]
3210    fn test_dense_rank_no_ties() {
3211        let runtime = setup_runtime();
3212        let data = json!([{"score": 90}, {"score": 85}, {"score": 80}]);
3213        let expr = runtime.compile("dense_rank(&score, @)").unwrap();
3214        let result = expr.search(&data).unwrap();
3215        assert_eq!(result, json!([1, 2, 3]));
3216    }
3217
3218    #[test]
3219    fn test_dense_rank_empty_array() {
3220        let runtime = setup_runtime();
3221        let data = json!([]);
3222        let expr = runtime.compile("dense_rank(&@, @)").unwrap();
3223        let result = expr.search(&data).unwrap();
3224        assert_eq!(result, json!([]));
3225    }
3226
3227    #[test]
3228    fn test_dense_rank_all_same() {
3229        let runtime = setup_runtime();
3230        let data = json!([{"v": 5}, {"v": 5}, {"v": 5}]);
3231        let expr = runtime.compile("dense_rank(&v, @)").unwrap();
3232        let result = expr.search(&data).unwrap();
3233        assert_eq!(result, json!([1, 1, 1]));
3234    }
3235
3236    #[test]
3237    fn test_dense_rank_numbers() {
3238        let runtime = setup_runtime();
3239        let data = json!([3, 1, 2, 1]);
3240        let expr = runtime.compile("dense_rank(&@, @)").unwrap();
3241        let result = expr.search(&data).unwrap();
3242        assert_eq!(result, json!([1, 3, 2, 3]));
3243    }
3244
3245    #[test]
3246    fn test_rank_vs_dense_rank() {
3247        let runtime = setup_runtime();
3248        let data = json!([{"s": 100}, {"s": 90}, {"s": 90}, {"s": 80}]);
3249
3250        let rank_expr = runtime.compile("rank(&s, @)").unwrap();
3251        let rank_result = rank_expr.search(&data).unwrap();
3252        // rank: 1, 2, 2, 4 (gap after tie)
3253        assert_eq!(rank_result, json!([1, 2, 2, 4]));
3254
3255        let dense_expr = runtime.compile("dense_rank(&s, @)").unwrap();
3256        let dense_result = dense_expr.search(&data).unwrap();
3257        // dense_rank: 1, 2, 2, 3 (no gap)
3258        assert_eq!(dense_result, json!([1, 2, 2, 3]));
3259    }
3260
3261    // =========================================================================
3262    // pivot tests
3263    // =========================================================================
3264
3265    #[test]
3266    fn test_pivot_basic() {
3267        let runtime = setup_runtime();
3268        let data = json!([
3269            {"name": "Alice", "score": 90},
3270            {"name": "Bob", "score": 85}
3271        ]);
3272        let expr = runtime.compile("pivot(@, &name, &score)").unwrap();
3273        let result = expr.search(&data).unwrap();
3274        assert_eq!(result, json!({"Alice": 90, "Bob": 85}));
3275    }
3276
3277    #[test]
3278    fn test_pivot_empty_array() {
3279        let runtime = setup_runtime();
3280        let data = json!([]);
3281        let expr = runtime.compile("pivot(@, &name, &score)").unwrap();
3282        let result = expr.search(&data).unwrap();
3283        assert_eq!(result, json!({}));
3284    }
3285
3286    #[test]
3287    fn test_pivot_numeric_keys() {
3288        let runtime = setup_runtime();
3289        let data = json!([
3290            {"id": 1, "value": "one"},
3291            {"id": 2, "value": "two"}
3292        ]);
3293        let expr = runtime.compile("pivot(@, &id, &value)").unwrap();
3294        let result = expr.search(&data).unwrap();
3295        assert_eq!(result, json!({"1": "one", "2": "two"}));
3296    }
3297
3298    #[test]
3299    fn test_pivot_duplicate_keys_last_wins() {
3300        let runtime = setup_runtime();
3301        let data = json!([
3302            {"name": "a", "v": 1},
3303            {"name": "a", "v": 2}
3304        ]);
3305        let expr = runtime.compile("pivot(@, &name, &v)").unwrap();
3306        let result = expr.search(&data).unwrap();
3307        assert_eq!(result, json!({"a": 2}));
3308    }
3309
3310    // =========================================================================
3311    // unpivot tests
3312    // =========================================================================
3313
3314    #[test]
3315    fn test_unpivot_basic() {
3316        let runtime = setup_runtime();
3317        let data = json!({"Alice": 90, "Bob": 85});
3318        let expr = runtime.compile("unpivot(@)").unwrap();
3319        let result = expr.search(&data).unwrap();
3320        let arr = result.as_array().unwrap();
3321        assert_eq!(arr.len(), 2);
3322        // Check that both entries exist (order may vary in JSON objects)
3323        let has_alice = arr
3324            .iter()
3325            .any(|v| v == &json!({"key": "Alice", "value": 90}));
3326        let has_bob = arr.iter().any(|v| v == &json!({"key": "Bob", "value": 85}));
3327        assert!(has_alice);
3328        assert!(has_bob);
3329    }
3330
3331    #[test]
3332    fn test_unpivot_empty_object() {
3333        let runtime = setup_runtime();
3334        let data = json!({});
3335        let expr = runtime.compile("unpivot(@)").unwrap();
3336        let result = expr.search(&data).unwrap();
3337        assert_eq!(result, json!([]));
3338    }
3339
3340    #[test]
3341    fn test_pivot_unpivot_roundtrip() {
3342        let runtime = setup_runtime();
3343        let data = json!([
3344            {"name": "x", "val": 10},
3345            {"name": "y", "val": 20}
3346        ]);
3347        // Pivot then check we get an object
3348        let expr = runtime.compile("pivot(@, &name, &val)").unwrap();
3349        let pivoted = expr.search(&data).unwrap();
3350        assert_eq!(pivoted, json!({"x": 10, "y": 20}));
3351
3352        // Unpivot it back
3353        let expr2 = runtime.compile("unpivot(@)").unwrap();
3354        let unpivoted = expr2.search(&pivoted).unwrap();
3355        let arr = unpivoted.as_array().unwrap();
3356        assert_eq!(arr.len(), 2);
3357        let has_x = arr.iter().any(|v| v == &json!({"key": "x", "value": 10}));
3358        let has_y = arr.iter().any(|v| v == &json!({"key": "y", "value": 20}));
3359        assert!(has_x);
3360        assert!(has_y);
3361    }
3362}