Skip to main content

presentar_yaml/
executor.rs

1//! Expression executor for data transformations.
2
3use crate::expression::{AggregateOp, Expression, RankMethod, Transform};
4use std::collections::HashMap;
5
6/// A generic value that can hold any data type.
7#[derive(Debug, Clone, PartialEq, Default)]
8pub enum Value {
9    /// Null value
10    #[default]
11    Null,
12    /// Boolean value
13    Bool(bool),
14    /// Numeric value
15    Number(f64),
16    /// String value
17    String(String),
18    /// Array of values
19    Array(Vec<Self>),
20    /// Object (key-value map)
21    Object(HashMap<String, Self>),
22}
23
24impl Value {
25    /// Create a new null value.
26    #[must_use]
27    pub const fn null() -> Self {
28        Self::Null
29    }
30
31    /// Create a new boolean value.
32    #[must_use]
33    pub const fn bool(v: bool) -> Self {
34        Self::Bool(v)
35    }
36
37    /// Create a new number value.
38    #[must_use]
39    pub const fn number(v: f64) -> Self {
40        Self::Number(v)
41    }
42
43    /// Create a new string value.
44    #[must_use]
45    pub fn string(v: impl Into<String>) -> Self {
46        Self::String(v.into())
47    }
48
49    /// Create a new array value.
50    #[must_use]
51    pub const fn array(v: Vec<Self>) -> Self {
52        Self::Array(v)
53    }
54
55    /// Create a new object value.
56    #[must_use]
57    pub const fn object(v: HashMap<String, Self>) -> Self {
58        Self::Object(v)
59    }
60
61    /// Check if value is null.
62    #[must_use]
63    pub const fn is_null(&self) -> bool {
64        matches!(self, Self::Null)
65    }
66
67    /// Check if value is a boolean.
68    #[must_use]
69    pub const fn is_bool(&self) -> bool {
70        matches!(self, Self::Bool(_))
71    }
72
73    /// Check if value is a number.
74    #[must_use]
75    pub const fn is_number(&self) -> bool {
76        matches!(self, Self::Number(_))
77    }
78
79    /// Check if value is a string.
80    #[must_use]
81    pub const fn is_string(&self) -> bool {
82        matches!(self, Self::String(_))
83    }
84
85    /// Check if value is an array.
86    #[must_use]
87    pub const fn is_array(&self) -> bool {
88        matches!(self, Self::Array(_))
89    }
90
91    /// Check if value is an object.
92    #[must_use]
93    pub const fn is_object(&self) -> bool {
94        matches!(self, Self::Object(_))
95    }
96
97    /// Get as boolean.
98    #[must_use]
99    pub const fn as_bool(&self) -> Option<bool> {
100        match self {
101            Self::Bool(v) => Some(*v),
102            _ => None,
103        }
104    }
105
106    /// Get as number.
107    #[must_use]
108    pub const fn as_number(&self) -> Option<f64> {
109        match self {
110            Self::Number(v) => Some(*v),
111            _ => None,
112        }
113    }
114
115    /// Get as string.
116    #[must_use]
117    pub fn as_str(&self) -> Option<&str> {
118        match self {
119            Self::String(v) => Some(v),
120            _ => None,
121        }
122    }
123
124    /// Get as array.
125    #[must_use]
126    pub const fn as_array(&self) -> Option<&Vec<Self>> {
127        match self {
128            Self::Array(v) => Some(v),
129            _ => None,
130        }
131    }
132
133    /// Get as mutable array.
134    #[must_use]
135    pub fn as_array_mut(&mut self) -> Option<&mut Vec<Self>> {
136        match self {
137            Self::Array(v) => Some(v),
138            _ => None,
139        }
140    }
141
142    /// Get as object.
143    #[must_use]
144    pub const fn as_object(&self) -> Option<&HashMap<String, Self>> {
145        match self {
146            Self::Object(v) => Some(v),
147            _ => None,
148        }
149    }
150
151    /// Get field from object.
152    #[must_use]
153    pub fn get(&self, key: &str) -> Option<&Self> {
154        match self {
155            Self::Object(map) => map.get(key),
156            _ => None,
157        }
158    }
159
160    /// Get as array or return `ExpectedArray` error (DRY helper for apply_ methods).
161    pub fn require_array(&self) -> Result<&Vec<Self>, ExecutionError> {
162        self.as_array().ok_or(ExecutionError::ExpectedArray)
163    }
164
165    /// Extract numeric values from array items by field name.
166    pub fn extract_numbers(&self, field: &str) -> Result<Vec<f64>, ExecutionError> {
167        Ok(self
168            .require_array()?
169            .iter()
170            .filter_map(|item| item.get(field)?.as_number())
171            .collect())
172    }
173
174    /// Get array length or object key count.
175    #[must_use]
176    pub fn len(&self) -> usize {
177        match self {
178            Self::Array(arr) => arr.len(),
179            Self::Object(obj) => obj.len(),
180            Self::String(s) => s.len(),
181            _ => 0,
182        }
183    }
184
185    /// Check if array or object is empty.
186    #[must_use]
187    pub fn is_empty(&self) -> bool {
188        self.len() == 0
189    }
190}
191
192impl From<bool> for Value {
193    fn from(v: bool) -> Self {
194        Self::Bool(v)
195    }
196}
197
198impl From<f64> for Value {
199    fn from(v: f64) -> Self {
200        Self::Number(v)
201    }
202}
203
204impl From<i32> for Value {
205    fn from(v: i32) -> Self {
206        Self::Number(f64::from(v))
207    }
208}
209
210impl From<i64> for Value {
211    fn from(v: i64) -> Self {
212        Self::Number(v as f64)
213    }
214}
215
216impl From<&str> for Value {
217    fn from(v: &str) -> Self {
218        Self::String(v.to_string())
219    }
220}
221
222impl From<String> for Value {
223    fn from(v: String) -> Self {
224        Self::String(v)
225    }
226}
227
228impl<T: Into<Self>> From<Vec<T>> for Value {
229    fn from(v: Vec<T>) -> Self {
230        Self::Array(v.into_iter().map(Into::into).collect())
231    }
232}
233
234/// Execution error.
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub enum ExecutionError {
237    /// Source not found in data context
238    SourceNotFound(String),
239    /// Expected an array for this transform
240    ExpectedArray,
241    /// Expected an object
242    ExpectedObject,
243    /// Field not found
244    FieldNotFound(String),
245    /// Type mismatch
246    TypeMismatch(String),
247    /// Invalid transform
248    InvalidTransform(String),
249}
250
251impl std::fmt::Display for ExecutionError {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        match self {
254            Self::SourceNotFound(name) => write!(f, "source not found: {name}"),
255            Self::ExpectedArray => write!(f, "expected an array"),
256            Self::ExpectedObject => write!(f, "expected an object"),
257            Self::FieldNotFound(name) => write!(f, "field not found: {name}"),
258            Self::TypeMismatch(msg) => write!(f, "type mismatch: {msg}"),
259            Self::InvalidTransform(msg) => write!(f, "invalid transform: {msg}"),
260        }
261    }
262}
263
264impl std::error::Error for ExecutionError {}
265
266/// Data context for expression execution.
267#[derive(Debug, Clone, Default)]
268pub struct DataContext {
269    /// Named data sources
270    sources: HashMap<String, Value>,
271}
272
273impl DataContext {
274    /// Create a new empty context.
275    #[must_use]
276    pub fn new() -> Self {
277        Self::default()
278    }
279
280    /// Add a data source.
281    pub fn insert(&mut self, name: impl Into<String>, value: Value) {
282        self.sources.insert(name.into(), value);
283    }
284
285    /// Get a data source.
286    #[must_use]
287    pub fn get(&self, name: &str) -> Option<&Value> {
288        // Support dotted paths like "data.transactions"
289        let parts: Vec<&str> = name.split('.').collect();
290        let mut current = self.sources.get(parts[0])?;
291
292        for part in &parts[1..] {
293            current = current.get(part)?;
294        }
295
296        Some(current)
297    }
298
299    /// Check if context has a source.
300    #[must_use]
301    pub fn contains(&self, name: &str) -> bool {
302        self.get(name).is_some()
303    }
304}
305
306/// Expression executor.
307#[derive(Debug, Default)]
308pub struct ExpressionExecutor;
309
310impl ExpressionExecutor {
311    /// Create a new executor.
312    #[must_use]
313    pub const fn new() -> Self {
314        Self
315    }
316
317    /// Execute an expression against a data context.
318    ///
319    /// # Errors
320    ///
321    /// Returns an error if execution fails.
322    pub fn execute(&self, expr: &Expression, ctx: &DataContext) -> Result<Value, ExecutionError> {
323        // Resolve source
324        let mut value = ctx
325            .get(&expr.source)
326            .cloned()
327            .ok_or_else(|| ExecutionError::SourceNotFound(expr.source.clone()))?;
328
329        // Apply transforms
330        for transform in &expr.transforms {
331            value = self.apply_transform(&value, transform, ctx)?;
332        }
333
334        Ok(value)
335    }
336
337    fn apply_transform(
338        &self,
339        value: &Value,
340        transform: &Transform,
341        ctx: &DataContext,
342    ) -> Result<Value, ExecutionError> {
343        match transform {
344            Transform::Filter {
345                field,
346                value: match_value,
347            } => self.apply_filter(value, field, match_value),
348            Transform::Select { fields } => self.apply_select(value, fields),
349            Transform::Sort { field, desc } => self.apply_sort(value, field, *desc),
350            Transform::Count => Ok(self.apply_count(value)),
351            Transform::Sum { field } => self.apply_sum(value, field),
352            Transform::Mean { field } => self.apply_mean(value, field),
353            Transform::Sample { n } => self.apply_sample(value, *n),
354            Transform::Percentage => self.apply_percentage(value),
355            Transform::Rate { window } => self.apply_rate(value, window),
356            Transform::Join { other, on } => self.apply_join(value, other, on, ctx),
357            Transform::GroupBy { field } => self.apply_group_by(value, field),
358            Transform::Distinct { field } => self.apply_distinct(value, field.as_deref()),
359            Transform::Where {
360                field,
361                op,
362                value: match_value,
363            } => self.apply_where(value, field, op, match_value),
364            Transform::Offset { n } => self.apply_offset(value, *n),
365            Transform::Min { field } => self.apply_min(value, field),
366            Transform::Max { field } => self.apply_max(value, field),
367            Transform::First { n } | Transform::Limit { n } => self.apply_limit(value, *n),
368            Transform::Last { n } => self.apply_last(value, *n),
369            Transform::Flatten => self.apply_flatten(value),
370            Transform::Reverse => self.apply_reverse(value),
371            // New transforms
372            Transform::Map { expr } => self.apply_map(value, expr),
373            Transform::Reduce { initial, expr } => self.apply_reduce(value, initial, expr),
374            Transform::Aggregate { field, op } => self.apply_aggregate(value, field, *op),
375            Transform::Pivot {
376                row_field,
377                col_field,
378                value_field,
379            } => self.apply_pivot(value, row_field, col_field, value_field),
380            Transform::CumulativeSum { field } => self.apply_cumsum(value, field),
381            Transform::Rank { field, method } => self.apply_rank(value, field, *method),
382            Transform::MovingAverage { field, window } => {
383                self.apply_moving_avg(value, field, *window)
384            }
385            Transform::PercentChange { field } => self.apply_pct_change(value, field),
386            Transform::Suggest { prefix, count } => self.apply_suggest(value, prefix, *count),
387        }
388    }
389
390    fn apply_filter(
391        &self,
392        value: &Value,
393        field: &str,
394        match_value: &str,
395    ) -> Result<Value, ExecutionError> {
396        let arr = value.require_array()?;
397
398        let filtered: Vec<Value> = arr
399            .iter()
400            .filter(|item| {
401                if let Some(obj) = item.as_object() {
402                    if let Some(val) = obj.get(field) {
403                        return self.value_matches(val, match_value);
404                    }
405                }
406                false
407            })
408            .cloned()
409            .collect();
410
411        Ok(Value::Array(filtered))
412    }
413
414    fn value_matches(&self, value: &Value, target: &str) -> bool {
415        match value {
416            Value::String(s) => s == target,
417            Value::Number(n) => {
418                if let Ok(t) = target.parse::<f64>() {
419                    (*n - t).abs() < f64::EPSILON
420                } else {
421                    false
422                }
423            }
424            Value::Bool(b) => {
425                matches!((b, target), (true, "true") | (false, "false"))
426            }
427            _ => false,
428        }
429    }
430
431    fn apply_select(&self, value: &Value, fields: &[String]) -> Result<Value, ExecutionError> {
432        let arr = value.require_array()?;
433
434        let selected: Vec<Value> = arr
435            .iter()
436            .map(|item| {
437                if let Some(obj) = item.as_object() {
438                    let mut new_obj = HashMap::new();
439                    for field in fields {
440                        if let Some(val) = obj.get(field) {
441                            new_obj.insert(field.clone(), val.clone());
442                        }
443                    }
444                    Value::Object(new_obj)
445                } else {
446                    item.clone()
447                }
448            })
449            .collect();
450
451        Ok(Value::Array(selected))
452    }
453
454    fn apply_sort(&self, value: &Value, field: &str, desc: bool) -> Result<Value, ExecutionError> {
455        let arr = value.require_array()?;
456        let mut sorted = arr.clone();
457
458        sorted.sort_by(|a, b| {
459            let a_val = a.get(field);
460            let b_val = b.get(field);
461
462            let cmp = match (a_val, b_val) {
463                (Some(Value::Number(a)), Some(Value::Number(b))) => {
464                    a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
465                }
466                (Some(Value::String(a)), Some(Value::String(b))) => a.cmp(b),
467                _ => std::cmp::Ordering::Equal,
468            };
469
470            if desc {
471                cmp.reverse()
472            } else {
473                cmp
474            }
475        });
476
477        Ok(Value::Array(sorted))
478    }
479
480    fn apply_limit(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
481        let arr = value.require_array()?;
482        Ok(Value::Array(arr.iter().take(n).cloned().collect()))
483    }
484
485    fn apply_count(&self, value: &Value) -> Value {
486        match value {
487            Value::Array(arr) => Value::Number(arr.len() as f64),
488            Value::Object(obj) => Value::Number(obj.len() as f64),
489            Value::String(s) => Value::Number(s.len() as f64),
490            _ => Value::Number(0.0),
491        }
492    }
493
494    fn apply_sum(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
495        let nums = value.extract_numbers(field)?;
496        Ok(Value::Number(nums.iter().sum()))
497    }
498
499    fn apply_mean(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
500        let nums = value.extract_numbers(field)?;
501        if nums.is_empty() {
502            return Ok(Value::Number(0.0));
503        }
504        Ok(Value::Number(nums.iter().sum::<f64>() / nums.len() as f64))
505    }
506
507    fn apply_sample(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
508        let arr = value.require_array()?;
509
510        // Simple deterministic "sampling" - just take first n elements
511        // Real implementation would use random sampling
512        Ok(Value::Array(arr.iter().take(n).cloned().collect()))
513    }
514
515    fn apply_percentage(&self, value: &Value) -> Result<Value, ExecutionError> {
516        match value {
517            Value::Number(n) => Ok(Value::Number(n * 100.0)),
518            _ => Err(ExecutionError::TypeMismatch(
519                "percentage requires a number".to_string(),
520            )),
521        }
522    }
523
524    fn apply_rate(&self, value: &Value, window: &str) -> Result<Value, ExecutionError> {
525        let arr = value.require_array()?;
526
527        // Parse window (e.g., "1m", "5m", "1h")
528        let window_ms = self.parse_window(window)?;
529
530        // For rate calculation, we need timestamped data
531        // Look for a "timestamp" or "time" field
532        let mut values_with_time: Vec<(f64, f64)> = arr
533            .iter()
534            .filter_map(|item| {
535                let obj = item.as_object()?;
536                let time = obj
537                    .get("timestamp")
538                    .or_else(|| obj.get("time"))
539                    .and_then(Value::as_number)?;
540                let val = obj
541                    .get("value")
542                    .or_else(|| obj.get("count"))
543                    .and_then(Value::as_number)
544                    .unwrap_or(1.0);
545                Some((time, val))
546            })
547            .collect();
548
549        if values_with_time.len() < 2 {
550            return Ok(Value::Number(0.0));
551        }
552
553        // Sort by time
554        values_with_time.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
555
556        // Calculate rate over the window
557        let window_ms_f64 = window_ms as f64;
558        let last_time = values_with_time.last().map_or(0.0, |v| v.0);
559        let window_start = last_time - window_ms_f64;
560
561        let sum_in_window: f64 = values_with_time
562            .iter()
563            .filter(|(t, _)| *t >= window_start)
564            .map(|(_, v)| v)
565            .sum();
566
567        // Rate per second
568        let rate = sum_in_window / (window_ms_f64 / 1000.0);
569
570        Ok(Value::Number(rate))
571    }
572
573    fn parse_window(&self, window: &str) -> Result<u64, ExecutionError> {
574        let window = window.trim();
575        if window.is_empty() {
576            return Err(ExecutionError::InvalidTransform("empty window".to_string()));
577        }
578
579        let (num_str, unit) = if let Some(s) = window.strip_suffix("ms") {
580            (s, "ms")
581        } else if let Some(s) = window.strip_suffix('s') {
582            (s, "s")
583        } else if let Some(s) = window.strip_suffix('m') {
584            (s, "m")
585        } else if let Some(s) = window.strip_suffix('h') {
586            (s, "h")
587        } else if let Some(s) = window.strip_suffix('d') {
588            (s, "d")
589        } else {
590            // Assume milliseconds if no unit
591            (window, "ms")
592        };
593
594        let num: u64 = num_str
595            .parse()
596            .map_err(|_| ExecutionError::InvalidTransform(format!("invalid window: {window}")))?;
597
598        let ms = match unit {
599            "s" => num * 1000,
600            "m" => num * 60 * 1000,
601            "h" => num * 60 * 60 * 1000,
602            "d" => num * 24 * 60 * 60 * 1000,
603            // "ms" and any other unit default to num (milliseconds)
604            _ => num,
605        };
606
607        Ok(ms)
608    }
609
610    fn apply_join(
611        &self,
612        value: &Value,
613        other: &str,
614        on: &str,
615        ctx: &DataContext,
616    ) -> Result<Value, ExecutionError> {
617        let left_arr = value.require_array()?;
618
619        // Get the other dataset from context
620        let right_value = ctx
621            .get(other)
622            .ok_or_else(|| ExecutionError::SourceNotFound(other.to_string()))?;
623        let right_arr = right_value
624            .as_array()
625            .ok_or(ExecutionError::ExpectedArray)?;
626
627        // Build a lookup map for the right side (keyed by the join field)
628        let mut right_lookup: HashMap<String, Vec<&Value>> = HashMap::new();
629        for item in right_arr {
630            if let Some(obj) = item.as_object() {
631                if let Some(key_val) = obj.get(on) {
632                    let key = self.value_to_string(key_val);
633                    right_lookup.entry(key).or_default().push(item);
634                }
635            }
636        }
637
638        // Perform the join (left join - keeps all left items)
639        let mut result = Vec::new();
640        for left_item in left_arr {
641            if let Some(left_obj) = left_item.as_object() {
642                if let Some(key_val) = left_obj.get(on) {
643                    let key = self.value_to_string(key_val);
644                    if let Some(right_items) = right_lookup.get(&key) {
645                        // Join with each matching right item
646                        for right_item in right_items {
647                            if let Some(right_obj) = right_item.as_object() {
648                                // Merge left and right objects
649                                let mut merged = left_obj.clone();
650                                for (k, v) in right_obj {
651                                    // Don't overwrite left values, prefix with source name
652                                    if merged.contains_key(k) && k != on {
653                                        merged.insert(format!("{other}_{k}"), v.clone());
654                                    } else if k != on {
655                                        merged.insert(k.clone(), v.clone());
656                                    }
657                                }
658                                result.push(Value::Object(merged));
659                            }
660                        }
661                    } else {
662                        // No match, keep left item as-is (left join behavior)
663                        result.push(left_item.clone());
664                    }
665                } else {
666                    // No join key, keep as-is
667                    result.push(left_item.clone());
668                }
669            } else {
670                // Not an object, keep as-is
671                result.push(left_item.clone());
672            }
673        }
674
675        Ok(Value::Array(result))
676    }
677
678    fn apply_group_by(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
679        let arr = value.require_array()?;
680
681        let mut groups: HashMap<String, Vec<Value>> = HashMap::new();
682
683        for item in arr {
684            let key = if let Some(obj) = item.as_object() {
685                if let Some(val) = obj.get(field) {
686                    self.value_to_string(val)
687                } else {
688                    "_null".to_string()
689                }
690            } else {
691                "_null".to_string()
692            };
693
694            groups.entry(key).or_default().push(item.clone());
695        }
696
697        // Convert to array of objects with key and items
698        let result: Vec<Value> = groups
699            .into_iter()
700            .map(|(key, items)| {
701                let mut obj = HashMap::new();
702                obj.insert("key".to_string(), Value::String(key));
703                obj.insert("items".to_string(), Value::Array(items.clone()));
704                obj.insert("count".to_string(), Value::Number(items.len() as f64));
705                Value::Object(obj)
706            })
707            .collect();
708
709        Ok(Value::Array(result))
710    }
711
712    fn value_to_string(&self, value: &Value) -> String {
713        match value {
714            Value::Null => "_null".to_string(),
715            Value::Bool(b) => b.to_string(),
716            Value::Number(n) => n.to_string(),
717            Value::String(s) => s.clone(),
718            Value::Array(_) => "_array".to_string(),
719            Value::Object(_) => "_object".to_string(),
720        }
721    }
722
723    fn apply_distinct(&self, value: &Value, field: Option<&str>) -> Result<Value, ExecutionError> {
724        let arr = value.require_array()?;
725
726        let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
727        let mut result = Vec::new();
728
729        for item in arr {
730            let key = if let Some(f) = field {
731                if let Some(obj) = item.as_object() {
732                    obj.get(f)
733                        .map(|v| self.value_to_string(v))
734                        .unwrap_or_default()
735                } else {
736                    self.value_to_string(item)
737                }
738            } else {
739                self.value_to_string(item)
740            };
741
742            if seen.insert(key) {
743                result.push(item.clone());
744            }
745        }
746
747        Ok(Value::Array(result))
748    }
749
750    fn apply_where(
751        &self,
752        value: &Value,
753        field: &str,
754        op: &str,
755        match_value: &str,
756    ) -> Result<Value, ExecutionError> {
757        let arr = value.require_array()?;
758
759        let filtered: Vec<Value> = arr
760            .iter()
761            .filter(|item| {
762                if let Some(obj) = item.as_object() {
763                    if let Some(val) = obj.get(field) {
764                        return self.compare_values(val, op, match_value);
765                    }
766                }
767                false
768            })
769            .cloned()
770            .collect();
771
772        Ok(Value::Array(filtered))
773    }
774
775    fn compare_values(&self, value: &Value, op: &str, target: &str) -> bool {
776        match op {
777            "eq" | "==" | "=" => self.value_matches(value, target),
778            "ne" | "!=" | "<>" => !self.value_matches(value, target),
779            "gt" | ">" => {
780                if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
781                    v > t
782                } else {
783                    false
784                }
785            }
786            "lt" | "<" => {
787                if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
788                    v < t
789                } else {
790                    false
791                }
792            }
793            "gte" | ">=" => {
794                if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
795                    v >= t
796                } else {
797                    false
798                }
799            }
800            "lte" | "<=" => {
801                if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
802                    v <= t
803                } else {
804                    false
805                }
806            }
807            "contains" => {
808                if let Some(s) = value.as_str() {
809                    s.contains(target)
810                } else {
811                    false
812                }
813            }
814            "starts_with" => {
815                if let Some(s) = value.as_str() {
816                    s.starts_with(target)
817                } else {
818                    false
819                }
820            }
821            "ends_with" => {
822                if let Some(s) = value.as_str() {
823                    s.ends_with(target)
824                } else {
825                    false
826                }
827            }
828            _ => false,
829        }
830    }
831
832    fn apply_offset(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
833        let arr = value.require_array()?;
834        Ok(Value::Array(arr.iter().skip(n).cloned().collect()))
835    }
836
837    fn apply_min(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
838        let nums = value.extract_numbers(field)?;
839        let min = nums.iter().copied().fold(f64::INFINITY, f64::min);
840        Ok(if min.is_infinite() {
841            Value::Null
842        } else {
843            Value::Number(min)
844        })
845    }
846
847    fn apply_max(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
848        let nums = value.extract_numbers(field)?;
849        let max = nums.iter().copied().fold(f64::NEG_INFINITY, f64::max);
850        Ok(if max.is_infinite() {
851            Value::Null
852        } else {
853            Value::Number(max)
854        })
855    }
856
857    fn apply_last(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
858        let arr = value.require_array()?;
859        let len = arr.len();
860        let skip = len.saturating_sub(n);
861        Ok(Value::Array(arr.iter().skip(skip).cloned().collect()))
862    }
863
864    fn apply_flatten(&self, value: &Value) -> Result<Value, ExecutionError> {
865        let arr = value.require_array()?;
866
867        let mut result = Vec::new();
868        for item in arr {
869            if let Some(inner) = item.as_array() {
870                result.extend(inner.iter().cloned());
871            } else {
872                result.push(item.clone());
873            }
874        }
875
876        Ok(Value::Array(result))
877    }
878
879    fn apply_reverse(&self, value: &Value) -> Result<Value, ExecutionError> {
880        let arr = value.require_array()?;
881        let mut reversed = arr.clone();
882        reversed.reverse();
883        Ok(Value::Array(reversed))
884    }
885
886    // =========================================================================
887    // New Transform Implementations
888    // =========================================================================
889
890    fn apply_map(&self, value: &Value, expr: &str) -> Result<Value, ExecutionError> {
891        let arr = value.require_array()?;
892
893        // Simple expression evaluation: extract field if expr is "item.field"
894        // For complex expressions, this would need a proper expression evaluator
895        let mapped: Vec<Value> = arr
896            .iter()
897            .map(|item| {
898                // Handle simple field access like "item.field"
899                if let Some(field) = expr.strip_prefix("item.") {
900                    if let Some(obj) = item.as_object() {
901                        obj.get(field).cloned().unwrap_or(Value::Null)
902                    } else {
903                        item.clone()
904                    }
905                } else {
906                    // Return item unchanged if we can't parse the expression
907                    item.clone()
908                }
909            })
910            .collect();
911
912        Ok(Value::Array(mapped))
913    }
914
915    fn apply_reduce(
916        &self,
917        value: &Value,
918        initial: &str,
919        _expr: &str,
920    ) -> Result<Value, ExecutionError> {
921        let arr = value.require_array()?;
922
923        // Parse initial value
924        let mut acc: f64 = initial.parse().unwrap_or(0.0);
925
926        // Simple sum reduction (a proper implementation would evaluate the expr)
927        for item in arr {
928            if let Some(n) = item.as_number() {
929                acc += n;
930            }
931        }
932
933        Ok(Value::Number(acc))
934    }
935
936    fn apply_aggregate(
937        &self,
938        value: &Value,
939        field: &str,
940        op: AggregateOp,
941    ) -> Result<Value, ExecutionError> {
942        let arr = value.require_array()?;
943
944        // For grouped data, expect array of {key: ..., values: [...]}
945        // For ungrouped data, operate on the field directly
946
947        let values: Vec<f64> = arr
948            .iter()
949            .filter_map(|item| {
950                if let Some(obj) = item.as_object() {
951                    // If this is a group with "values" key
952                    if let Some(Value::Array(group_values)) = obj.get("values") {
953                        return Some(
954                            group_values
955                                .iter()
956                                .filter_map(|v| v.get(field)?.as_number())
957                                .collect::<Vec<_>>(),
958                        );
959                    }
960                    // Direct field access
961                    obj.get(field)?.as_number().map(|n| vec![n])
962                } else {
963                    None
964                }
965            })
966            .flatten()
967            .collect();
968
969        let result = match op {
970            AggregateOp::Sum => values.iter().sum(),
971            AggregateOp::Count => values.len() as f64,
972            AggregateOp::Mean => {
973                if values.is_empty() {
974                    0.0
975                } else {
976                    values.iter().sum::<f64>() / values.len() as f64
977                }
978            }
979            AggregateOp::Min => values.iter().cloned().fold(f64::INFINITY, f64::min),
980            AggregateOp::Max => values.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
981            AggregateOp::First => values.first().copied().unwrap_or(0.0),
982            AggregateOp::Last => values.last().copied().unwrap_or(0.0),
983        };
984
985        Ok(Value::Number(result))
986    }
987
988    fn apply_pivot(
989        &self,
990        value: &Value,
991        row_field: &str,
992        col_field: &str,
993        value_field: &str,
994    ) -> Result<Value, ExecutionError> {
995        let arr = value.require_array()?;
996
997        // Build pivot table
998        let mut rows: HashMap<String, HashMap<String, f64>> = HashMap::new();
999
1000        for item in arr {
1001            if let Some(obj) = item.as_object() {
1002                let row_key = obj
1003                    .get(row_field)
1004                    .map(|v| self.value_to_string(v))
1005                    .unwrap_or_default();
1006                let col_key = obj
1007                    .get(col_field)
1008                    .map(|v| self.value_to_string(v))
1009                    .unwrap_or_default();
1010                let val = obj
1011                    .get(value_field)
1012                    .and_then(|v| v.as_number())
1013                    .unwrap_or(0.0);
1014
1015                rows.entry(row_key)
1016                    .or_default()
1017                    .entry(col_key)
1018                    .and_modify(|v| *v += val)
1019                    .or_insert(val);
1020            }
1021        }
1022
1023        // Convert to array of objects
1024        let result: Vec<Value> = rows
1025            .into_iter()
1026            .map(|(row_key, cols)| {
1027                let mut obj = HashMap::new();
1028                obj.insert(row_field.to_string(), Value::String(row_key));
1029                for (col_key, val) in cols {
1030                    obj.insert(col_key, Value::Number(val));
1031                }
1032                Value::Object(obj)
1033            })
1034            .collect();
1035
1036        Ok(Value::Array(result))
1037    }
1038
1039    fn apply_cumsum(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
1040        let arr = value.require_array()?;
1041
1042        let mut running_sum = 0.0;
1043        let result: Vec<Value> = arr
1044            .iter()
1045            .map(|item| {
1046                if let Some(obj) = item.as_object() {
1047                    let val = obj.get(field).and_then(|v| v.as_number()).unwrap_or(0.0);
1048                    running_sum += val;
1049
1050                    let mut new_obj = obj.clone();
1051                    new_obj.insert(format!("{field}_cumsum"), Value::Number(running_sum));
1052                    Value::Object(new_obj)
1053                } else {
1054                    item.clone()
1055                }
1056            })
1057            .collect();
1058
1059        Ok(Value::Array(result))
1060    }
1061
1062    fn apply_rank(
1063        &self,
1064        value: &Value,
1065        field: &str,
1066        method: RankMethod,
1067    ) -> Result<Value, ExecutionError> {
1068        let arr = value.require_array()?;
1069
1070        // Extract values with indices
1071        let mut indexed: Vec<(usize, f64)> = arr
1072            .iter()
1073            .enumerate()
1074            .filter_map(|(i, item)| item.as_object()?.get(field)?.as_number().map(|n| (i, n)))
1075            .collect();
1076
1077        // Sort by value (descending for ranking)
1078        indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1079
1080        // Assign ranks based on method
1081        let mut ranks = vec![0.0; arr.len()];
1082        match method {
1083            RankMethod::Dense => {
1084                let mut rank = 0;
1085                let mut prev_val: Option<f64> = None;
1086                for (i, val) in indexed {
1087                    if prev_val != Some(val) {
1088                        rank += 1;
1089                    }
1090                    ranks[i] = rank as f64;
1091                    prev_val = Some(val);
1092                }
1093            }
1094            RankMethod::Ordinal => {
1095                for (rank, (i, _)) in indexed.iter().enumerate() {
1096                    ranks[*i] = (rank + 1) as f64;
1097                }
1098            }
1099            RankMethod::Average => {
1100                let mut i = 0;
1101                while i < indexed.len() {
1102                    let val = indexed[i].1;
1103                    let start = i;
1104                    while i < indexed.len() && (indexed[i].1 - val).abs() < f64::EPSILON {
1105                        i += 1;
1106                    }
1107                    let avg_rank =
1108                        (start + 1..=i).map(|r| r as f64).sum::<f64>() / (i - start) as f64;
1109                    for j in start..i {
1110                        ranks[indexed[j].0] = avg_rank;
1111                    }
1112                }
1113            }
1114        }
1115
1116        // Add rank to each object
1117        let result: Vec<Value> = arr
1118            .iter()
1119            .enumerate()
1120            .map(|(i, item)| {
1121                if let Some(obj) = item.as_object() {
1122                    let mut new_obj = obj.clone();
1123                    new_obj.insert(format!("{field}_rank"), Value::Number(ranks[i]));
1124                    Value::Object(new_obj)
1125                } else {
1126                    item.clone()
1127                }
1128            })
1129            .collect();
1130
1131        Ok(Value::Array(result))
1132    }
1133
1134    fn apply_moving_avg(
1135        &self,
1136        value: &Value,
1137        field: &str,
1138        window: usize,
1139    ) -> Result<Value, ExecutionError> {
1140        let arr = value.require_array()?;
1141
1142        let values: Vec<f64> = arr
1143            .iter()
1144            .filter_map(|item| item.as_object()?.get(field)?.as_number())
1145            .collect();
1146
1147        let result: Vec<Value> = arr
1148            .iter()
1149            .enumerate()
1150            .map(|(i, item)| {
1151                if let Some(obj) = item.as_object() {
1152                    let start = i.saturating_sub(window - 1);
1153                    let window_values = &values[start..=i.min(values.len() - 1)];
1154                    let ma = if window_values.is_empty() {
1155                        0.0
1156                    } else {
1157                        window_values.iter().sum::<f64>() / window_values.len() as f64
1158                    };
1159
1160                    let mut new_obj = obj.clone();
1161                    new_obj.insert(format!("{field}_ma{window}"), Value::Number(ma));
1162                    Value::Object(new_obj)
1163                } else {
1164                    item.clone()
1165                }
1166            })
1167            .collect();
1168
1169        Ok(Value::Array(result))
1170    }
1171
1172    fn apply_pct_change(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
1173        let arr = value.require_array()?;
1174
1175        let values: Vec<f64> = arr
1176            .iter()
1177            .filter_map(|item| item.as_object()?.get(field)?.as_number())
1178            .collect();
1179
1180        let result: Vec<Value> = arr
1181            .iter()
1182            .enumerate()
1183            .map(|(i, item)| {
1184                if let Some(obj) = item.as_object() {
1185                    let pct = if i == 0 || values.get(i - 1).map_or(true, |&prev| prev == 0.0) {
1186                        0.0
1187                    } else {
1188                        let prev = values[i - 1];
1189                        let curr = values.get(i).copied().unwrap_or(prev);
1190                        (curr - prev) / prev * 100.0
1191                    };
1192
1193                    let mut new_obj = obj.clone();
1194                    new_obj.insert(format!("{field}_pct_change"), Value::Number(pct));
1195                    Value::Object(new_obj)
1196                } else {
1197                    item.clone()
1198                }
1199            })
1200            .collect();
1201
1202        Ok(Value::Array(result))
1203    }
1204
1205    /// Apply suggestion transform for N-gram/autocomplete models.
1206    ///
1207    /// The input `value` should be a model object with a `model_type` field.
1208    /// This is a stub implementation - actual model inference is handled by
1209    /// the runtime layer which injects results into the context.
1210    ///
1211    /// In production, the runtime loads the .apr model and provides suggestions
1212    /// through a callback or pre-computed context value.
1213    #[allow(clippy::unnecessary_wraps)] // Returns Result for API consistency with other transforms
1214    fn apply_suggest(
1215        &self,
1216        value: &Value,
1217        prefix: &str,
1218        count: usize,
1219    ) -> Result<Value, ExecutionError> {
1220        // Check if value is a model object with pre-computed suggestions
1221        if let Some(obj) = value.as_object() {
1222            // If the model has pre-computed suggestions for this prefix, use them
1223            if let Some(suggestions) = obj.get("_suggestions") {
1224                if let Some(arr) = suggestions.as_array() {
1225                    return Ok(Value::Array(arr.iter().take(count).cloned().collect()));
1226                }
1227            }
1228
1229            // If this is a model reference, return placeholder suggestions
1230            // The runtime layer should populate _suggestions before execution
1231            if obj.contains_key("model_type") || obj.contains_key("source") {
1232                // Return empty array - runtime should inject actual suggestions
1233                return Ok(Value::Array(vec![]));
1234            }
1235        }
1236
1237        // For testing/demo: if value is an array of suggestion objects, filter by prefix
1238        if let Some(arr) = value.as_array() {
1239            let filtered: Vec<Value> = arr
1240                .iter()
1241                .filter(|item| {
1242                    if let Some(obj) = item.as_object() {
1243                        if let Some(text) = obj.get("text").and_then(|v| v.as_str()) {
1244                            return text.starts_with(prefix);
1245                        }
1246                    }
1247                    false
1248                })
1249                .take(count)
1250                .cloned()
1251                .collect();
1252            return Ok(Value::Array(filtered));
1253        }
1254
1255        // Fallback: return empty suggestions
1256        Ok(Value::Array(vec![]))
1257    }
1258}
1259
1260#[cfg(test)]
1261mod tests {
1262    use super::*;
1263    use crate::expression::ExpressionParser;
1264
1265    // ===== Value Tests =====
1266
1267    #[test]
1268    fn test_value_null() {
1269        let v = Value::null();
1270        assert!(v.is_null());
1271        assert!(!v.is_bool());
1272    }
1273
1274    #[test]
1275    fn test_value_bool() {
1276        let v = Value::bool(true);
1277        assert!(v.is_bool());
1278        assert_eq!(v.as_bool(), Some(true));
1279    }
1280
1281    #[test]
1282    fn test_value_number() {
1283        let v = Value::number(42.5);
1284        assert!(v.is_number());
1285        assert_eq!(v.as_number(), Some(42.5));
1286    }
1287
1288    #[test]
1289    fn test_value_string() {
1290        let v = Value::string("hello");
1291        assert!(v.is_string());
1292        assert_eq!(v.as_str(), Some("hello"));
1293    }
1294
1295    #[test]
1296    fn test_value_array() {
1297        let v = Value::array(vec![Value::number(1.0), Value::number(2.0)]);
1298        assert!(v.is_array());
1299        assert_eq!(v.len(), 2);
1300    }
1301
1302    #[test]
1303    fn test_value_object() {
1304        let mut map = HashMap::new();
1305        map.insert("name".to_string(), Value::string("test"));
1306        let v = Value::object(map);
1307        assert!(v.is_object());
1308        assert_eq!(v.get("name").unwrap().as_str(), Some("test"));
1309    }
1310
1311    #[test]
1312    fn test_value_from_bool() {
1313        let v: Value = true.into();
1314        assert_eq!(v, Value::Bool(true));
1315    }
1316
1317    #[test]
1318    fn test_value_from_number() {
1319        let v: Value = 42.0f64.into();
1320        assert_eq!(v, Value::Number(42.0));
1321    }
1322
1323    #[test]
1324    fn test_value_from_i32() {
1325        let v: Value = 42i32.into();
1326        assert_eq!(v, Value::Number(42.0));
1327    }
1328
1329    #[test]
1330    fn test_value_from_str() {
1331        let v: Value = "hello".into();
1332        assert_eq!(v, Value::String("hello".to_string()));
1333    }
1334
1335    #[test]
1336    fn test_value_default() {
1337        assert_eq!(Value::default(), Value::Null);
1338    }
1339
1340    #[test]
1341    fn test_value_is_empty() {
1342        assert!(Value::array(vec![]).is_empty());
1343        assert!(!Value::array(vec![Value::Null]).is_empty());
1344    }
1345
1346    // ===== DataContext Tests =====
1347
1348    #[test]
1349    fn test_context_new() {
1350        let ctx = DataContext::new();
1351        assert!(!ctx.contains("foo"));
1352    }
1353
1354    #[test]
1355    fn test_context_insert_get() {
1356        let mut ctx = DataContext::new();
1357        ctx.insert("users", Value::array(vec![]));
1358        assert!(ctx.contains("users"));
1359    }
1360
1361    #[test]
1362    fn test_context_dotted_path() {
1363        let mut ctx = DataContext::new();
1364        let mut data = HashMap::new();
1365        data.insert("transactions".to_string(), Value::array(vec![]));
1366        ctx.insert("data", Value::object(data));
1367
1368        assert!(ctx.contains("data.transactions"));
1369    }
1370
1371    // ===== ExecutionError Tests =====
1372
1373    #[test]
1374    fn test_error_display() {
1375        assert_eq!(
1376            ExecutionError::SourceNotFound("foo".to_string()).to_string(),
1377            "source not found: foo"
1378        );
1379        assert_eq!(
1380            ExecutionError::ExpectedArray.to_string(),
1381            "expected an array"
1382        );
1383    }
1384
1385    // ===== Executor Tests =====
1386
1387    fn make_test_data() -> DataContext {
1388        let mut ctx = DataContext::new();
1389
1390        // Create test transactions
1391        let transactions: Vec<Value> = vec![
1392            {
1393                let mut t = HashMap::new();
1394                t.insert("id".to_string(), Value::number(1.0));
1395                t.insert("status".to_string(), Value::string("completed"));
1396                t.insert("amount".to_string(), Value::number(100.0));
1397                Value::Object(t)
1398            },
1399            {
1400                let mut t = HashMap::new();
1401                t.insert("id".to_string(), Value::number(2.0));
1402                t.insert("status".to_string(), Value::string("pending"));
1403                t.insert("amount".to_string(), Value::number(50.0));
1404                Value::Object(t)
1405            },
1406            {
1407                let mut t = HashMap::new();
1408                t.insert("id".to_string(), Value::number(3.0));
1409                t.insert("status".to_string(), Value::string("completed"));
1410                t.insert("amount".to_string(), Value::number(75.0));
1411                Value::Object(t)
1412            },
1413        ];
1414
1415        let mut data = HashMap::new();
1416        data.insert("transactions".to_string(), Value::Array(transactions));
1417        ctx.insert("data", Value::object(data));
1418
1419        ctx
1420    }
1421
1422    #[test]
1423    fn test_execute_simple_source() {
1424        let ctx = make_test_data();
1425        let parser = ExpressionParser::new();
1426        let executor = ExpressionExecutor::new();
1427
1428        let expr = parser.parse("data.transactions").unwrap();
1429        let result = executor.execute(&expr, &ctx).unwrap();
1430
1431        assert!(result.is_array());
1432        assert_eq!(result.len(), 3);
1433    }
1434
1435    #[test]
1436    fn test_execute_source_not_found() {
1437        let ctx = DataContext::new();
1438        let parser = ExpressionParser::new();
1439        let executor = ExpressionExecutor::new();
1440
1441        let expr = parser.parse("nonexistent").unwrap();
1442        let result = executor.execute(&expr, &ctx);
1443
1444        assert!(matches!(result, Err(ExecutionError::SourceNotFound(_))));
1445    }
1446
1447    #[test]
1448    fn test_execute_filter() {
1449        let ctx = make_test_data();
1450        let parser = ExpressionParser::new();
1451        let executor = ExpressionExecutor::new();
1452
1453        let expr = parser
1454            .parse("{{ data.transactions | filter(status=completed) }}")
1455            .unwrap();
1456        let result = executor.execute(&expr, &ctx).unwrap();
1457
1458        assert!(result.is_array());
1459        assert_eq!(result.len(), 2);
1460    }
1461
1462    #[test]
1463    fn test_execute_count() {
1464        let ctx = make_test_data();
1465        let parser = ExpressionParser::new();
1466        let executor = ExpressionExecutor::new();
1467
1468        let expr = parser.parse("{{ data.transactions | count }}").unwrap();
1469        let result = executor.execute(&expr, &ctx).unwrap();
1470
1471        assert_eq!(result.as_number(), Some(3.0));
1472    }
1473
1474    #[test]
1475    fn test_execute_filter_then_count() {
1476        let ctx = make_test_data();
1477        let parser = ExpressionParser::new();
1478        let executor = ExpressionExecutor::new();
1479
1480        let expr = parser
1481            .parse("{{ data.transactions | filter(status=completed) | count }}")
1482            .unwrap();
1483        let result = executor.execute(&expr, &ctx).unwrap();
1484
1485        assert_eq!(result.as_number(), Some(2.0));
1486    }
1487
1488    #[test]
1489    fn test_execute_select() {
1490        let ctx = make_test_data();
1491        let parser = ExpressionParser::new();
1492        let executor = ExpressionExecutor::new();
1493
1494        let expr = parser
1495            .parse("{{ data.transactions | select(id, status) }}")
1496            .unwrap();
1497        let result = executor.execute(&expr, &ctx).unwrap();
1498
1499        let arr = result.as_array().unwrap();
1500        assert_eq!(arr.len(), 3);
1501
1502        // First item should only have id and status
1503        let first = arr[0].as_object().unwrap();
1504        assert!(first.contains_key("id"));
1505        assert!(first.contains_key("status"));
1506        assert!(!first.contains_key("amount"));
1507    }
1508
1509    #[test]
1510    fn test_execute_sort_asc() {
1511        let ctx = make_test_data();
1512        let parser = ExpressionParser::new();
1513        let executor = ExpressionExecutor::new();
1514
1515        let expr = parser
1516            .parse("{{ data.transactions | sort(amount) }}")
1517            .unwrap();
1518        let result = executor.execute(&expr, &ctx).unwrap();
1519
1520        let arr = result.as_array().unwrap();
1521        let amounts: Vec<f64> = arr
1522            .iter()
1523            .filter_map(|v| v.get("amount")?.as_number())
1524            .collect();
1525        assert_eq!(amounts, vec![50.0, 75.0, 100.0]);
1526    }
1527
1528    #[test]
1529    fn test_execute_sort_desc() {
1530        let ctx = make_test_data();
1531        let parser = ExpressionParser::new();
1532        let executor = ExpressionExecutor::new();
1533
1534        let expr = parser
1535            .parse("{{ data.transactions | sort(amount, desc=true) }}")
1536            .unwrap();
1537        let result = executor.execute(&expr, &ctx).unwrap();
1538
1539        let arr = result.as_array().unwrap();
1540        let amounts: Vec<f64> = arr
1541            .iter()
1542            .filter_map(|v| v.get("amount")?.as_number())
1543            .collect();
1544        assert_eq!(amounts, vec![100.0, 75.0, 50.0]);
1545    }
1546
1547    #[test]
1548    fn test_execute_limit() {
1549        let ctx = make_test_data();
1550        let parser = ExpressionParser::new();
1551        let executor = ExpressionExecutor::new();
1552
1553        let expr = parser.parse("{{ data.transactions | limit(2) }}").unwrap();
1554        let result = executor.execute(&expr, &ctx).unwrap();
1555
1556        assert_eq!(result.len(), 2);
1557    }
1558
1559    #[test]
1560    fn test_execute_sum() {
1561        let ctx = make_test_data();
1562        let parser = ExpressionParser::new();
1563        let executor = ExpressionExecutor::new();
1564
1565        let expr = parser
1566            .parse("{{ data.transactions | sum(amount) }}")
1567            .unwrap();
1568        let result = executor.execute(&expr, &ctx).unwrap();
1569
1570        assert_eq!(result.as_number(), Some(225.0)); // 100 + 50 + 75
1571    }
1572
1573    #[test]
1574    fn test_execute_mean() {
1575        let ctx = make_test_data();
1576        let parser = ExpressionParser::new();
1577        let executor = ExpressionExecutor::new();
1578
1579        let expr = parser
1580            .parse("{{ data.transactions | mean(amount) }}")
1581            .unwrap();
1582        let result = executor.execute(&expr, &ctx).unwrap();
1583
1584        assert_eq!(result.as_number(), Some(75.0)); // 225 / 3
1585    }
1586
1587    #[test]
1588    fn test_execute_sample() {
1589        let ctx = make_test_data();
1590        let parser = ExpressionParser::new();
1591        let executor = ExpressionExecutor::new();
1592
1593        let expr = parser.parse("{{ data.transactions | sample(2) }}").unwrap();
1594        let result = executor.execute(&expr, &ctx).unwrap();
1595
1596        assert_eq!(result.len(), 2);
1597    }
1598
1599    #[test]
1600    fn test_execute_percentage() {
1601        let mut ctx = DataContext::new();
1602        ctx.insert("ratio", Value::number(0.75));
1603
1604        let parser = ExpressionParser::new();
1605        let executor = ExpressionExecutor::new();
1606
1607        let expr = parser.parse("{{ ratio | percentage }}").unwrap();
1608        let result = executor.execute(&expr, &ctx).unwrap();
1609
1610        assert_eq!(result.as_number(), Some(75.0));
1611    }
1612
1613    #[test]
1614    fn test_execute_chain() {
1615        let ctx = make_test_data();
1616        let parser = ExpressionParser::new();
1617        let executor = ExpressionExecutor::new();
1618
1619        let expr = parser
1620            .parse("{{ data.transactions | filter(status=completed) | sort(amount, desc=true) | limit(1) }}")
1621            .unwrap();
1622        let result = executor.execute(&expr, &ctx).unwrap();
1623
1624        let arr = result.as_array().unwrap();
1625        assert_eq!(arr.len(), 1);
1626        assert_eq!(arr[0].get("amount").unwrap().as_number(), Some(100.0));
1627    }
1628
1629    #[test]
1630    fn test_filter_numeric_match() {
1631        let ctx = make_test_data();
1632        let parser = ExpressionParser::new();
1633        let executor = ExpressionExecutor::new();
1634
1635        let expr = parser
1636            .parse("{{ data.transactions | filter(amount=100) }}")
1637            .unwrap();
1638        let result = executor.execute(&expr, &ctx).unwrap();
1639
1640        assert_eq!(result.len(), 1);
1641    }
1642
1643    #[test]
1644    fn test_execute_on_empty_array() {
1645        let mut ctx = DataContext::new();
1646        ctx.insert("items", Value::array(vec![]));
1647
1648        let parser = ExpressionParser::new();
1649        let executor = ExpressionExecutor::new();
1650
1651        let expr = parser.parse("{{ items | count }}").unwrap();
1652        let result = executor.execute(&expr, &ctx).unwrap();
1653
1654        assert_eq!(result.as_number(), Some(0.0));
1655    }
1656
1657    #[test]
1658    fn test_execute_mean_empty_array() {
1659        let mut ctx = DataContext::new();
1660        ctx.insert("items", Value::array(vec![]));
1661
1662        let parser = ExpressionParser::new();
1663        let executor = ExpressionExecutor::new();
1664
1665        let expr = parser.parse("{{ items | mean(value) }}").unwrap();
1666        let result = executor.execute(&expr, &ctx).unwrap();
1667
1668        assert_eq!(result.as_number(), Some(0.0));
1669    }
1670
1671    // ===== Rate Transform Tests =====
1672
1673    fn make_time_series_data() -> DataContext {
1674        let mut ctx = DataContext::new();
1675
1676        let events: Vec<Value> = vec![
1677            {
1678                let mut e = HashMap::new();
1679                e.insert("timestamp".to_string(), Value::number(1000.0));
1680                e.insert("value".to_string(), Value::number(10.0));
1681                Value::Object(e)
1682            },
1683            {
1684                let mut e = HashMap::new();
1685                e.insert("timestamp".to_string(), Value::number(2000.0));
1686                e.insert("value".to_string(), Value::number(20.0));
1687                Value::Object(e)
1688            },
1689            {
1690                let mut e = HashMap::new();
1691                e.insert("timestamp".to_string(), Value::number(3000.0));
1692                e.insert("value".to_string(), Value::number(30.0));
1693                Value::Object(e)
1694            },
1695        ];
1696
1697        ctx.insert("events", Value::Array(events));
1698        ctx
1699    }
1700
1701    #[test]
1702    fn test_execute_rate() {
1703        let ctx = make_time_series_data();
1704        let parser = ExpressionParser::new();
1705        let executor = ExpressionExecutor::new();
1706
1707        let expr = parser.parse("{{ events | rate(5s) }}").unwrap();
1708        let result = executor.execute(&expr, &ctx).unwrap();
1709
1710        // Rate should be sum of values in window / window in seconds
1711        // Window is 5s (5000ms), all 3 values sum to 60, rate = 60 / 5 = 12
1712        assert!(result.is_number());
1713        let rate = result.as_number().unwrap();
1714        assert!(rate > 0.0);
1715    }
1716
1717    #[test]
1718    fn test_execute_rate_minute_window() {
1719        let ctx = make_time_series_data();
1720        let parser = ExpressionParser::new();
1721        let executor = ExpressionExecutor::new();
1722
1723        let expr = parser.parse("{{ events | rate(1m) }}").unwrap();
1724        let result = executor.execute(&expr, &ctx).unwrap();
1725
1726        assert!(result.is_number());
1727    }
1728
1729    // ===== Group By Tests =====
1730
1731    #[test]
1732    fn test_execute_group_by() {
1733        let ctx = make_test_data();
1734        let parser = ExpressionParser::new();
1735        let executor = ExpressionExecutor::new();
1736
1737        let expr = parser
1738            .parse("{{ data.transactions | group_by(status) }}")
1739            .unwrap();
1740        let result = executor.execute(&expr, &ctx).unwrap();
1741
1742        let arr = result.as_array().unwrap();
1743        // Should have 2 groups: "completed" (2 items) and "pending" (1 item)
1744        assert_eq!(arr.len(), 2);
1745
1746        for group in arr {
1747            let obj = group.as_object().unwrap();
1748            assert!(obj.contains_key("key"));
1749            assert!(obj.contains_key("items"));
1750            assert!(obj.contains_key("count"));
1751        }
1752    }
1753
1754    // ===== Distinct Tests =====
1755
1756    #[test]
1757    fn test_execute_distinct_field() {
1758        let ctx = make_test_data();
1759        let parser = ExpressionParser::new();
1760        let executor = ExpressionExecutor::new();
1761
1762        let expr = parser
1763            .parse("{{ data.transactions | distinct(status) }}")
1764            .unwrap();
1765        let result = executor.execute(&expr, &ctx).unwrap();
1766
1767        // Should get first of each distinct status
1768        assert_eq!(result.len(), 2); // "completed" and "pending"
1769    }
1770
1771    #[test]
1772    fn test_execute_distinct_no_field() {
1773        let mut ctx = DataContext::new();
1774        ctx.insert(
1775            "items",
1776            Value::array(vec![
1777                Value::string("a"),
1778                Value::string("b"),
1779                Value::string("a"),
1780                Value::string("c"),
1781            ]),
1782        );
1783
1784        let parser = ExpressionParser::new();
1785        let executor = ExpressionExecutor::new();
1786
1787        let expr = parser.parse("{{ items | distinct }}").unwrap();
1788        let result = executor.execute(&expr, &ctx).unwrap();
1789
1790        assert_eq!(result.len(), 3); // "a", "b", "c"
1791    }
1792
1793    // ===== Where Tests =====
1794
1795    #[test]
1796    fn test_execute_where_gt() {
1797        let ctx = make_test_data();
1798        let parser = ExpressionParser::new();
1799        let executor = ExpressionExecutor::new();
1800
1801        let expr = parser
1802            .parse("{{ data.transactions | where(amount, gt, 60) }}")
1803            .unwrap();
1804        let result = executor.execute(&expr, &ctx).unwrap();
1805
1806        // Should get 2 items: amount=100 and amount=75
1807        assert_eq!(result.len(), 2);
1808    }
1809
1810    #[test]
1811    fn test_execute_where_lt() {
1812        let ctx = make_test_data();
1813        let parser = ExpressionParser::new();
1814        let executor = ExpressionExecutor::new();
1815
1816        let expr = parser
1817            .parse("{{ data.transactions | where(amount, lt, 80) }}")
1818            .unwrap();
1819        let result = executor.execute(&expr, &ctx).unwrap();
1820
1821        // Should get 2 items: amount=50 and amount=75
1822        assert_eq!(result.len(), 2);
1823    }
1824
1825    #[test]
1826    fn test_execute_where_eq() {
1827        let ctx = make_test_data();
1828        let parser = ExpressionParser::new();
1829        let executor = ExpressionExecutor::new();
1830
1831        let expr = parser
1832            .parse("{{ data.transactions | where(status, eq, pending) }}")
1833            .unwrap();
1834        let result = executor.execute(&expr, &ctx).unwrap();
1835
1836        assert_eq!(result.len(), 1);
1837    }
1838
1839    #[test]
1840    fn test_execute_where_ne() {
1841        let ctx = make_test_data();
1842        let parser = ExpressionParser::new();
1843        let executor = ExpressionExecutor::new();
1844
1845        let expr = parser
1846            .parse("{{ data.transactions | where(status, ne, pending) }}")
1847            .unwrap();
1848        let result = executor.execute(&expr, &ctx).unwrap();
1849
1850        assert_eq!(result.len(), 2);
1851    }
1852
1853    #[test]
1854    fn test_execute_where_contains() {
1855        let mut ctx = DataContext::new();
1856        let items: Vec<Value> = vec![
1857            {
1858                let mut t = HashMap::new();
1859                t.insert("name".to_string(), Value::string("hello world"));
1860                Value::Object(t)
1861            },
1862            {
1863                let mut t = HashMap::new();
1864                t.insert("name".to_string(), Value::string("goodbye"));
1865                Value::Object(t)
1866            },
1867        ];
1868        ctx.insert("items", Value::Array(items));
1869
1870        let parser = ExpressionParser::new();
1871        let executor = ExpressionExecutor::new();
1872
1873        let expr = parser
1874            .parse("{{ items | where(name, contains, world) }}")
1875            .unwrap();
1876        let result = executor.execute(&expr, &ctx).unwrap();
1877
1878        assert_eq!(result.len(), 1);
1879    }
1880
1881    // ===== Offset Tests =====
1882
1883    #[test]
1884    fn test_execute_offset() {
1885        let ctx = make_test_data();
1886        let parser = ExpressionParser::new();
1887        let executor = ExpressionExecutor::new();
1888
1889        let expr = parser.parse("{{ data.transactions | offset(1) }}").unwrap();
1890        let result = executor.execute(&expr, &ctx).unwrap();
1891
1892        assert_eq!(result.len(), 2); // Original 3, skip 1
1893    }
1894
1895    #[test]
1896    fn test_execute_offset_with_limit() {
1897        let ctx = make_test_data();
1898        let parser = ExpressionParser::new();
1899        let executor = ExpressionExecutor::new();
1900
1901        let expr = parser
1902            .parse("{{ data.transactions | offset(1) | limit(1) }}")
1903            .unwrap();
1904        let result = executor.execute(&expr, &ctx).unwrap();
1905
1906        assert_eq!(result.len(), 1);
1907    }
1908
1909    // ===== Min/Max Tests =====
1910
1911    #[test]
1912    fn test_execute_min() {
1913        let ctx = make_test_data();
1914        let parser = ExpressionParser::new();
1915        let executor = ExpressionExecutor::new();
1916
1917        let expr = parser
1918            .parse("{{ data.transactions | min(amount) }}")
1919            .unwrap();
1920        let result = executor.execute(&expr, &ctx).unwrap();
1921
1922        assert_eq!(result.as_number(), Some(50.0));
1923    }
1924
1925    #[test]
1926    fn test_execute_max() {
1927        let ctx = make_test_data();
1928        let parser = ExpressionParser::new();
1929        let executor = ExpressionExecutor::new();
1930
1931        let expr = parser
1932            .parse("{{ data.transactions | max(amount) }}")
1933            .unwrap();
1934        let result = executor.execute(&expr, &ctx).unwrap();
1935
1936        assert_eq!(result.as_number(), Some(100.0));
1937    }
1938
1939    #[test]
1940    fn test_execute_min_empty() {
1941        let mut ctx = DataContext::new();
1942        ctx.insert("items", Value::array(vec![]));
1943
1944        let parser = ExpressionParser::new();
1945        let executor = ExpressionExecutor::new();
1946
1947        let expr = parser.parse("{{ items | min(value) }}").unwrap();
1948        let result = executor.execute(&expr, &ctx).unwrap();
1949
1950        assert!(result.is_null());
1951    }
1952
1953    // ===== First/Last Tests =====
1954
1955    #[test]
1956    fn test_execute_first() {
1957        let ctx = make_test_data();
1958        let parser = ExpressionParser::new();
1959        let executor = ExpressionExecutor::new();
1960
1961        let expr = parser.parse("{{ data.transactions | first(2) }}").unwrap();
1962        let result = executor.execute(&expr, &ctx).unwrap();
1963
1964        assert_eq!(result.len(), 2);
1965    }
1966
1967    #[test]
1968    fn test_execute_last() {
1969        let ctx = make_test_data();
1970        let parser = ExpressionParser::new();
1971        let executor = ExpressionExecutor::new();
1972
1973        let expr = parser.parse("{{ data.transactions | last(2) }}").unwrap();
1974        let result = executor.execute(&expr, &ctx).unwrap();
1975
1976        let arr = result.as_array().unwrap();
1977        assert_eq!(arr.len(), 2);
1978
1979        // Last item should be id=3
1980        assert_eq!(arr[1].get("id").unwrap().as_number(), Some(3.0));
1981    }
1982
1983    // ===== Flatten Tests =====
1984
1985    #[test]
1986    fn test_execute_flatten() {
1987        let mut ctx = DataContext::new();
1988        ctx.insert(
1989            "nested",
1990            Value::array(vec![
1991                Value::array(vec![Value::number(1.0), Value::number(2.0)]),
1992                Value::array(vec![Value::number(3.0), Value::number(4.0)]),
1993            ]),
1994        );
1995
1996        let parser = ExpressionParser::new();
1997        let executor = ExpressionExecutor::new();
1998
1999        let expr = parser.parse("{{ nested | flatten }}").unwrap();
2000        let result = executor.execute(&expr, &ctx).unwrap();
2001
2002        let arr = result.as_array().unwrap();
2003        assert_eq!(arr.len(), 4);
2004        assert_eq!(arr[0].as_number(), Some(1.0));
2005        assert_eq!(arr[3].as_number(), Some(4.0));
2006    }
2007
2008    #[test]
2009    fn test_execute_flatten_mixed() {
2010        let mut ctx = DataContext::new();
2011        ctx.insert(
2012            "items",
2013            Value::array(vec![
2014                Value::number(1.0),
2015                Value::array(vec![Value::number(2.0), Value::number(3.0)]),
2016                Value::number(4.0),
2017            ]),
2018        );
2019
2020        let parser = ExpressionParser::new();
2021        let executor = ExpressionExecutor::new();
2022
2023        let expr = parser.parse("{{ items | flatten }}").unwrap();
2024        let result = executor.execute(&expr, &ctx).unwrap();
2025
2026        assert_eq!(result.len(), 4);
2027    }
2028
2029    // ===== Reverse Tests =====
2030
2031    #[test]
2032    fn test_execute_reverse() {
2033        let ctx = make_test_data();
2034        let parser = ExpressionParser::new();
2035        let executor = ExpressionExecutor::new();
2036
2037        let expr = parser.parse("{{ data.transactions | reverse }}").unwrap();
2038        let result = executor.execute(&expr, &ctx).unwrap();
2039
2040        let arr = result.as_array().unwrap();
2041        // First item should now be the last one (id=3)
2042        assert_eq!(arr[0].get("id").unwrap().as_number(), Some(3.0));
2043    }
2044
2045    // ===== Complex Chain Tests =====
2046
2047    #[test]
2048    fn test_execute_complex_chain() {
2049        let ctx = make_test_data();
2050        let parser = ExpressionParser::new();
2051        let executor = ExpressionExecutor::new();
2052
2053        // Filter by status, sort by amount descending, get first 1
2054        let expr = parser
2055            .parse("{{ data.transactions | where(status, eq, completed) | sort(amount, desc=true) | first(1) }}")
2056            .unwrap();
2057        let result = executor.execute(&expr, &ctx).unwrap();
2058
2059        let arr = result.as_array().unwrap();
2060        assert_eq!(arr.len(), 1);
2061        // Should be the completed transaction with highest amount (100)
2062        assert_eq!(arr[0].get("amount").unwrap().as_number(), Some(100.0));
2063    }
2064
2065    #[test]
2066    fn test_execute_group_then_count() {
2067        let ctx = make_test_data();
2068        let parser = ExpressionParser::new();
2069        let executor = ExpressionExecutor::new();
2070
2071        let expr = parser
2072            .parse("{{ data.transactions | group_by(status) | count }}")
2073            .unwrap();
2074        let result = executor.execute(&expr, &ctx).unwrap();
2075
2076        // Should count the groups (2)
2077        assert_eq!(result.as_number(), Some(2.0));
2078    }
2079
2080    // ===== Join Tests =====
2081
2082    fn make_join_test_data() -> DataContext {
2083        let mut ctx = DataContext::new();
2084
2085        // Orders dataset
2086        let orders = Value::Array(vec![
2087            {
2088                let mut obj = HashMap::new();
2089                obj.insert("id".to_string(), Value::Number(1.0));
2090                obj.insert("customer_id".to_string(), Value::Number(100.0));
2091                obj.insert("amount".to_string(), Value::Number(50.0));
2092                Value::Object(obj)
2093            },
2094            {
2095                let mut obj = HashMap::new();
2096                obj.insert("id".to_string(), Value::Number(2.0));
2097                obj.insert("customer_id".to_string(), Value::Number(101.0));
2098                obj.insert("amount".to_string(), Value::Number(75.0));
2099                Value::Object(obj)
2100            },
2101            {
2102                let mut obj = HashMap::new();
2103                obj.insert("id".to_string(), Value::Number(3.0));
2104                obj.insert("customer_id".to_string(), Value::Number(100.0));
2105                obj.insert("amount".to_string(), Value::Number(25.0));
2106                Value::Object(obj)
2107            },
2108            {
2109                let mut obj = HashMap::new();
2110                obj.insert("id".to_string(), Value::Number(4.0));
2111                obj.insert("customer_id".to_string(), Value::Number(999.0)); // No matching customer
2112                obj.insert("amount".to_string(), Value::Number(10.0));
2113                Value::Object(obj)
2114            },
2115        ]);
2116
2117        // Customers dataset
2118        let customers = Value::Array(vec![
2119            {
2120                let mut obj = HashMap::new();
2121                obj.insert("customer_id".to_string(), Value::Number(100.0));
2122                obj.insert("name".to_string(), Value::String("Alice".to_string()));
2123                obj.insert("tier".to_string(), Value::String("gold".to_string()));
2124                Value::Object(obj)
2125            },
2126            {
2127                let mut obj = HashMap::new();
2128                obj.insert("customer_id".to_string(), Value::Number(101.0));
2129                obj.insert("name".to_string(), Value::String("Bob".to_string()));
2130                obj.insert("tier".to_string(), Value::String("silver".to_string()));
2131                Value::Object(obj)
2132            },
2133        ]);
2134
2135        ctx.insert("orders", orders);
2136        ctx.insert("customers", customers);
2137        ctx
2138    }
2139
2140    #[test]
2141    fn test_execute_join_basic() {
2142        let ctx = make_join_test_data();
2143        let parser = ExpressionParser::new();
2144        let executor = ExpressionExecutor::new();
2145
2146        let expr = parser
2147            .parse("{{ orders | join(customers, on=customer_id) }}")
2148            .unwrap();
2149        let result = executor.execute(&expr, &ctx).unwrap();
2150
2151        let arr = result.as_array().unwrap();
2152        // 4 orders total - 3 with matches, 1 without
2153        assert_eq!(arr.len(), 4);
2154
2155        // Check first order (customer 100 - Alice)
2156        let first = arr[0].as_object().unwrap();
2157        assert_eq!(first.get("id").unwrap().as_number(), Some(1.0));
2158        assert_eq!(first.get("name").unwrap().as_str(), Some("Alice"));
2159        assert_eq!(first.get("tier").unwrap().as_str(), Some("gold"));
2160    }
2161
2162    #[test]
2163    fn test_execute_join_multiple_matches() {
2164        let ctx = make_join_test_data();
2165        let executor = ExpressionExecutor::new();
2166
2167        // Create expression manually for testing
2168        let expr = Expression {
2169            source: "orders".to_string(),
2170            transforms: vec![Transform::Join {
2171                other: "customers".to_string(),
2172                on: "customer_id".to_string(),
2173            }],
2174        };
2175
2176        let result = executor.execute(&expr, &ctx).unwrap();
2177        let arr = result.as_array().unwrap();
2178
2179        // Count orders for customer 100 (should have 2 orders: id 1 and id 3)
2180        let alice_orders: Vec<_> = arr
2181            .iter()
2182            .filter(|v| {
2183                v.as_object()
2184                    .and_then(|o| o.get("name"))
2185                    .and_then(|n| n.as_str())
2186                    == Some("Alice")
2187            })
2188            .collect();
2189        assert_eq!(alice_orders.len(), 2);
2190    }
2191
2192    #[test]
2193    fn test_execute_join_no_match_keeps_left() {
2194        let ctx = make_join_test_data();
2195        let executor = ExpressionExecutor::new();
2196
2197        let expr = Expression {
2198            source: "orders".to_string(),
2199            transforms: vec![Transform::Join {
2200                other: "customers".to_string(),
2201                on: "customer_id".to_string(),
2202            }],
2203        };
2204
2205        let result = executor.execute(&expr, &ctx).unwrap();
2206        let arr = result.as_array().unwrap();
2207
2208        // Find order 4 (customer 999, no match)
2209        let order4: Vec<_> = arr
2210            .iter()
2211            .filter(|v| {
2212                v.as_object()
2213                    .and_then(|o| o.get("id"))
2214                    .and_then(|n| n.as_number())
2215                    == Some(4.0)
2216            })
2217            .collect();
2218        assert_eq!(order4.len(), 1);
2219
2220        // Should still have original fields but no name/tier
2221        let obj = order4[0].as_object().unwrap();
2222        assert_eq!(obj.get("customer_id").unwrap().as_number(), Some(999.0));
2223        assert!(obj.get("name").is_none());
2224    }
2225
2226    #[test]
2227    fn test_execute_join_other_source_not_found() {
2228        let ctx = make_join_test_data();
2229        let executor = ExpressionExecutor::new();
2230
2231        let expr = Expression {
2232            source: "orders".to_string(),
2233            transforms: vec![Transform::Join {
2234                other: "nonexistent".to_string(),
2235                on: "customer_id".to_string(),
2236            }],
2237        };
2238
2239        let result = executor.execute(&expr, &ctx);
2240        assert!(result.is_err());
2241        assert!(matches!(
2242            result.unwrap_err(),
2243            ExecutionError::SourceNotFound(_)
2244        ));
2245    }
2246
2247    #[test]
2248    fn test_execute_join_chained_with_filter() {
2249        let ctx = make_join_test_data();
2250        let parser = ExpressionParser::new();
2251        let executor = ExpressionExecutor::new();
2252
2253        // Join and then filter to only gold tier customers
2254        let expr = parser
2255            .parse("{{ orders | join(customers, on=customer_id) | filter(tier=gold) }}")
2256            .unwrap();
2257        let result = executor.execute(&expr, &ctx).unwrap();
2258
2259        let arr = result.as_array().unwrap();
2260        // Only orders from customer 100 (Alice, gold tier) - 2 orders
2261        assert_eq!(arr.len(), 2);
2262    }
2263
2264    #[test]
2265    fn test_execute_join_empty_left() {
2266        let mut ctx = DataContext::new();
2267        ctx.insert("empty", Value::Array(vec![]));
2268        ctx.insert(
2269            "other",
2270            Value::Array(vec![{
2271                let mut obj = HashMap::new();
2272                obj.insert("id".to_string(), Value::Number(1.0));
2273                Value::Object(obj)
2274            }]),
2275        );
2276
2277        let executor = ExpressionExecutor::new();
2278        let expr = Expression {
2279            source: "empty".to_string(),
2280            transforms: vec![Transform::Join {
2281                other: "other".to_string(),
2282                on: "id".to_string(),
2283            }],
2284        };
2285
2286        let result = executor.execute(&expr, &ctx).unwrap();
2287        let arr = result.as_array().unwrap();
2288        assert!(arr.is_empty());
2289    }
2290
2291    #[test]
2292    fn test_execute_join_empty_right() {
2293        let mut ctx = DataContext::new();
2294        ctx.insert(
2295            "orders",
2296            Value::Array(vec![{
2297                let mut obj = HashMap::new();
2298                obj.insert("id".to_string(), Value::Number(1.0));
2299                Value::Object(obj)
2300            }]),
2301        );
2302        ctx.insert("empty", Value::Array(vec![]));
2303
2304        let executor = ExpressionExecutor::new();
2305        let expr = Expression {
2306            source: "orders".to_string(),
2307            transforms: vec![Transform::Join {
2308                other: "empty".to_string(),
2309                on: "id".to_string(),
2310            }],
2311        };
2312
2313        let result = executor.execute(&expr, &ctx).unwrap();
2314        let arr = result.as_array().unwrap();
2315        // Left join keeps left items even with no matches
2316        assert_eq!(arr.len(), 1);
2317    }
2318
2319    #[test]
2320    fn test_execute_join_conflicting_field_names() {
2321        let mut ctx = DataContext::new();
2322
2323        // Both have "value" field
2324        ctx.insert(
2325            "left",
2326            Value::Array(vec![{
2327                let mut obj = HashMap::new();
2328                obj.insert("id".to_string(), Value::Number(1.0));
2329                obj.insert("value".to_string(), Value::String("left_val".to_string()));
2330                Value::Object(obj)
2331            }]),
2332        );
2333        ctx.insert(
2334            "right",
2335            Value::Array(vec![{
2336                let mut obj = HashMap::new();
2337                obj.insert("id".to_string(), Value::Number(1.0));
2338                obj.insert("value".to_string(), Value::String("right_val".to_string()));
2339                obj.insert("extra".to_string(), Value::String("extra_val".to_string()));
2340                Value::Object(obj)
2341            }]),
2342        );
2343
2344        let executor = ExpressionExecutor::new();
2345        let expr = Expression {
2346            source: "left".to_string(),
2347            transforms: vec![Transform::Join {
2348                other: "right".to_string(),
2349                on: "id".to_string(),
2350            }],
2351        };
2352
2353        let result = executor.execute(&expr, &ctx).unwrap();
2354        let arr = result.as_array().unwrap();
2355        assert_eq!(arr.len(), 1);
2356
2357        let obj = arr[0].as_object().unwrap();
2358        // Original left value should be preserved
2359        assert_eq!(obj.get("value").unwrap().as_str(), Some("left_val"));
2360        // Right value should be prefixed
2361        assert_eq!(obj.get("right_value").unwrap().as_str(), Some("right_val"));
2362        // Non-conflicting fields should be added directly
2363        assert_eq!(obj.get("extra").unwrap().as_str(), Some("extra_val"));
2364    }
2365
2366    #[test]
2367    fn test_execute_join_with_sum() {
2368        let ctx = make_join_test_data();
2369        let parser = ExpressionParser::new();
2370        let executor = ExpressionExecutor::new();
2371
2372        // Join, filter to gold tier, sum amounts
2373        let expr = parser
2374            .parse(
2375                "{{ orders | join(customers, on=customer_id) | filter(tier=gold) | sum(amount) }}",
2376            )
2377            .unwrap();
2378        let result = executor.execute(&expr, &ctx).unwrap();
2379
2380        // Alice has orders 1 (50) and 3 (25) = 75
2381        assert_eq!(result.as_number(), Some(75.0));
2382    }
2383
2384    // ===== Suggest Transform Tests =====
2385
2386    #[test]
2387    fn test_execute_suggest_with_array() {
2388        let mut ctx = DataContext::new();
2389
2390        // Create suggestion data (simulating pre-computed suggestions)
2391        let suggestions = Value::Array(vec![
2392            {
2393                let mut obj = HashMap::new();
2394                obj.insert("text".to_string(), Value::String("git status".to_string()));
2395                obj.insert("score".to_string(), Value::Number(0.15));
2396                Value::Object(obj)
2397            },
2398            {
2399                let mut obj = HashMap::new();
2400                obj.insert("text".to_string(), Value::String("git commit".to_string()));
2401                obj.insert("score".to_string(), Value::Number(0.12));
2402                Value::Object(obj)
2403            },
2404            {
2405                let mut obj = HashMap::new();
2406                obj.insert("text".to_string(), Value::String("cargo build".to_string()));
2407                obj.insert("score".to_string(), Value::Number(0.10));
2408                Value::Object(obj)
2409            },
2410        ]);
2411        ctx.insert("suggestions", suggestions);
2412
2413        let parser = ExpressionParser::new();
2414        let executor = ExpressionExecutor::new();
2415
2416        // Filter suggestions starting with "git"
2417        let expr = parser.parse("{{ suggestions | suggest(git, 5) }}").unwrap();
2418        let result = executor.execute(&expr, &ctx).unwrap();
2419
2420        let arr = result.as_array().unwrap();
2421        assert_eq!(arr.len(), 2); // Only git status and git commit
2422    }
2423
2424    #[test]
2425    fn test_execute_suggest_with_model_object() {
2426        let mut ctx = DataContext::new();
2427
2428        // Create a model object with pre-computed suggestions
2429        let mut model = HashMap::new();
2430        model.insert(
2431            "model_type".to_string(),
2432            Value::String("ngram_lm".to_string()),
2433        );
2434        model.insert(
2435            "source".to_string(),
2436            Value::String("./model.apr".to_string()),
2437        );
2438        model.insert(
2439            "_suggestions".to_string(),
2440            Value::Array(vec![
2441                {
2442                    let mut obj = HashMap::new();
2443                    obj.insert("text".to_string(), Value::String("git status".to_string()));
2444                    obj.insert("score".to_string(), Value::Number(0.15));
2445                    Value::Object(obj)
2446                },
2447                {
2448                    let mut obj = HashMap::new();
2449                    obj.insert("text".to_string(), Value::String("git commit".to_string()));
2450                    obj.insert("score".to_string(), Value::Number(0.12));
2451                    Value::Object(obj)
2452                },
2453            ]),
2454        );
2455        ctx.insert("model", Value::Object(model));
2456
2457        let parser = ExpressionParser::new();
2458        let executor = ExpressionExecutor::new();
2459
2460        let expr = parser.parse("{{ model | suggest(git, 5) }}").unwrap();
2461        let result = executor.execute(&expr, &ctx).unwrap();
2462
2463        let arr = result.as_array().unwrap();
2464        assert_eq!(arr.len(), 2); // Pre-computed suggestions
2465    }
2466
2467    #[test]
2468    fn test_execute_suggest_empty_model() {
2469        let mut ctx = DataContext::new();
2470
2471        // Model without pre-computed suggestions
2472        let mut model = HashMap::new();
2473        model.insert(
2474            "model_type".to_string(),
2475            Value::String("ngram_lm".to_string()),
2476        );
2477        ctx.insert("model", Value::Object(model));
2478
2479        let parser = ExpressionParser::new();
2480        let executor = ExpressionExecutor::new();
2481
2482        let expr = parser.parse("{{ model | suggest(git, 5) }}").unwrap();
2483        let result = executor.execute(&expr, &ctx).unwrap();
2484
2485        let arr = result.as_array().unwrap();
2486        assert!(arr.is_empty()); // No suggestions when not pre-computed
2487    }
2488
2489    #[test]
2490    fn test_parse_suggest() {
2491        let parser = ExpressionParser::new();
2492        let expr = parser.parse("{{ model | suggest(git, 8) }}").unwrap();
2493
2494        assert_eq!(expr.source, "model");
2495        assert_eq!(expr.transforms.len(), 1);
2496        assert!(matches!(
2497            &expr.transforms[0],
2498            Transform::Suggest { prefix, count } if prefix == "git" && *count == 8
2499        ));
2500    }
2501
2502    // ===== Additional Value Tests for Coverage =====
2503
2504    #[test]
2505    fn test_value_from_i64() {
2506        let v: Value = 123i64.into();
2507        assert_eq!(v.as_number(), Some(123.0));
2508    }
2509
2510    #[test]
2511    fn test_value_as_array_mut() {
2512        let mut v = Value::array(vec![Value::number(1.0), Value::number(2.0)]);
2513        if let Some(arr) = v.as_array_mut() {
2514            arr.push(Value::number(3.0));
2515        }
2516        assert_eq!(v.len(), 3);
2517    }
2518
2519    #[test]
2520    fn test_value_as_array_mut_non_array() {
2521        let mut v = Value::string("hello");
2522        assert!(v.as_array_mut().is_none());
2523    }
2524
2525    #[test]
2526    fn test_value_as_object() {
2527        let mut map = HashMap::new();
2528        map.insert("key".to_string(), Value::string("value"));
2529        let v = Value::object(map);
2530        assert!(v.as_object().is_some());
2531        assert_eq!(v.as_object().unwrap().len(), 1);
2532    }
2533
2534    #[test]
2535    fn test_value_as_object_non_object() {
2536        let v = Value::string("hello");
2537        assert!(v.as_object().is_none());
2538    }
2539
2540    #[test]
2541    fn test_value_len_string() {
2542        let v = Value::string("hello");
2543        assert_eq!(v.len(), 5);
2544    }
2545
2546    #[test]
2547    fn test_value_len_null() {
2548        let v = Value::Null;
2549        assert_eq!(v.len(), 0);
2550    }
2551
2552    #[test]
2553    fn test_value_is_empty_string() {
2554        assert!(Value::string("").is_empty());
2555        assert!(!Value::string("a").is_empty());
2556    }
2557
2558    #[test]
2559    fn test_value_get_non_object() {
2560        let v = Value::array(vec![]);
2561        assert!(v.get("key").is_none());
2562    }
2563
2564    #[test]
2565    fn test_value_as_bool_non_bool() {
2566        let v = Value::string("true");
2567        assert!(v.as_bool().is_none());
2568    }
2569
2570    #[test]
2571    fn test_value_as_number_non_number() {
2572        let v = Value::string("42");
2573        assert!(v.as_number().is_none());
2574    }
2575
2576    #[test]
2577    fn test_value_as_str_non_string() {
2578        let v = Value::number(42.0);
2579        assert!(v.as_str().is_none());
2580    }
2581
2582    #[test]
2583    fn test_value_as_array_non_array() {
2584        let v = Value::string("hello");
2585        assert!(v.as_array().is_none());
2586    }
2587
2588    #[test]
2589    fn test_value_from_vec() {
2590        let v: Value = vec![1i32, 2, 3].into();
2591        assert!(v.is_array());
2592        assert_eq!(v.len(), 3);
2593    }
2594
2595    // ===== ExecutionError Display Tests =====
2596
2597    #[test]
2598    fn test_error_display_expected_object() {
2599        assert_eq!(
2600            ExecutionError::ExpectedObject.to_string(),
2601            "expected an object"
2602        );
2603    }
2604
2605    #[test]
2606    fn test_error_display_field_not_found() {
2607        assert_eq!(
2608            ExecutionError::FieldNotFound("missing".to_string()).to_string(),
2609            "field not found: missing"
2610        );
2611    }
2612
2613    #[test]
2614    fn test_error_display_type_mismatch() {
2615        assert_eq!(
2616            ExecutionError::TypeMismatch("expected number".to_string()).to_string(),
2617            "type mismatch: expected number"
2618        );
2619    }
2620
2621    #[test]
2622    fn test_error_display_invalid_transform() {
2623        assert_eq!(
2624            ExecutionError::InvalidTransform("bad window".to_string()).to_string(),
2625            "invalid transform: bad window"
2626        );
2627    }
2628
2629    // ===== Window Parsing Tests =====
2630
2631    #[test]
2632    fn test_parse_window_seconds() {
2633        let executor = ExpressionExecutor::new();
2634        assert_eq!(executor.parse_window("5s").unwrap(), 5000);
2635    }
2636
2637    #[test]
2638    fn test_parse_window_minutes() {
2639        let executor = ExpressionExecutor::new();
2640        assert_eq!(executor.parse_window("2m").unwrap(), 120000);
2641    }
2642
2643    #[test]
2644    fn test_parse_window_hours() {
2645        let executor = ExpressionExecutor::new();
2646        assert_eq!(executor.parse_window("1h").unwrap(), 3600000);
2647    }
2648
2649    #[test]
2650    fn test_parse_window_days() {
2651        let executor = ExpressionExecutor::new();
2652        assert_eq!(executor.parse_window("1d").unwrap(), 86400000);
2653    }
2654
2655    #[test]
2656    fn test_parse_window_milliseconds() {
2657        let executor = ExpressionExecutor::new();
2658        assert_eq!(executor.parse_window("500ms").unwrap(), 500);
2659    }
2660
2661    #[test]
2662    fn test_parse_window_no_unit() {
2663        let executor = ExpressionExecutor::new();
2664        // No unit defaults to milliseconds
2665        assert_eq!(executor.parse_window("1000").unwrap(), 1000);
2666    }
2667
2668    #[test]
2669    fn test_parse_window_empty() {
2670        let executor = ExpressionExecutor::new();
2671        let result = executor.parse_window("");
2672        assert!(result.is_err());
2673    }
2674
2675    #[test]
2676    fn test_parse_window_invalid() {
2677        let executor = ExpressionExecutor::new();
2678        let result = executor.parse_window("abc");
2679        assert!(result.is_err());
2680    }
2681
2682    // ===== Compare Values Tests =====
2683
2684    #[test]
2685    fn test_compare_values_gte() {
2686        let executor = ExpressionExecutor::new();
2687        assert!(executor.compare_values(&Value::Number(50.0), ">=", "50"));
2688        assert!(executor.compare_values(&Value::Number(51.0), "gte", "50"));
2689        assert!(!executor.compare_values(&Value::Number(49.0), ">=", "50"));
2690    }
2691
2692    #[test]
2693    fn test_compare_values_lte() {
2694        let executor = ExpressionExecutor::new();
2695        assert!(executor.compare_values(&Value::Number(50.0), "<=", "50"));
2696        assert!(executor.compare_values(&Value::Number(49.0), "lte", "50"));
2697        assert!(!executor.compare_values(&Value::Number(51.0), "<=", "50"));
2698    }
2699
2700    #[test]
2701    fn test_compare_values_starts_with() {
2702        let executor = ExpressionExecutor::new();
2703        assert!(executor.compare_values(
2704            &Value::String("hello world".to_string()),
2705            "starts_with",
2706            "hello"
2707        ));
2708        assert!(!executor.compare_values(
2709            &Value::String("world hello".to_string()),
2710            "starts_with",
2711            "hello"
2712        ));
2713        assert!(!executor.compare_values(&Value::Number(123.0), "starts_with", "hello"));
2714    }
2715
2716    #[test]
2717    fn test_compare_values_ends_with() {
2718        let executor = ExpressionExecutor::new();
2719        assert!(executor.compare_values(
2720            &Value::String("hello world".to_string()),
2721            "ends_with",
2722            "world"
2723        ));
2724        assert!(!executor.compare_values(
2725            &Value::String("world hello".to_string()),
2726            "ends_with",
2727            "world"
2728        ));
2729        assert!(!executor.compare_values(&Value::Number(123.0), "ends_with", "world"));
2730    }
2731
2732    #[test]
2733    fn test_compare_values_unknown_op() {
2734        let executor = ExpressionExecutor::new();
2735        assert!(!executor.compare_values(&Value::Number(50.0), "unknown", "50"));
2736    }
2737
2738    #[test]
2739    fn test_compare_values_non_numeric_gt() {
2740        let executor = ExpressionExecutor::new();
2741        assert!(!executor.compare_values(&Value::String("abc".to_string()), ">", "50"));
2742    }
2743
2744    // ===== Value Matching Tests =====
2745
2746    #[test]
2747    fn test_value_matches_bool_true() {
2748        let executor = ExpressionExecutor::new();
2749        assert!(executor.value_matches(&Value::Bool(true), "true"));
2750        assert!(!executor.value_matches(&Value::Bool(true), "false"));
2751    }
2752
2753    #[test]
2754    fn test_value_matches_bool_false() {
2755        let executor = ExpressionExecutor::new();
2756        assert!(executor.value_matches(&Value::Bool(false), "false"));
2757        assert!(!executor.value_matches(&Value::Bool(false), "true"));
2758    }
2759
2760    #[test]
2761    fn test_value_matches_number_parse_failure() {
2762        let executor = ExpressionExecutor::new();
2763        assert!(!executor.value_matches(&Value::Number(42.0), "not_a_number"));
2764    }
2765
2766    #[test]
2767    fn test_value_matches_null() {
2768        let executor = ExpressionExecutor::new();
2769        assert!(!executor.value_matches(&Value::Null, "null"));
2770    }
2771
2772    #[test]
2773    fn test_value_matches_array() {
2774        let executor = ExpressionExecutor::new();
2775        assert!(!executor.value_matches(&Value::Array(vec![]), "[]"));
2776    }
2777
2778    // ===== Value To String Tests =====
2779
2780    #[test]
2781    fn test_value_to_string_null() {
2782        let executor = ExpressionExecutor::new();
2783        assert_eq!(executor.value_to_string(&Value::Null), "_null");
2784    }
2785
2786    #[test]
2787    fn test_value_to_string_bool() {
2788        let executor = ExpressionExecutor::new();
2789        assert_eq!(executor.value_to_string(&Value::Bool(true)), "true");
2790        assert_eq!(executor.value_to_string(&Value::Bool(false)), "false");
2791    }
2792
2793    #[test]
2794    fn test_value_to_string_array() {
2795        let executor = ExpressionExecutor::new();
2796        assert_eq!(executor.value_to_string(&Value::Array(vec![])), "_array");
2797    }
2798
2799    #[test]
2800    fn test_value_to_string_object() {
2801        let executor = ExpressionExecutor::new();
2802        assert_eq!(
2803            executor.value_to_string(&Value::Object(HashMap::new())),
2804            "_object"
2805        );
2806    }
2807
2808    // ===== Apply Transform Tests =====
2809
2810    #[test]
2811    fn test_apply_count_object() {
2812        let executor = ExpressionExecutor::new();
2813        let mut obj = HashMap::new();
2814        obj.insert("a".to_string(), Value::number(1.0));
2815        obj.insert("b".to_string(), Value::number(2.0));
2816        let value = Value::Object(obj);
2817        let result = executor.apply_count(&value);
2818        assert_eq!(result.as_number(), Some(2.0));
2819    }
2820
2821    #[test]
2822    fn test_apply_count_string() {
2823        let executor = ExpressionExecutor::new();
2824        let result = executor.apply_count(&Value::String("hello".to_string()));
2825        assert_eq!(result.as_number(), Some(5.0));
2826    }
2827
2828    #[test]
2829    fn test_apply_count_null() {
2830        let executor = ExpressionExecutor::new();
2831        let result = executor.apply_count(&Value::Null);
2832        assert_eq!(result.as_number(), Some(0.0));
2833    }
2834
2835    #[test]
2836    fn test_apply_percentage_non_number() {
2837        let executor = ExpressionExecutor::new();
2838        let result = executor.apply_percentage(&Value::String("hello".to_string()));
2839        assert!(result.is_err());
2840    }
2841
2842    // ===== Map Transform Tests =====
2843
2844    #[test]
2845    fn test_apply_map_field_extract() {
2846        let mut ctx = DataContext::new();
2847        let items = Value::Array(vec![
2848            {
2849                let mut obj = HashMap::new();
2850                obj.insert("name".to_string(), Value::String("Alice".to_string()));
2851                obj.insert("age".to_string(), Value::Number(30.0));
2852                Value::Object(obj)
2853            },
2854            {
2855                let mut obj = HashMap::new();
2856                obj.insert("name".to_string(), Value::String("Bob".to_string()));
2857                obj.insert("age".to_string(), Value::Number(25.0));
2858                Value::Object(obj)
2859            },
2860        ]);
2861        ctx.insert("users", items);
2862
2863        let executor = ExpressionExecutor::new();
2864        let expr = Expression {
2865            source: "users".to_string(),
2866            transforms: vec![Transform::Map {
2867                expr: "item.name".to_string(),
2868            }],
2869        };
2870        let result = executor.execute(&expr, &ctx).unwrap();
2871        let arr = result.as_array().unwrap();
2872        assert_eq!(arr.len(), 2);
2873        assert_eq!(arr[0].as_str(), Some("Alice"));
2874        assert_eq!(arr[1].as_str(), Some("Bob"));
2875    }
2876
2877    #[test]
2878    fn test_apply_map_non_field() {
2879        let mut ctx = DataContext::new();
2880        ctx.insert(
2881            "items",
2882            Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]),
2883        );
2884
2885        let executor = ExpressionExecutor::new();
2886        let expr = Expression {
2887            source: "items".to_string(),
2888            transforms: vec![Transform::Map {
2889                expr: "unknown".to_string(),
2890            }],
2891        };
2892        let result = executor.execute(&expr, &ctx).unwrap();
2893        let arr = result.as_array().unwrap();
2894        // Items returned unchanged when expression can't be parsed
2895        assert_eq!(arr[0].as_number(), Some(1.0));
2896    }
2897
2898    #[test]
2899    fn test_apply_map_missing_field() {
2900        let mut ctx = DataContext::new();
2901        let items = Value::Array(vec![{
2902            let mut obj = HashMap::new();
2903            obj.insert("x".to_string(), Value::Number(1.0));
2904            Value::Object(obj)
2905        }]);
2906        ctx.insert("items", items);
2907
2908        let executor = ExpressionExecutor::new();
2909        let expr = Expression {
2910            source: "items".to_string(),
2911            transforms: vec![Transform::Map {
2912                expr: "item.missing".to_string(),
2913            }],
2914        };
2915        let result = executor.execute(&expr, &ctx).unwrap();
2916        let arr = result.as_array().unwrap();
2917        assert!(arr[0].is_null());
2918    }
2919
2920    // ===== Reduce Transform Tests =====
2921
2922    #[test]
2923    fn test_apply_reduce() {
2924        let mut ctx = DataContext::new();
2925        ctx.insert(
2926            "numbers",
2927            Value::Array(vec![
2928                Value::Number(1.0),
2929                Value::Number(2.0),
2930                Value::Number(3.0),
2931            ]),
2932        );
2933
2934        let executor = ExpressionExecutor::new();
2935        let expr = Expression {
2936            source: "numbers".to_string(),
2937            transforms: vec![Transform::Reduce {
2938                initial: "0".to_string(),
2939                expr: "acc + item".to_string(),
2940            }],
2941        };
2942        let result = executor.execute(&expr, &ctx).unwrap();
2943        assert_eq!(result.as_number(), Some(6.0));
2944    }
2945
2946    #[test]
2947    fn test_apply_reduce_with_initial() {
2948        let mut ctx = DataContext::new();
2949        ctx.insert("numbers", Value::Array(vec![Value::Number(5.0)]));
2950
2951        let executor = ExpressionExecutor::new();
2952        let expr = Expression {
2953            source: "numbers".to_string(),
2954            transforms: vec![Transform::Reduce {
2955                initial: "10".to_string(),
2956                expr: "acc + item".to_string(),
2957            }],
2958        };
2959        let result = executor.execute(&expr, &ctx).unwrap();
2960        assert_eq!(result.as_number(), Some(15.0));
2961    }
2962
2963    #[test]
2964    fn test_apply_reduce_non_numeric() {
2965        let mut ctx = DataContext::new();
2966        ctx.insert(
2967            "items",
2968            Value::Array(vec![
2969                Value::String("a".to_string()),
2970                Value::String("b".to_string()),
2971            ]),
2972        );
2973
2974        let executor = ExpressionExecutor::new();
2975        let expr = Expression {
2976            source: "items".to_string(),
2977            transforms: vec![Transform::Reduce {
2978                initial: "0".to_string(),
2979                expr: "acc".to_string(),
2980            }],
2981        };
2982        let result = executor.execute(&expr, &ctx).unwrap();
2983        // Non-numeric items are skipped
2984        assert_eq!(result.as_number(), Some(0.0));
2985    }
2986
2987    // ===== Aggregate Transform Tests =====
2988
2989    #[test]
2990    fn test_apply_aggregate_sum() {
2991        let ctx = make_test_data();
2992        let executor = ExpressionExecutor::new();
2993        let expr = Expression {
2994            source: "data.transactions".to_string(),
2995            transforms: vec![Transform::Aggregate {
2996                field: "amount".to_string(),
2997                op: AggregateOp::Sum,
2998            }],
2999        };
3000        let result = executor.execute(&expr, &ctx).unwrap();
3001        assert_eq!(result.as_number(), Some(225.0));
3002    }
3003
3004    #[test]
3005    fn test_apply_aggregate_count() {
3006        let ctx = make_test_data();
3007        let executor = ExpressionExecutor::new();
3008        let expr = Expression {
3009            source: "data.transactions".to_string(),
3010            transforms: vec![Transform::Aggregate {
3011                field: "amount".to_string(),
3012                op: AggregateOp::Count,
3013            }],
3014        };
3015        let result = executor.execute(&expr, &ctx).unwrap();
3016        assert_eq!(result.as_number(), Some(3.0));
3017    }
3018
3019    #[test]
3020    fn test_apply_aggregate_mean() {
3021        let ctx = make_test_data();
3022        let executor = ExpressionExecutor::new();
3023        let expr = Expression {
3024            source: "data.transactions".to_string(),
3025            transforms: vec![Transform::Aggregate {
3026                field: "amount".to_string(),
3027                op: AggregateOp::Mean,
3028            }],
3029        };
3030        let result = executor.execute(&expr, &ctx).unwrap();
3031        assert_eq!(result.as_number(), Some(75.0));
3032    }
3033
3034    #[test]
3035    fn test_apply_aggregate_min() {
3036        let ctx = make_test_data();
3037        let executor = ExpressionExecutor::new();
3038        let expr = Expression {
3039            source: "data.transactions".to_string(),
3040            transforms: vec![Transform::Aggregate {
3041                field: "amount".to_string(),
3042                op: AggregateOp::Min,
3043            }],
3044        };
3045        let result = executor.execute(&expr, &ctx).unwrap();
3046        assert_eq!(result.as_number(), Some(50.0));
3047    }
3048
3049    #[test]
3050    fn test_apply_aggregate_max() {
3051        let ctx = make_test_data();
3052        let executor = ExpressionExecutor::new();
3053        let expr = Expression {
3054            source: "data.transactions".to_string(),
3055            transforms: vec![Transform::Aggregate {
3056                field: "amount".to_string(),
3057                op: AggregateOp::Max,
3058            }],
3059        };
3060        let result = executor.execute(&expr, &ctx).unwrap();
3061        assert_eq!(result.as_number(), Some(100.0));
3062    }
3063
3064    #[test]
3065    fn test_apply_aggregate_first() {
3066        let ctx = make_test_data();
3067        let executor = ExpressionExecutor::new();
3068        let expr = Expression {
3069            source: "data.transactions".to_string(),
3070            transforms: vec![Transform::Aggregate {
3071                field: "amount".to_string(),
3072                op: AggregateOp::First,
3073            }],
3074        };
3075        let result = executor.execute(&expr, &ctx).unwrap();
3076        assert_eq!(result.as_number(), Some(100.0));
3077    }
3078
3079    #[test]
3080    fn test_apply_aggregate_last() {
3081        let ctx = make_test_data();
3082        let executor = ExpressionExecutor::new();
3083        let expr = Expression {
3084            source: "data.transactions".to_string(),
3085            transforms: vec![Transform::Aggregate {
3086                field: "amount".to_string(),
3087                op: AggregateOp::Last,
3088            }],
3089        };
3090        let result = executor.execute(&expr, &ctx).unwrap();
3091        assert_eq!(result.as_number(), Some(75.0));
3092    }
3093
3094    #[test]
3095    fn test_apply_aggregate_mean_empty() {
3096        let mut ctx = DataContext::new();
3097        ctx.insert("items", Value::Array(vec![]));
3098
3099        let executor = ExpressionExecutor::new();
3100        let expr = Expression {
3101            source: "items".to_string(),
3102            transforms: vec![Transform::Aggregate {
3103                field: "value".to_string(),
3104                op: AggregateOp::Mean,
3105            }],
3106        };
3107        let result = executor.execute(&expr, &ctx).unwrap();
3108        assert_eq!(result.as_number(), Some(0.0));
3109    }
3110
3111    // ===== Pivot Transform Tests =====
3112
3113    #[test]
3114    fn test_apply_pivot() {
3115        let mut ctx = DataContext::new();
3116        let sales = Value::Array(vec![
3117            {
3118                let mut obj = HashMap::new();
3119                obj.insert("region".to_string(), Value::String("North".to_string()));
3120                obj.insert("product".to_string(), Value::String("A".to_string()));
3121                obj.insert("revenue".to_string(), Value::Number(100.0));
3122                Value::Object(obj)
3123            },
3124            {
3125                let mut obj = HashMap::new();
3126                obj.insert("region".to_string(), Value::String("North".to_string()));
3127                obj.insert("product".to_string(), Value::String("B".to_string()));
3128                obj.insert("revenue".to_string(), Value::Number(150.0));
3129                Value::Object(obj)
3130            },
3131            {
3132                let mut obj = HashMap::new();
3133                obj.insert("region".to_string(), Value::String("South".to_string()));
3134                obj.insert("product".to_string(), Value::String("A".to_string()));
3135                obj.insert("revenue".to_string(), Value::Number(200.0));
3136                Value::Object(obj)
3137            },
3138        ]);
3139        ctx.insert("sales", sales);
3140
3141        let executor = ExpressionExecutor::new();
3142        let expr = Expression {
3143            source: "sales".to_string(),
3144            transforms: vec![Transform::Pivot {
3145                row_field: "region".to_string(),
3146                col_field: "product".to_string(),
3147                value_field: "revenue".to_string(),
3148            }],
3149        };
3150        let result = executor.execute(&expr, &ctx).unwrap();
3151        let arr = result.as_array().unwrap();
3152        assert_eq!(arr.len(), 2); // Two regions
3153    }
3154
3155    // ===== Cumulative Sum Tests =====
3156
3157    #[test]
3158    fn test_apply_cumsum() {
3159        let mut ctx = DataContext::new();
3160        let items = Value::Array(vec![
3161            {
3162                let mut obj = HashMap::new();
3163                obj.insert("value".to_string(), Value::Number(10.0));
3164                Value::Object(obj)
3165            },
3166            {
3167                let mut obj = HashMap::new();
3168                obj.insert("value".to_string(), Value::Number(20.0));
3169                Value::Object(obj)
3170            },
3171            {
3172                let mut obj = HashMap::new();
3173                obj.insert("value".to_string(), Value::Number(30.0));
3174                Value::Object(obj)
3175            },
3176        ]);
3177        ctx.insert("items", items);
3178
3179        let executor = ExpressionExecutor::new();
3180        let expr = Expression {
3181            source: "items".to_string(),
3182            transforms: vec![Transform::CumulativeSum {
3183                field: "value".to_string(),
3184            }],
3185        };
3186        let result = executor.execute(&expr, &ctx).unwrap();
3187        let arr = result.as_array().unwrap();
3188
3189        assert_eq!(arr[0].get("value_cumsum").unwrap().as_number(), Some(10.0));
3190        assert_eq!(arr[1].get("value_cumsum").unwrap().as_number(), Some(30.0));
3191        assert_eq!(arr[2].get("value_cumsum").unwrap().as_number(), Some(60.0));
3192    }
3193
3194    #[test]
3195    fn test_apply_cumsum_non_object() {
3196        let mut ctx = DataContext::new();
3197        ctx.insert(
3198            "items",
3199            Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]),
3200        );
3201
3202        let executor = ExpressionExecutor::new();
3203        let expr = Expression {
3204            source: "items".to_string(),
3205            transforms: vec![Transform::CumulativeSum {
3206                field: "x".to_string(),
3207            }],
3208        };
3209        let result = executor.execute(&expr, &ctx).unwrap();
3210        let arr = result.as_array().unwrap();
3211        // Non-objects returned as-is
3212        assert_eq!(arr[0].as_number(), Some(1.0));
3213    }
3214
3215    // ===== Rank Transform Tests =====
3216
3217    #[test]
3218    fn test_apply_rank_dense() {
3219        let mut ctx = DataContext::new();
3220        let items = Value::Array(vec![
3221            {
3222                let mut obj = HashMap::new();
3223                obj.insert("score".to_string(), Value::Number(100.0));
3224                Value::Object(obj)
3225            },
3226            {
3227                let mut obj = HashMap::new();
3228                obj.insert("score".to_string(), Value::Number(90.0));
3229                Value::Object(obj)
3230            },
3231            {
3232                let mut obj = HashMap::new();
3233                obj.insert("score".to_string(), Value::Number(100.0));
3234                Value::Object(obj)
3235            },
3236        ]);
3237        ctx.insert("items", items);
3238
3239        let executor = ExpressionExecutor::new();
3240        let expr = Expression {
3241            source: "items".to_string(),
3242            transforms: vec![Transform::Rank {
3243                field: "score".to_string(),
3244                method: RankMethod::Dense,
3245            }],
3246        };
3247        let result = executor.execute(&expr, &ctx).unwrap();
3248        let arr = result.as_array().unwrap();
3249
3250        // Both 100s should have rank 1, 90 should have rank 2
3251        assert_eq!(arr[0].get("score_rank").unwrap().as_number(), Some(1.0));
3252        assert_eq!(arr[1].get("score_rank").unwrap().as_number(), Some(2.0));
3253        assert_eq!(arr[2].get("score_rank").unwrap().as_number(), Some(1.0));
3254    }
3255
3256    #[test]
3257    fn test_apply_rank_ordinal() {
3258        let mut ctx = DataContext::new();
3259        let items = Value::Array(vec![
3260            {
3261                let mut obj = HashMap::new();
3262                obj.insert("score".to_string(), Value::Number(100.0));
3263                Value::Object(obj)
3264            },
3265            {
3266                let mut obj = HashMap::new();
3267                obj.insert("score".to_string(), Value::Number(90.0));
3268                Value::Object(obj)
3269            },
3270        ]);
3271        ctx.insert("items", items);
3272
3273        let executor = ExpressionExecutor::new();
3274        let expr = Expression {
3275            source: "items".to_string(),
3276            transforms: vec![Transform::Rank {
3277                field: "score".to_string(),
3278                method: RankMethod::Ordinal,
3279            }],
3280        };
3281        let result = executor.execute(&expr, &ctx).unwrap();
3282        let arr = result.as_array().unwrap();
3283
3284        assert_eq!(arr[0].get("score_rank").unwrap().as_number(), Some(1.0));
3285        assert_eq!(arr[1].get("score_rank").unwrap().as_number(), Some(2.0));
3286    }
3287
3288    #[test]
3289    fn test_apply_rank_average() {
3290        let mut ctx = DataContext::new();
3291        let items = Value::Array(vec![
3292            {
3293                let mut obj = HashMap::new();
3294                obj.insert("score".to_string(), Value::Number(100.0));
3295                Value::Object(obj)
3296            },
3297            {
3298                let mut obj = HashMap::new();
3299                obj.insert("score".to_string(), Value::Number(100.0));
3300                Value::Object(obj)
3301            },
3302            {
3303                let mut obj = HashMap::new();
3304                obj.insert("score".to_string(), Value::Number(80.0));
3305                Value::Object(obj)
3306            },
3307        ]);
3308        ctx.insert("items", items);
3309
3310        let executor = ExpressionExecutor::new();
3311        let expr = Expression {
3312            source: "items".to_string(),
3313            transforms: vec![Transform::Rank {
3314                field: "score".to_string(),
3315                method: RankMethod::Average,
3316            }],
3317        };
3318        let result = executor.execute(&expr, &ctx).unwrap();
3319        let arr = result.as_array().unwrap();
3320
3321        // Ties at 100 get average of ranks 1 and 2 = 1.5
3322        assert_eq!(arr[0].get("score_rank").unwrap().as_number(), Some(1.5));
3323        assert_eq!(arr[1].get("score_rank").unwrap().as_number(), Some(1.5));
3324        assert_eq!(arr[2].get("score_rank").unwrap().as_number(), Some(3.0));
3325    }
3326
3327    #[test]
3328    fn test_apply_rank_non_object() {
3329        let mut ctx = DataContext::new();
3330        ctx.insert(
3331            "items",
3332            Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]),
3333        );
3334
3335        let executor = ExpressionExecutor::new();
3336        let expr = Expression {
3337            source: "items".to_string(),
3338            transforms: vec![Transform::Rank {
3339                field: "x".to_string(),
3340                method: RankMethod::Dense,
3341            }],
3342        };
3343        let result = executor.execute(&expr, &ctx).unwrap();
3344        let arr = result.as_array().unwrap();
3345        // Non-objects returned as-is
3346        assert_eq!(arr[0].as_number(), Some(1.0));
3347    }
3348
3349    // ===== Moving Average Tests =====
3350
3351    #[test]
3352    fn test_apply_moving_average() {
3353        let mut ctx = DataContext::new();
3354        let items = Value::Array(vec![
3355            {
3356                let mut obj = HashMap::new();
3357                obj.insert("value".to_string(), Value::Number(10.0));
3358                Value::Object(obj)
3359            },
3360            {
3361                let mut obj = HashMap::new();
3362                obj.insert("value".to_string(), Value::Number(20.0));
3363                Value::Object(obj)
3364            },
3365            {
3366                let mut obj = HashMap::new();
3367                obj.insert("value".to_string(), Value::Number(30.0));
3368                Value::Object(obj)
3369            },
3370            {
3371                let mut obj = HashMap::new();
3372                obj.insert("value".to_string(), Value::Number(40.0));
3373                Value::Object(obj)
3374            },
3375        ]);
3376        ctx.insert("items", items);
3377
3378        let executor = ExpressionExecutor::new();
3379        let expr = Expression {
3380            source: "items".to_string(),
3381            transforms: vec![Transform::MovingAverage {
3382                field: "value".to_string(),
3383                window: 2,
3384            }],
3385        };
3386        let result = executor.execute(&expr, &ctx).unwrap();
3387        let arr = result.as_array().unwrap();
3388
3389        // First: just 10 (window of 1)
3390        assert_eq!(arr[0].get("value_ma2").unwrap().as_number(), Some(10.0));
3391        // Second: (10 + 20) / 2 = 15
3392        assert_eq!(arr[1].get("value_ma2").unwrap().as_number(), Some(15.0));
3393        // Third: (20 + 30) / 2 = 25
3394        assert_eq!(arr[2].get("value_ma2").unwrap().as_number(), Some(25.0));
3395    }
3396
3397    #[test]
3398    fn test_apply_moving_average_non_object() {
3399        let mut ctx = DataContext::new();
3400        ctx.insert("items", Value::Array(vec![Value::Number(1.0)]));
3401
3402        let executor = ExpressionExecutor::new();
3403        let expr = Expression {
3404            source: "items".to_string(),
3405            transforms: vec![Transform::MovingAverage {
3406                field: "x".to_string(),
3407                window: 3,
3408            }],
3409        };
3410        let result = executor.execute(&expr, &ctx).unwrap();
3411        let arr = result.as_array().unwrap();
3412        assert_eq!(arr[0].as_number(), Some(1.0));
3413    }
3414
3415    // ===== Percent Change Tests =====
3416
3417    #[test]
3418    fn test_apply_percent_change() {
3419        let mut ctx = DataContext::new();
3420        let items = Value::Array(vec![
3421            {
3422                let mut obj = HashMap::new();
3423                obj.insert("price".to_string(), Value::Number(100.0));
3424                Value::Object(obj)
3425            },
3426            {
3427                let mut obj = HashMap::new();
3428                obj.insert("price".to_string(), Value::Number(110.0));
3429                Value::Object(obj)
3430            },
3431            {
3432                let mut obj = HashMap::new();
3433                obj.insert("price".to_string(), Value::Number(99.0));
3434                Value::Object(obj)
3435            },
3436        ]);
3437        ctx.insert("items", items);
3438
3439        let executor = ExpressionExecutor::new();
3440        let expr = Expression {
3441            source: "items".to_string(),
3442            transforms: vec![Transform::PercentChange {
3443                field: "price".to_string(),
3444            }],
3445        };
3446        let result = executor.execute(&expr, &ctx).unwrap();
3447        let arr = result.as_array().unwrap();
3448
3449        // First: 0% (no previous)
3450        assert_eq!(
3451            arr[0].get("price_pct_change").unwrap().as_number(),
3452            Some(0.0)
3453        );
3454        // Second: (110 - 100) / 100 * 100 = 10%
3455        assert_eq!(
3456            arr[1].get("price_pct_change").unwrap().as_number(),
3457            Some(10.0)
3458        );
3459        // Third: (99 - 110) / 110 * 100 = -10%
3460        let pct = arr[2].get("price_pct_change").unwrap().as_number().unwrap();
3461        assert!((pct - (-10.0)).abs() < 0.1);
3462    }
3463
3464    #[test]
3465    fn test_apply_percent_change_zero_prev() {
3466        let mut ctx = DataContext::new();
3467        let items = Value::Array(vec![
3468            {
3469                let mut obj = HashMap::new();
3470                obj.insert("value".to_string(), Value::Number(0.0));
3471                Value::Object(obj)
3472            },
3473            {
3474                let mut obj = HashMap::new();
3475                obj.insert("value".to_string(), Value::Number(10.0));
3476                Value::Object(obj)
3477            },
3478        ]);
3479        ctx.insert("items", items);
3480
3481        let executor = ExpressionExecutor::new();
3482        let expr = Expression {
3483            source: "items".to_string(),
3484            transforms: vec![Transform::PercentChange {
3485                field: "value".to_string(),
3486            }],
3487        };
3488        let result = executor.execute(&expr, &ctx).unwrap();
3489        let arr = result.as_array().unwrap();
3490
3491        // When previous is 0, return 0 to avoid division by zero
3492        assert_eq!(
3493            arr[1].get("value_pct_change").unwrap().as_number(),
3494            Some(0.0)
3495        );
3496    }
3497
3498    #[test]
3499    fn test_apply_percent_change_non_object() {
3500        let mut ctx = DataContext::new();
3501        ctx.insert("items", Value::Array(vec![Value::Number(1.0)]));
3502
3503        let executor = ExpressionExecutor::new();
3504        let expr = Expression {
3505            source: "items".to_string(),
3506            transforms: vec![Transform::PercentChange {
3507                field: "x".to_string(),
3508            }],
3509        };
3510        let result = executor.execute(&expr, &ctx).unwrap();
3511        let arr = result.as_array().unwrap();
3512        assert_eq!(arr[0].as_number(), Some(1.0));
3513    }
3514
3515    // ===== Sort Tests =====
3516
3517    #[test]
3518    fn test_sort_by_string() {
3519        let mut ctx = DataContext::new();
3520        let items = Value::Array(vec![
3521            {
3522                let mut obj = HashMap::new();
3523                obj.insert("name".to_string(), Value::String("Charlie".to_string()));
3524                Value::Object(obj)
3525            },
3526            {
3527                let mut obj = HashMap::new();
3528                obj.insert("name".to_string(), Value::String("Alice".to_string()));
3529                Value::Object(obj)
3530            },
3531            {
3532                let mut obj = HashMap::new();
3533                obj.insert("name".to_string(), Value::String("Bob".to_string()));
3534                Value::Object(obj)
3535            },
3536        ]);
3537        ctx.insert("items", items);
3538
3539        let parser = ExpressionParser::new();
3540        let executor = ExpressionExecutor::new();
3541
3542        let expr = parser.parse("{{ items | sort(name) }}").unwrap();
3543        let result = executor.execute(&expr, &ctx).unwrap();
3544        let arr = result.as_array().unwrap();
3545
3546        assert_eq!(arr[0].get("name").unwrap().as_str(), Some("Alice"));
3547        assert_eq!(arr[1].get("name").unwrap().as_str(), Some("Bob"));
3548        assert_eq!(arr[2].get("name").unwrap().as_str(), Some("Charlie"));
3549    }
3550
3551    #[test]
3552    fn test_sort_missing_field() {
3553        let mut ctx = DataContext::new();
3554        let items = Value::Array(vec![
3555            {
3556                let mut obj = HashMap::new();
3557                obj.insert("x".to_string(), Value::Number(1.0));
3558                Value::Object(obj)
3559            },
3560            {
3561                let mut obj = HashMap::new();
3562                obj.insert("y".to_string(), Value::Number(2.0));
3563                Value::Object(obj)
3564            },
3565        ]);
3566        ctx.insert("items", items);
3567
3568        let parser = ExpressionParser::new();
3569        let executor = ExpressionExecutor::new();
3570
3571        let expr = parser.parse("{{ items | sort(z) }}").unwrap();
3572        let result = executor.execute(&expr, &ctx).unwrap();
3573        // Should not crash, items remain in original order when field missing
3574        assert_eq!(result.len(), 2);
3575    }
3576
3577    // ===== Select Tests =====
3578
3579    #[test]
3580    fn test_select_non_object_items() {
3581        let mut ctx = DataContext::new();
3582        ctx.insert(
3583            "items",
3584            Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]),
3585        );
3586
3587        let parser = ExpressionParser::new();
3588        let executor = ExpressionExecutor::new();
3589
3590        let expr = parser.parse("{{ items | select(x) }}").unwrap();
3591        let result = executor.execute(&expr, &ctx).unwrap();
3592        let arr = result.as_array().unwrap();
3593        // Non-objects returned as-is
3594        assert_eq!(arr[0].as_number(), Some(1.0));
3595    }
3596
3597    // ===== Rate Edge Cases =====
3598
3599    #[test]
3600    fn test_rate_insufficient_data() {
3601        let mut ctx = DataContext::new();
3602        ctx.insert(
3603            "events",
3604            Value::Array(vec![{
3605                let mut e = HashMap::new();
3606                e.insert("timestamp".to_string(), Value::Number(1000.0));
3607                Value::Object(e)
3608            }]),
3609        );
3610
3611        let parser = ExpressionParser::new();
3612        let executor = ExpressionExecutor::new();
3613
3614        let expr = parser.parse("{{ events | rate(1s) }}").unwrap();
3615        let result = executor.execute(&expr, &ctx).unwrap();
3616        // With only 1 data point, rate should be 0
3617        assert_eq!(result.as_number(), Some(0.0));
3618    }
3619
3620    #[test]
3621    fn test_rate_with_time_field() {
3622        let mut ctx = DataContext::new();
3623        let events: Vec<Value> = vec![
3624            {
3625                let mut e = HashMap::new();
3626                e.insert("time".to_string(), Value::Number(1000.0));
3627                e.insert("count".to_string(), Value::Number(5.0));
3628                Value::Object(e)
3629            },
3630            {
3631                let mut e = HashMap::new();
3632                e.insert("time".to_string(), Value::Number(2000.0));
3633                e.insert("count".to_string(), Value::Number(10.0));
3634                Value::Object(e)
3635            },
3636        ];
3637        ctx.insert("events", Value::Array(events));
3638
3639        let parser = ExpressionParser::new();
3640        let executor = ExpressionExecutor::new();
3641
3642        let expr = parser.parse("{{ events | rate(5s) }}").unwrap();
3643        let result = executor.execute(&expr, &ctx).unwrap();
3644        assert!(result.is_number());
3645    }
3646
3647    // ===== Suggest Edge Cases =====
3648
3649    #[test]
3650    fn test_suggest_count_limit() {
3651        let mut ctx = DataContext::new();
3652        let suggestions = Value::Array(vec![
3653            {
3654                let mut obj = HashMap::new();
3655                obj.insert("text".to_string(), Value::String("git status".to_string()));
3656                Value::Object(obj)
3657            },
3658            {
3659                let mut obj = HashMap::new();
3660                obj.insert("text".to_string(), Value::String("git commit".to_string()));
3661                Value::Object(obj)
3662            },
3663            {
3664                let mut obj = HashMap::new();
3665                obj.insert("text".to_string(), Value::String("git push".to_string()));
3666                Value::Object(obj)
3667            },
3668        ]);
3669        ctx.insert("suggestions", suggestions);
3670
3671        let parser = ExpressionParser::new();
3672        let executor = ExpressionExecutor::new();
3673
3674        let expr = parser.parse("{{ suggestions | suggest(git, 2) }}").unwrap();
3675        let result = executor.execute(&expr, &ctx).unwrap();
3676        let arr = result.as_array().unwrap();
3677        assert_eq!(arr.len(), 2); // Limited to 2
3678    }
3679
3680    #[test]
3681    fn test_suggest_with_source_field() {
3682        let mut ctx = DataContext::new();
3683        let mut model = HashMap::new();
3684        model.insert("source".to_string(), Value::String("model.apr".to_string()));
3685        ctx.insert("model", Value::Object(model));
3686
3687        let parser = ExpressionParser::new();
3688        let executor = ExpressionExecutor::new();
3689
3690        let expr = parser.parse("{{ model | suggest(git, 5) }}").unwrap();
3691        let result = executor.execute(&expr, &ctx).unwrap();
3692        let arr = result.as_array().unwrap();
3693        // Model with source but no _suggestions returns empty
3694        assert!(arr.is_empty());
3695    }
3696
3697    #[test]
3698    fn test_suggest_non_array_non_object() {
3699        let mut ctx = DataContext::new();
3700        ctx.insert("value", Value::Number(42.0));
3701
3702        let parser = ExpressionParser::new();
3703        let executor = ExpressionExecutor::new();
3704
3705        let expr = parser.parse("{{ value | suggest(x, 5) }}").unwrap();
3706        let result = executor.execute(&expr, &ctx).unwrap();
3707        let arr = result.as_array().unwrap();
3708        assert!(arr.is_empty());
3709    }
3710
3711    // ===== Additional Coverage Tests =====
3712
3713    #[test]
3714    fn test_value_from_string_owned() {
3715        let s = String::from("owned");
3716        let v: Value = s.into();
3717        assert_eq!(v.as_str(), Some("owned"));
3718    }
3719
3720    #[test]
3721    fn test_value_is_type_methods() {
3722        assert!(Value::Null.is_null());
3723        assert!(!Value::Null.is_bool());
3724        assert!(!Value::Null.is_number());
3725        assert!(!Value::Null.is_string());
3726        assert!(!Value::Null.is_array());
3727        assert!(!Value::Null.is_object());
3728
3729        assert!(Value::Bool(true).is_bool());
3730        assert!(Value::Number(1.0).is_number());
3731        assert!(Value::String("s".to_string()).is_string());
3732        assert!(Value::Array(vec![]).is_array());
3733        assert!(Value::Object(HashMap::new()).is_object());
3734    }
3735
3736    #[test]
3737    fn test_execution_error_is_error_trait() {
3738        let err = ExecutionError::SourceNotFound("test".to_string());
3739        let _: &dyn std::error::Error = &err;
3740    }
3741
3742    #[test]
3743    fn test_data_context_get_nested_path() {
3744        let mut ctx = DataContext::new();
3745        let mut inner = HashMap::new();
3746        let mut deep = HashMap::new();
3747        deep.insert("value".to_string(), Value::number(42.0));
3748        inner.insert("nested".to_string(), Value::Object(deep));
3749        ctx.insert("data", Value::Object(inner));
3750
3751        assert_eq!(
3752            ctx.get("data.nested.value").unwrap().as_number(),
3753            Some(42.0)
3754        );
3755        assert!(ctx.get("data.nonexistent").is_none());
3756        assert!(ctx.get("nonexistent.path").is_none());
3757    }
3758
3759    #[test]
3760    fn test_filter_on_non_object_items() {
3761        let mut ctx = DataContext::new();
3762        ctx.insert(
3763            "items",
3764            Value::Array(vec![Value::Number(1.0), Value::String("test".to_string())]),
3765        );
3766
3767        let parser = ExpressionParser::new();
3768        let executor = ExpressionExecutor::new();
3769
3770        let expr = parser.parse("{{ items | filter(x=1) }}").unwrap();
3771        let result = executor.execute(&expr, &ctx).unwrap();
3772        // Non-objects should be filtered out
3773        assert!(result.as_array().unwrap().is_empty());
3774    }
3775
3776    #[test]
3777    fn test_filter_non_array() {
3778        let mut ctx = DataContext::new();
3779        ctx.insert("value", Value::Number(42.0));
3780
3781        let executor = ExpressionExecutor::new();
3782        let expr = Expression {
3783            source: "value".to_string(),
3784            transforms: vec![Transform::Filter {
3785                field: "x".to_string(),
3786                value: "1".to_string(),
3787            }],
3788        };
3789        let result = executor.execute(&expr, &ctx);
3790        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3791    }
3792
3793    #[test]
3794    fn test_select_non_array() {
3795        let mut ctx = DataContext::new();
3796        ctx.insert("value", Value::Number(42.0));
3797
3798        let executor = ExpressionExecutor::new();
3799        let expr = Expression {
3800            source: "value".to_string(),
3801            transforms: vec![Transform::Select {
3802                fields: vec!["x".to_string()],
3803            }],
3804        };
3805        let result = executor.execute(&expr, &ctx);
3806        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3807    }
3808
3809    #[test]
3810    fn test_sort_non_array() {
3811        let mut ctx = DataContext::new();
3812        ctx.insert("value", Value::Number(42.0));
3813
3814        let executor = ExpressionExecutor::new();
3815        let expr = Expression {
3816            source: "value".to_string(),
3817            transforms: vec![Transform::Sort {
3818                field: "x".to_string(),
3819                desc: false,
3820            }],
3821        };
3822        let result = executor.execute(&expr, &ctx);
3823        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3824    }
3825
3826    #[test]
3827    fn test_limit_non_array() {
3828        let mut ctx = DataContext::new();
3829        ctx.insert("value", Value::Number(42.0));
3830
3831        let executor = ExpressionExecutor::new();
3832        let expr = Expression {
3833            source: "value".to_string(),
3834            transforms: vec![Transform::Limit { n: 5 }],
3835        };
3836        let result = executor.execute(&expr, &ctx);
3837        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3838    }
3839
3840    #[test]
3841    fn test_sum_non_array() {
3842        let mut ctx = DataContext::new();
3843        ctx.insert("value", Value::Number(42.0));
3844
3845        let executor = ExpressionExecutor::new();
3846        let expr = Expression {
3847            source: "value".to_string(),
3848            transforms: vec![Transform::Sum {
3849                field: "x".to_string(),
3850            }],
3851        };
3852        let result = executor.execute(&expr, &ctx);
3853        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3854    }
3855
3856    #[test]
3857    fn test_mean_non_array() {
3858        let mut ctx = DataContext::new();
3859        ctx.insert("value", Value::Number(42.0));
3860
3861        let executor = ExpressionExecutor::new();
3862        let expr = Expression {
3863            source: "value".to_string(),
3864            transforms: vec![Transform::Mean {
3865                field: "x".to_string(),
3866            }],
3867        };
3868        let result = executor.execute(&expr, &ctx);
3869        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3870    }
3871
3872    #[test]
3873    fn test_sample_non_array() {
3874        let mut ctx = DataContext::new();
3875        ctx.insert("value", Value::Number(42.0));
3876
3877        let executor = ExpressionExecutor::new();
3878        let expr = Expression {
3879            source: "value".to_string(),
3880            transforms: vec![Transform::Sample { n: 5 }],
3881        };
3882        let result = executor.execute(&expr, &ctx);
3883        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3884    }
3885
3886    #[test]
3887    fn test_rate_non_array() {
3888        let mut ctx = DataContext::new();
3889        ctx.insert("value", Value::Number(42.0));
3890
3891        let executor = ExpressionExecutor::new();
3892        let expr = Expression {
3893            source: "value".to_string(),
3894            transforms: vec![Transform::Rate {
3895                window: "1s".to_string(),
3896            }],
3897        };
3898        let result = executor.execute(&expr, &ctx);
3899        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3900    }
3901
3902    #[test]
3903    fn test_join_non_array_left() {
3904        let mut ctx = DataContext::new();
3905        ctx.insert("left", Value::Number(42.0));
3906        ctx.insert("right", Value::Array(vec![]));
3907
3908        let executor = ExpressionExecutor::new();
3909        let expr = Expression {
3910            source: "left".to_string(),
3911            transforms: vec![Transform::Join {
3912                other: "right".to_string(),
3913                on: "id".to_string(),
3914            }],
3915        };
3916        let result = executor.execute(&expr, &ctx);
3917        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3918    }
3919
3920    #[test]
3921    fn test_join_non_array_right() {
3922        let mut ctx = DataContext::new();
3923        ctx.insert("left", Value::Array(vec![]));
3924        ctx.insert("right", Value::Number(42.0));
3925
3926        let executor = ExpressionExecutor::new();
3927        let expr = Expression {
3928            source: "left".to_string(),
3929            transforms: vec![Transform::Join {
3930                other: "right".to_string(),
3931                on: "id".to_string(),
3932            }],
3933        };
3934        let result = executor.execute(&expr, &ctx);
3935        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3936    }
3937
3938    #[test]
3939    fn test_group_by_non_array() {
3940        let mut ctx = DataContext::new();
3941        ctx.insert("value", Value::Number(42.0));
3942
3943        let executor = ExpressionExecutor::new();
3944        let expr = Expression {
3945            source: "value".to_string(),
3946            transforms: vec![Transform::GroupBy {
3947                field: "x".to_string(),
3948            }],
3949        };
3950        let result = executor.execute(&expr, &ctx);
3951        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
3952    }
3953
3954    #[test]
3955    fn test_group_by_non_object_items() {
3956        let mut ctx = DataContext::new();
3957        ctx.insert(
3958            "items",
3959            Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]),
3960        );
3961
3962        let executor = ExpressionExecutor::new();
3963        let expr = Expression {
3964            source: "items".to_string(),
3965            transforms: vec![Transform::GroupBy {
3966                field: "x".to_string(),
3967            }],
3968        };
3969        let result = executor.execute(&expr, &ctx).unwrap();
3970        let arr = result.as_array().unwrap();
3971        // All non-objects go to "_null" group
3972        assert_eq!(arr.len(), 1);
3973        assert_eq!(arr[0].get("key").unwrap().as_str(), Some("_null"));
3974    }
3975
3976    #[test]
3977    fn test_group_by_missing_field() {
3978        let mut ctx = DataContext::new();
3979        let items = Value::Array(vec![{
3980            let mut obj = HashMap::new();
3981            obj.insert("x".to_string(), Value::Number(1.0));
3982            Value::Object(obj)
3983        }]);
3984        ctx.insert("items", items);
3985
3986        let executor = ExpressionExecutor::new();
3987        let expr = Expression {
3988            source: "items".to_string(),
3989            transforms: vec![Transform::GroupBy {
3990                field: "missing".to_string(),
3991            }],
3992        };
3993        let result = executor.execute(&expr, &ctx).unwrap();
3994        let arr = result.as_array().unwrap();
3995        // Missing field goes to "_null" group
3996        assert_eq!(arr.len(), 1);
3997        assert_eq!(arr[0].get("key").unwrap().as_str(), Some("_null"));
3998    }
3999
4000    #[test]
4001    fn test_distinct_non_array() {
4002        let mut ctx = DataContext::new();
4003        ctx.insert("value", Value::Number(42.0));
4004
4005        let executor = ExpressionExecutor::new();
4006        let expr = Expression {
4007            source: "value".to_string(),
4008            transforms: vec![Transform::Distinct { field: None }],
4009        };
4010        let result = executor.execute(&expr, &ctx);
4011        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4012    }
4013
4014    #[test]
4015    fn test_distinct_with_field_non_object() {
4016        let mut ctx = DataContext::new();
4017        ctx.insert(
4018            "items",
4019            Value::Array(vec![
4020                Value::Number(1.0),
4021                Value::Number(1.0),
4022                Value::Number(2.0),
4023            ]),
4024        );
4025
4026        let executor = ExpressionExecutor::new();
4027        let expr = Expression {
4028            source: "items".to_string(),
4029            transforms: vec![Transform::Distinct {
4030                field: Some("x".to_string()),
4031            }],
4032        };
4033        let result = executor.execute(&expr, &ctx).unwrap();
4034        let arr = result.as_array().unwrap();
4035        // Uses value_to_string on non-objects
4036        assert_eq!(arr.len(), 2);
4037    }
4038
4039    #[test]
4040    fn test_where_non_array() {
4041        let mut ctx = DataContext::new();
4042        ctx.insert("value", Value::Number(42.0));
4043
4044        let executor = ExpressionExecutor::new();
4045        let expr = Expression {
4046            source: "value".to_string(),
4047            transforms: vec![Transform::Where {
4048                field: "x".to_string(),
4049                op: "eq".to_string(),
4050                value: "1".to_string(),
4051            }],
4052        };
4053        let result = executor.execute(&expr, &ctx);
4054        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4055    }
4056
4057    #[test]
4058    fn test_offset_non_array() {
4059        let mut ctx = DataContext::new();
4060        ctx.insert("value", Value::Number(42.0));
4061
4062        let executor = ExpressionExecutor::new();
4063        let expr = Expression {
4064            source: "value".to_string(),
4065            transforms: vec![Transform::Offset { n: 5 }],
4066        };
4067        let result = executor.execute(&expr, &ctx);
4068        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4069    }
4070
4071    #[test]
4072    fn test_min_non_array() {
4073        let mut ctx = DataContext::new();
4074        ctx.insert("value", Value::Number(42.0));
4075
4076        let executor = ExpressionExecutor::new();
4077        let expr = Expression {
4078            source: "value".to_string(),
4079            transforms: vec![Transform::Min {
4080                field: "x".to_string(),
4081            }],
4082        };
4083        let result = executor.execute(&expr, &ctx);
4084        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4085    }
4086
4087    #[test]
4088    fn test_max_non_array() {
4089        let mut ctx = DataContext::new();
4090        ctx.insert("value", Value::Number(42.0));
4091
4092        let executor = ExpressionExecutor::new();
4093        let expr = Expression {
4094            source: "value".to_string(),
4095            transforms: vec![Transform::Max {
4096                field: "x".to_string(),
4097            }],
4098        };
4099        let result = executor.execute(&expr, &ctx);
4100        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4101    }
4102
4103    #[test]
4104    fn test_max_empty() {
4105        let mut ctx = DataContext::new();
4106        ctx.insert("items", Value::Array(vec![]));
4107
4108        let executor = ExpressionExecutor::new();
4109        let expr = Expression {
4110            source: "items".to_string(),
4111            transforms: vec![Transform::Max {
4112                field: "x".to_string(),
4113            }],
4114        };
4115        let result = executor.execute(&expr, &ctx).unwrap();
4116        assert!(result.is_null());
4117    }
4118
4119    #[test]
4120    fn test_first_non_array() {
4121        let mut ctx = DataContext::new();
4122        ctx.insert("value", Value::Number(42.0));
4123
4124        let executor = ExpressionExecutor::new();
4125        let expr = Expression {
4126            source: "value".to_string(),
4127            transforms: vec![Transform::First { n: 5 }],
4128        };
4129        let result = executor.execute(&expr, &ctx);
4130        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4131    }
4132
4133    #[test]
4134    fn test_last_non_array() {
4135        let mut ctx = DataContext::new();
4136        ctx.insert("value", Value::Number(42.0));
4137
4138        let executor = ExpressionExecutor::new();
4139        let expr = Expression {
4140            source: "value".to_string(),
4141            transforms: vec![Transform::Last { n: 5 }],
4142        };
4143        let result = executor.execute(&expr, &ctx);
4144        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4145    }
4146
4147    #[test]
4148    fn test_flatten_non_array() {
4149        let mut ctx = DataContext::new();
4150        ctx.insert("value", Value::Number(42.0));
4151
4152        let executor = ExpressionExecutor::new();
4153        let expr = Expression {
4154            source: "value".to_string(),
4155            transforms: vec![Transform::Flatten],
4156        };
4157        let result = executor.execute(&expr, &ctx);
4158        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4159    }
4160
4161    #[test]
4162    fn test_reverse_non_array() {
4163        let mut ctx = DataContext::new();
4164        ctx.insert("value", Value::Number(42.0));
4165
4166        let executor = ExpressionExecutor::new();
4167        let expr = Expression {
4168            source: "value".to_string(),
4169            transforms: vec![Transform::Reverse],
4170        };
4171        let result = executor.execute(&expr, &ctx);
4172        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4173    }
4174
4175    #[test]
4176    fn test_map_non_array() {
4177        let mut ctx = DataContext::new();
4178        ctx.insert("value", Value::Number(42.0));
4179
4180        let executor = ExpressionExecutor::new();
4181        let expr = Expression {
4182            source: "value".to_string(),
4183            transforms: vec![Transform::Map {
4184                expr: "item.x".to_string(),
4185            }],
4186        };
4187        let result = executor.execute(&expr, &ctx);
4188        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4189    }
4190
4191    #[test]
4192    fn test_reduce_non_array() {
4193        let mut ctx = DataContext::new();
4194        ctx.insert("value", Value::Number(42.0));
4195
4196        let executor = ExpressionExecutor::new();
4197        let expr = Expression {
4198            source: "value".to_string(),
4199            transforms: vec![Transform::Reduce {
4200                initial: "0".to_string(),
4201                expr: "acc + item".to_string(),
4202            }],
4203        };
4204        let result = executor.execute(&expr, &ctx);
4205        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4206    }
4207
4208    #[test]
4209    fn test_reduce_invalid_initial() {
4210        let mut ctx = DataContext::new();
4211        ctx.insert("items", Value::Array(vec![Value::Number(1.0)]));
4212
4213        let executor = ExpressionExecutor::new();
4214        let expr = Expression {
4215            source: "items".to_string(),
4216            transforms: vec![Transform::Reduce {
4217                initial: "not_a_number".to_string(),
4218                expr: "acc + item".to_string(),
4219            }],
4220        };
4221        let result = executor.execute(&expr, &ctx).unwrap();
4222        // Invalid initial defaults to 0
4223        assert_eq!(result.as_number(), Some(1.0));
4224    }
4225
4226    #[test]
4227    fn test_aggregate_non_array() {
4228        let mut ctx = DataContext::new();
4229        ctx.insert("value", Value::Number(42.0));
4230
4231        let executor = ExpressionExecutor::new();
4232        let expr = Expression {
4233            source: "value".to_string(),
4234            transforms: vec![Transform::Aggregate {
4235                field: "x".to_string(),
4236                op: AggregateOp::Sum,
4237            }],
4238        };
4239        let result = executor.execute(&expr, &ctx);
4240        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4241    }
4242
4243    #[test]
4244    fn test_aggregate_with_grouped_data() {
4245        let mut ctx = DataContext::new();
4246        let grouped = Value::Array(vec![{
4247            let mut obj = HashMap::new();
4248            obj.insert("key".to_string(), Value::String("A".to_string()));
4249            obj.insert(
4250                "values".to_string(),
4251                Value::Array(vec![
4252                    {
4253                        let mut v = HashMap::new();
4254                        v.insert("amount".to_string(), Value::Number(10.0));
4255                        Value::Object(v)
4256                    },
4257                    {
4258                        let mut v = HashMap::new();
4259                        v.insert("amount".to_string(), Value::Number(20.0));
4260                        Value::Object(v)
4261                    },
4262                ]),
4263            );
4264            Value::Object(obj)
4265        }]);
4266        ctx.insert("groups", grouped);
4267
4268        let executor = ExpressionExecutor::new();
4269        let expr = Expression {
4270            source: "groups".to_string(),
4271            transforms: vec![Transform::Aggregate {
4272                field: "amount".to_string(),
4273                op: AggregateOp::Sum,
4274            }],
4275        };
4276        let result = executor.execute(&expr, &ctx).unwrap();
4277        assert_eq!(result.as_number(), Some(30.0));
4278    }
4279
4280    #[test]
4281    fn test_aggregate_first_empty() {
4282        let mut ctx = DataContext::new();
4283        ctx.insert("items", Value::Array(vec![]));
4284
4285        let executor = ExpressionExecutor::new();
4286        let expr = Expression {
4287            source: "items".to_string(),
4288            transforms: vec![Transform::Aggregate {
4289                field: "x".to_string(),
4290                op: AggregateOp::First,
4291            }],
4292        };
4293        let result = executor.execute(&expr, &ctx).unwrap();
4294        assert_eq!(result.as_number(), Some(0.0));
4295    }
4296
4297    #[test]
4298    fn test_aggregate_last_empty() {
4299        let mut ctx = DataContext::new();
4300        ctx.insert("items", Value::Array(vec![]));
4301
4302        let executor = ExpressionExecutor::new();
4303        let expr = Expression {
4304            source: "items".to_string(),
4305            transforms: vec![Transform::Aggregate {
4306                field: "x".to_string(),
4307                op: AggregateOp::Last,
4308            }],
4309        };
4310        let result = executor.execute(&expr, &ctx).unwrap();
4311        assert_eq!(result.as_number(), Some(0.0));
4312    }
4313
4314    #[test]
4315    fn test_pivot_non_array() {
4316        let mut ctx = DataContext::new();
4317        ctx.insert("value", Value::Number(42.0));
4318
4319        let executor = ExpressionExecutor::new();
4320        let expr = Expression {
4321            source: "value".to_string(),
4322            transforms: vec![Transform::Pivot {
4323                row_field: "r".to_string(),
4324                col_field: "c".to_string(),
4325                value_field: "v".to_string(),
4326            }],
4327        };
4328        let result = executor.execute(&expr, &ctx);
4329        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4330    }
4331
4332    #[test]
4333    fn test_pivot_non_object_items() {
4334        let mut ctx = DataContext::new();
4335        ctx.insert("items", Value::Array(vec![Value::Number(1.0)]));
4336
4337        let executor = ExpressionExecutor::new();
4338        let expr = Expression {
4339            source: "items".to_string(),
4340            transforms: vec![Transform::Pivot {
4341                row_field: "r".to_string(),
4342                col_field: "c".to_string(),
4343                value_field: "v".to_string(),
4344            }],
4345        };
4346        let result = executor.execute(&expr, &ctx).unwrap();
4347        // Non-objects are skipped
4348        assert!(result.as_array().unwrap().is_empty());
4349    }
4350
4351    #[test]
4352    fn test_cumsum_non_array() {
4353        let mut ctx = DataContext::new();
4354        ctx.insert("value", Value::Number(42.0));
4355
4356        let executor = ExpressionExecutor::new();
4357        let expr = Expression {
4358            source: "value".to_string(),
4359            transforms: vec![Transform::CumulativeSum {
4360                field: "x".to_string(),
4361            }],
4362        };
4363        let result = executor.execute(&expr, &ctx);
4364        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4365    }
4366
4367    #[test]
4368    fn test_rank_non_array() {
4369        let mut ctx = DataContext::new();
4370        ctx.insert("value", Value::Number(42.0));
4371
4372        let executor = ExpressionExecutor::new();
4373        let expr = Expression {
4374            source: "value".to_string(),
4375            transforms: vec![Transform::Rank {
4376                field: "x".to_string(),
4377                method: RankMethod::Dense,
4378            }],
4379        };
4380        let result = executor.execute(&expr, &ctx);
4381        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4382    }
4383
4384    #[test]
4385    fn test_moving_average_non_array() {
4386        let mut ctx = DataContext::new();
4387        ctx.insert("value", Value::Number(42.0));
4388
4389        let executor = ExpressionExecutor::new();
4390        let expr = Expression {
4391            source: "value".to_string(),
4392            transforms: vec![Transform::MovingAverage {
4393                field: "x".to_string(),
4394                window: 3,
4395            }],
4396        };
4397        let result = executor.execute(&expr, &ctx);
4398        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4399    }
4400
4401    #[test]
4402    fn test_pct_change_non_array() {
4403        let mut ctx = DataContext::new();
4404        ctx.insert("value", Value::Number(42.0));
4405
4406        let executor = ExpressionExecutor::new();
4407        let expr = Expression {
4408            source: "value".to_string(),
4409            transforms: vec![Transform::PercentChange {
4410                field: "x".to_string(),
4411            }],
4412        };
4413        let result = executor.execute(&expr, &ctx);
4414        assert!(matches!(result, Err(ExecutionError::ExpectedArray)));
4415    }
4416
4417    #[test]
4418    fn test_compare_values_invalid_target() {
4419        let executor = ExpressionExecutor::new();
4420        // Invalid numeric comparison when target can't be parsed
4421        assert!(!executor.compare_values(&Value::Number(50.0), ">", "not_number"));
4422        assert!(!executor.compare_values(&Value::Number(50.0), "<", "not_number"));
4423        assert!(!executor.compare_values(&Value::Number(50.0), ">=", "not_number"));
4424        assert!(!executor.compare_values(&Value::Number(50.0), "<=", "not_number"));
4425    }
4426
4427    #[test]
4428    fn test_compare_values_contains_non_string() {
4429        let executor = ExpressionExecutor::new();
4430        assert!(!executor.compare_values(&Value::Number(123.0), "contains", "12"));
4431    }
4432
4433    #[test]
4434    fn test_join_left_item_not_object() {
4435        let mut ctx = DataContext::new();
4436        ctx.insert("left", Value::Array(vec![Value::Number(1.0)]));
4437        ctx.insert(
4438            "right",
4439            Value::Array(vec![{
4440                let mut obj = HashMap::new();
4441                obj.insert("id".to_string(), Value::Number(1.0));
4442                Value::Object(obj)
4443            }]),
4444        );
4445
4446        let executor = ExpressionExecutor::new();
4447        let expr = Expression {
4448            source: "left".to_string(),
4449            transforms: vec![Transform::Join {
4450                other: "right".to_string(),
4451                on: "id".to_string(),
4452            }],
4453        };
4454        let result = executor.execute(&expr, &ctx).unwrap();
4455        let arr = result.as_array().unwrap();
4456        // Non-object left items kept as-is
4457        assert_eq!(arr.len(), 1);
4458        assert_eq!(arr[0].as_number(), Some(1.0));
4459    }
4460
4461    #[test]
4462    fn test_join_left_item_missing_key() {
4463        let mut ctx = DataContext::new();
4464        ctx.insert(
4465            "left",
4466            Value::Array(vec![{
4467                let mut obj = HashMap::new();
4468                obj.insert("other".to_string(), Value::Number(1.0));
4469                Value::Object(obj)
4470            }]),
4471        );
4472        ctx.insert(
4473            "right",
4474            Value::Array(vec![{
4475                let mut obj = HashMap::new();
4476                obj.insert("id".to_string(), Value::Number(1.0));
4477                Value::Object(obj)
4478            }]),
4479        );
4480
4481        let executor = ExpressionExecutor::new();
4482        let expr = Expression {
4483            source: "left".to_string(),
4484            transforms: vec![Transform::Join {
4485                other: "right".to_string(),
4486                on: "id".to_string(),
4487            }],
4488        };
4489        let result = executor.execute(&expr, &ctx).unwrap();
4490        let arr = result.as_array().unwrap();
4491        // Left items without join key kept as-is
4492        assert_eq!(arr.len(), 1);
4493    }
4494
4495    #[test]
4496    fn test_join_right_item_not_object() {
4497        let mut ctx = DataContext::new();
4498        ctx.insert(
4499            "left",
4500            Value::Array(vec![{
4501                let mut obj = HashMap::new();
4502                obj.insert("id".to_string(), Value::Number(1.0));
4503                Value::Object(obj)
4504            }]),
4505        );
4506        ctx.insert("right", Value::Array(vec![Value::Number(1.0)]));
4507
4508        let executor = ExpressionExecutor::new();
4509        let expr = Expression {
4510            source: "left".to_string(),
4511            transforms: vec![Transform::Join {
4512                other: "right".to_string(),
4513                on: "id".to_string(),
4514            }],
4515        };
4516        let result = executor.execute(&expr, &ctx).unwrap();
4517        let arr = result.as_array().unwrap();
4518        // No match found for non-object right items
4519        assert_eq!(arr.len(), 1);
4520    }
4521}