Skip to main content

presentar_yaml/
executor.rs

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