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