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<Value>),
20    /// Object (key-value map)
21    Object(HashMap<String, Value>),
22}
23
24impl Value {
25    /// Create a new null value.
26    #[must_use]
27    pub const fn null() -> Self {
28        Self::Null
29    }
30
31    /// Create a new boolean value.
32    #[must_use]
33    pub const fn bool(v: bool) -> Self {
34        Self::Bool(v)
35    }
36
37    /// Create a new number value.
38    #[must_use]
39    pub const fn number(v: f64) -> Self {
40        Self::Number(v)
41    }
42
43    /// Create a new string value.
44    #[must_use]
45    pub fn string(v: impl Into<String>) -> Self {
46        Self::String(v.into())
47    }
48
49    /// Create a new array value.
50    #[must_use]
51    pub const fn array(v: Vec<Self>) -> Self {
52        Self::Array(v)
53    }
54
55    /// Create a new object value.
56    #[must_use]
57    pub const fn object(v: HashMap<String, Self>) -> Self {
58        Self::Object(v)
59    }
60
61    /// Check if value is null.
62    #[must_use]
63    pub const fn is_null(&self) -> bool {
64        matches!(self, Self::Null)
65    }
66
67    /// Check if value is a boolean.
68    #[must_use]
69    pub const fn is_bool(&self) -> bool {
70        matches!(self, Self::Bool(_))
71    }
72
73    /// Check if value is a number.
74    #[must_use]
75    pub const fn is_number(&self) -> bool {
76        matches!(self, Self::Number(_))
77    }
78
79    /// Check if value is a string.
80    #[must_use]
81    pub const fn is_string(&self) -> bool {
82        matches!(self, Self::String(_))
83    }
84
85    /// Check if value is an array.
86    #[must_use]
87    pub const fn is_array(&self) -> bool {
88        matches!(self, Self::Array(_))
89    }
90
91    /// Check if value is an object.
92    #[must_use]
93    pub const fn is_object(&self) -> bool {
94        matches!(self, Self::Object(_))
95    }
96
97    /// Get as boolean.
98    #[must_use]
99    pub const fn as_bool(&self) -> Option<bool> {
100        match self {
101            Self::Bool(v) => Some(*v),
102            _ => None,
103        }
104    }
105
106    /// Get as number.
107    #[must_use]
108    pub const fn as_number(&self) -> Option<f64> {
109        match self {
110            Self::Number(v) => Some(*v),
111            _ => None,
112        }
113    }
114
115    /// Get as string.
116    #[must_use]
117    pub fn as_str(&self) -> Option<&str> {
118        match self {
119            Self::String(v) => Some(v),
120            _ => None,
121        }
122    }
123
124    /// Get as array.
125    #[must_use]
126    pub const fn as_array(&self) -> Option<&Vec<Self>> {
127        match self {
128            Self::Array(v) => Some(v),
129            _ => None,
130        }
131    }
132
133    /// Get as mutable array.
134    #[must_use]
135    pub fn as_array_mut(&mut self) -> Option<&mut Vec<Self>> {
136        match self {
137            Self::Array(v) => Some(v),
138            _ => None,
139        }
140    }
141
142    /// Get as object.
143    #[must_use]
144    pub const fn as_object(&self) -> Option<&HashMap<String, Self>> {
145        match self {
146            Self::Object(v) => Some(v),
147            _ => None,
148        }
149    }
150
151    /// Get field from object.
152    #[must_use]
153    pub fn get(&self, key: &str) -> Option<&Self> {
154        match self {
155            Self::Object(map) => map.get(key),
156            _ => None,
157        }
158    }
159
160    /// Get array length or object key count.
161    #[must_use]
162    pub fn len(&self) -> usize {
163        match self {
164            Self::Array(arr) => arr.len(),
165            Self::Object(obj) => obj.len(),
166            Self::String(s) => s.len(),
167            _ => 0,
168        }
169    }
170
171    /// Check if array or object is empty.
172    #[must_use]
173    pub fn is_empty(&self) -> bool {
174        self.len() == 0
175    }
176}
177
178impl From<bool> for Value {
179    fn from(v: bool) -> Self {
180        Self::Bool(v)
181    }
182}
183
184impl From<f64> for Value {
185    fn from(v: f64) -> Self {
186        Self::Number(v)
187    }
188}
189
190impl From<i32> for Value {
191    fn from(v: i32) -> Self {
192        Self::Number(f64::from(v))
193    }
194}
195
196impl From<i64> for Value {
197    fn from(v: i64) -> Self {
198        Self::Number(v as f64)
199    }
200}
201
202impl From<&str> for Value {
203    fn from(v: &str) -> Self {
204        Self::String(v.to_string())
205    }
206}
207
208impl From<String> for Value {
209    fn from(v: String) -> Self {
210        Self::String(v)
211    }
212}
213
214impl<T: Into<Self>> From<Vec<T>> for Value {
215    fn from(v: Vec<T>) -> Self {
216        Self::Array(v.into_iter().map(Into::into).collect())
217    }
218}
219
220/// Execution error.
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub enum ExecutionError {
223    /// Source not found in data context
224    SourceNotFound(String),
225    /// Expected an array for this transform
226    ExpectedArray,
227    /// Expected an object
228    ExpectedObject,
229    /// Field not found
230    FieldNotFound(String),
231    /// Type mismatch
232    TypeMismatch(String),
233    /// Invalid transform
234    InvalidTransform(String),
235}
236
237impl std::fmt::Display for ExecutionError {
238    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239        match self {
240            Self::SourceNotFound(name) => write!(f, "source not found: {name}"),
241            Self::ExpectedArray => write!(f, "expected an array"),
242            Self::ExpectedObject => write!(f, "expected an object"),
243            Self::FieldNotFound(name) => write!(f, "field not found: {name}"),
244            Self::TypeMismatch(msg) => write!(f, "type mismatch: {msg}"),
245            Self::InvalidTransform(msg) => write!(f, "invalid transform: {msg}"),
246        }
247    }
248}
249
250impl std::error::Error for ExecutionError {}
251
252/// Data context for expression execution.
253#[derive(Debug, Clone, Default)]
254pub struct DataContext {
255    /// Named data sources
256    sources: HashMap<String, Value>,
257}
258
259impl DataContext {
260    /// Create a new empty context.
261    #[must_use]
262    pub fn new() -> Self {
263        Self::default()
264    }
265
266    /// Add a data source.
267    pub fn insert(&mut self, name: impl Into<String>, value: Value) {
268        self.sources.insert(name.into(), value);
269    }
270
271    /// Get a data source.
272    #[must_use]
273    pub fn get(&self, name: &str) -> Option<&Value> {
274        // Support dotted paths like "data.transactions"
275        let parts: Vec<&str> = name.split('.').collect();
276        let mut current = self.sources.get(parts[0])?;
277
278        for part in &parts[1..] {
279            current = current.get(part)?;
280        }
281
282        Some(current)
283    }
284
285    /// Check if context has a source.
286    #[must_use]
287    pub fn contains(&self, name: &str) -> bool {
288        self.get(name).is_some()
289    }
290}
291
292/// Expression executor.
293#[derive(Debug, Default)]
294pub struct ExpressionExecutor;
295
296impl ExpressionExecutor {
297    /// Create a new executor.
298    #[must_use]
299    pub const fn new() -> Self {
300        Self
301    }
302
303    /// Execute an expression against a data context.
304    ///
305    /// # Errors
306    ///
307    /// Returns an error if execution fails.
308    pub fn execute(&self, expr: &Expression, ctx: &DataContext) -> Result<Value, ExecutionError> {
309        // Resolve source
310        let mut value = ctx
311            .get(&expr.source)
312            .cloned()
313            .ok_or_else(|| ExecutionError::SourceNotFound(expr.source.clone()))?;
314
315        // Apply transforms
316        for transform in &expr.transforms {
317            value = self.apply_transform(&value, transform, ctx)?;
318        }
319
320        Ok(value)
321    }
322
323    fn apply_transform(
324        &self,
325        value: &Value,
326        transform: &Transform,
327        ctx: &DataContext,
328    ) -> Result<Value, ExecutionError> {
329        match transform {
330            Transform::Filter {
331                field,
332                value: match_value,
333            } => self.apply_filter(value, field, match_value),
334            Transform::Select { fields } => self.apply_select(value, fields),
335            Transform::Sort { field, desc } => self.apply_sort(value, field, *desc),
336            Transform::Count => Ok(self.apply_count(value)),
337            Transform::Sum { field } => self.apply_sum(value, field),
338            Transform::Mean { field } => self.apply_mean(value, field),
339            Transform::Sample { n } => self.apply_sample(value, *n),
340            Transform::Percentage => self.apply_percentage(value),
341            Transform::Rate { window } => self.apply_rate(value, window),
342            Transform::Join { other, on } => self.apply_join(value, other, on, ctx),
343            Transform::GroupBy { field } => self.apply_group_by(value, field),
344            Transform::Distinct { field } => self.apply_distinct(value, field.as_deref()),
345            Transform::Where {
346                field,
347                op,
348                value: match_value,
349            } => self.apply_where(value, field, op, match_value),
350            Transform::Offset { n } => self.apply_offset(value, *n),
351            Transform::Min { field } => self.apply_min(value, field),
352            Transform::Max { field } => self.apply_max(value, field),
353            Transform::First { n } | Transform::Limit { n } => self.apply_limit(value, *n),
354            Transform::Last { n } => self.apply_last(value, *n),
355            Transform::Flatten => self.apply_flatten(value),
356            Transform::Reverse => self.apply_reverse(value),
357            // New transforms
358            Transform::Map { expr } => self.apply_map(value, expr),
359            Transform::Reduce { initial, expr } => self.apply_reduce(value, initial, expr),
360            Transform::Aggregate { field, op } => self.apply_aggregate(value, field, *op),
361            Transform::Pivot {
362                row_field,
363                col_field,
364                value_field,
365            } => self.apply_pivot(value, row_field, col_field, value_field),
366            Transform::CumulativeSum { field } => self.apply_cumsum(value, field),
367            Transform::Rank { field, method } => self.apply_rank(value, field, *method),
368            Transform::MovingAverage { field, window } => {
369                self.apply_moving_avg(value, field, *window)
370            }
371            Transform::PercentChange { field } => self.apply_pct_change(value, field),
372            Transform::Suggest { prefix, count } => self.apply_suggest(value, prefix, *count),
373        }
374    }
375
376    fn apply_filter(
377        &self,
378        value: &Value,
379        field: &str,
380        match_value: &str,
381    ) -> Result<Value, ExecutionError> {
382        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
383
384        let filtered: Vec<Value> = arr
385            .iter()
386            .filter(|item| {
387                if let Some(obj) = item.as_object() {
388                    if let Some(val) = obj.get(field) {
389                        return self.value_matches(val, match_value);
390                    }
391                }
392                false
393            })
394            .cloned()
395            .collect();
396
397        Ok(Value::Array(filtered))
398    }
399
400    fn value_matches(&self, value: &Value, target: &str) -> bool {
401        match value {
402            Value::String(s) => s == target,
403            Value::Number(n) => {
404                if let Ok(t) = target.parse::<f64>() {
405                    (*n - t).abs() < f64::EPSILON
406                } else {
407                    false
408                }
409            }
410            Value::Bool(b) => {
411                matches!((b, target), (true, "true") | (false, "false"))
412            }
413            _ => false,
414        }
415    }
416
417    fn apply_select(&self, value: &Value, fields: &[String]) -> Result<Value, ExecutionError> {
418        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
419
420        let selected: Vec<Value> = arr
421            .iter()
422            .map(|item| {
423                if let Some(obj) = item.as_object() {
424                    let mut new_obj = HashMap::new();
425                    for field in fields {
426                        if let Some(val) = obj.get(field) {
427                            new_obj.insert(field.clone(), val.clone());
428                        }
429                    }
430                    Value::Object(new_obj)
431                } else {
432                    item.clone()
433                }
434            })
435            .collect();
436
437        Ok(Value::Array(selected))
438    }
439
440    fn apply_sort(&self, value: &Value, field: &str, desc: bool) -> Result<Value, ExecutionError> {
441        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
442        let mut sorted = arr.clone();
443
444        sorted.sort_by(|a, b| {
445            let a_val = a.get(field);
446            let b_val = b.get(field);
447
448            let cmp = match (a_val, b_val) {
449                (Some(Value::Number(a)), Some(Value::Number(b))) => {
450                    a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
451                }
452                (Some(Value::String(a)), Some(Value::String(b))) => a.cmp(b),
453                _ => std::cmp::Ordering::Equal,
454            };
455
456            if desc {
457                cmp.reverse()
458            } else {
459                cmp
460            }
461        });
462
463        Ok(Value::Array(sorted))
464    }
465
466    fn apply_limit(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
467        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
468        Ok(Value::Array(arr.iter().take(n).cloned().collect()))
469    }
470
471    fn apply_count(&self, value: &Value) -> Value {
472        match value {
473            Value::Array(arr) => Value::Number(arr.len() as f64),
474            Value::Object(obj) => Value::Number(obj.len() as f64),
475            Value::String(s) => Value::Number(s.len() as f64),
476            _ => Value::Number(0.0),
477        }
478    }
479
480    fn apply_sum(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
481        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
482
483        let sum: f64 = arr
484            .iter()
485            .filter_map(|item| item.get(field)?.as_number())
486            .sum();
487
488        Ok(Value::Number(sum))
489    }
490
491    fn apply_mean(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
492        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
493
494        let values: Vec<f64> = arr
495            .iter()
496            .filter_map(|item| item.get(field)?.as_number())
497            .collect();
498
499        if values.is_empty() {
500            return Ok(Value::Number(0.0));
501        }
502
503        let sum: f64 = values.iter().sum();
504        let mean = sum / values.len() as f64;
505
506        Ok(Value::Number(mean))
507    }
508
509    fn apply_sample(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
510        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
511
512        // Simple deterministic "sampling" - just take first n elements
513        // Real implementation would use random sampling
514        Ok(Value::Array(arr.iter().take(n).cloned().collect()))
515    }
516
517    fn apply_percentage(&self, value: &Value) -> Result<Value, ExecutionError> {
518        match value {
519            Value::Number(n) => Ok(Value::Number(n * 100.0)),
520            _ => Err(ExecutionError::TypeMismatch(
521                "percentage requires a number".to_string(),
522            )),
523        }
524    }
525
526    fn apply_rate(&self, value: &Value, window: &str) -> Result<Value, ExecutionError> {
527        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
528
529        // Parse window (e.g., "1m", "5m", "1h")
530        let window_ms = self.parse_window(window)?;
531
532        // For rate calculation, we need timestamped data
533        // Look for a "timestamp" or "time" field
534        let mut values_with_time: Vec<(f64, f64)> = arr
535            .iter()
536            .filter_map(|item| {
537                let obj = item.as_object()?;
538                let time = obj
539                    .get("timestamp")
540                    .or_else(|| obj.get("time"))
541                    .and_then(Value::as_number)?;
542                let val = obj
543                    .get("value")
544                    .or_else(|| obj.get("count"))
545                    .and_then(Value::as_number)
546                    .unwrap_or(1.0);
547                Some((time, val))
548            })
549            .collect();
550
551        if values_with_time.len() < 2 {
552            return Ok(Value::Number(0.0));
553        }
554
555        // Sort by time
556        values_with_time.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
557
558        // Calculate rate over the window
559        let window_ms_f64 = window_ms as f64;
560        let last_time = values_with_time.last().map_or(0.0, |v| v.0);
561        let window_start = last_time - window_ms_f64;
562
563        let sum_in_window: f64 = values_with_time
564            .iter()
565            .filter(|(t, _)| *t >= window_start)
566            .map(|(_, v)| v)
567            .sum();
568
569        // Rate per second
570        let rate = sum_in_window / (window_ms_f64 / 1000.0);
571
572        Ok(Value::Number(rate))
573    }
574
575    fn parse_window(&self, window: &str) -> Result<u64, ExecutionError> {
576        let window = window.trim();
577        if window.is_empty() {
578            return Err(ExecutionError::InvalidTransform("empty window".to_string()));
579        }
580
581        let (num_str, unit) = if let Some(s) = window.strip_suffix("ms") {
582            (s, "ms")
583        } else if let Some(s) = window.strip_suffix('s') {
584            (s, "s")
585        } else if let Some(s) = window.strip_suffix('m') {
586            (s, "m")
587        } else if let Some(s) = window.strip_suffix('h') {
588            (s, "h")
589        } else if let Some(s) = window.strip_suffix('d') {
590            (s, "d")
591        } else {
592            // Assume milliseconds if no unit
593            (window, "ms")
594        };
595
596        let num: u64 = num_str
597            .parse()
598            .map_err(|_| ExecutionError::InvalidTransform(format!("invalid window: {window}")))?;
599
600        let ms = match unit {
601            "s" => num * 1000,
602            "m" => num * 60 * 1000,
603            "h" => num * 60 * 60 * 1000,
604            "d" => num * 24 * 60 * 60 * 1000,
605            // "ms" and any other unit default to num (milliseconds)
606            _ => num,
607        };
608
609        Ok(ms)
610    }
611
612    fn apply_join(
613        &self,
614        value: &Value,
615        other: &str,
616        on: &str,
617        ctx: &DataContext,
618    ) -> Result<Value, ExecutionError> {
619        let left_arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
620
621        // Get the other dataset from context
622        let right_value = ctx
623            .get(other)
624            .ok_or_else(|| ExecutionError::SourceNotFound(other.to_string()))?;
625        let right_arr = right_value
626            .as_array()
627            .ok_or(ExecutionError::ExpectedArray)?;
628
629        // Build a lookup map for the right side (keyed by the join field)
630        let mut right_lookup: HashMap<String, Vec<&Value>> = HashMap::new();
631        for item in right_arr {
632            if let Some(obj) = item.as_object() {
633                if let Some(key_val) = obj.get(on) {
634                    let key = self.value_to_string(key_val);
635                    right_lookup.entry(key).or_default().push(item);
636                }
637            }
638        }
639
640        // Perform the join (left join - keeps all left items)
641        let mut result = Vec::new();
642        for left_item in left_arr {
643            if let Some(left_obj) = left_item.as_object() {
644                if let Some(key_val) = left_obj.get(on) {
645                    let key = self.value_to_string(key_val);
646                    if let Some(right_items) = right_lookup.get(&key) {
647                        // Join with each matching right item
648                        for right_item in right_items {
649                            if let Some(right_obj) = right_item.as_object() {
650                                // Merge left and right objects
651                                let mut merged = left_obj.clone();
652                                for (k, v) in right_obj {
653                                    // Don't overwrite left values, prefix with source name
654                                    if merged.contains_key(k) && k != on {
655                                        merged.insert(format!("{other}_{k}"), v.clone());
656                                    } else if k != on {
657                                        merged.insert(k.clone(), v.clone());
658                                    }
659                                }
660                                result.push(Value::Object(merged));
661                            }
662                        }
663                    } else {
664                        // No match, keep left item as-is (left join behavior)
665                        result.push(left_item.clone());
666                    }
667                } else {
668                    // No join key, keep as-is
669                    result.push(left_item.clone());
670                }
671            } else {
672                // Not an object, keep as-is
673                result.push(left_item.clone());
674            }
675        }
676
677        Ok(Value::Array(result))
678    }
679
680    fn apply_group_by(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
681        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
682
683        let mut groups: HashMap<String, Vec<Value>> = HashMap::new();
684
685        for item in arr {
686            let key = if let Some(obj) = item.as_object() {
687                if let Some(val) = obj.get(field) {
688                    self.value_to_string(val)
689                } else {
690                    "_null".to_string()
691                }
692            } else {
693                "_null".to_string()
694            };
695
696            groups.entry(key).or_default().push(item.clone());
697        }
698
699        // Convert to array of objects with key and items
700        let result: Vec<Value> = groups
701            .into_iter()
702            .map(|(key, items)| {
703                let mut obj = HashMap::new();
704                obj.insert("key".to_string(), Value::String(key));
705                obj.insert("items".to_string(), Value::Array(items.clone()));
706                obj.insert("count".to_string(), Value::Number(items.len() as f64));
707                Value::Object(obj)
708            })
709            .collect();
710
711        Ok(Value::Array(result))
712    }
713
714    fn value_to_string(&self, value: &Value) -> String {
715        match value {
716            Value::Null => "_null".to_string(),
717            Value::Bool(b) => b.to_string(),
718            Value::Number(n) => n.to_string(),
719            Value::String(s) => s.clone(),
720            Value::Array(_) => "_array".to_string(),
721            Value::Object(_) => "_object".to_string(),
722        }
723    }
724
725    fn apply_distinct(&self, value: &Value, field: Option<&str>) -> Result<Value, ExecutionError> {
726        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
727
728        let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
729        let mut result = Vec::new();
730
731        for item in arr {
732            let key = if let Some(f) = field {
733                if let Some(obj) = item.as_object() {
734                    obj.get(f)
735                        .map(|v| self.value_to_string(v))
736                        .unwrap_or_default()
737                } else {
738                    self.value_to_string(item)
739                }
740            } else {
741                self.value_to_string(item)
742            };
743
744            if seen.insert(key) {
745                result.push(item.clone());
746            }
747        }
748
749        Ok(Value::Array(result))
750    }
751
752    fn apply_where(
753        &self,
754        value: &Value,
755        field: &str,
756        op: &str,
757        match_value: &str,
758    ) -> Result<Value, ExecutionError> {
759        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
760
761        let filtered: Vec<Value> = arr
762            .iter()
763            .filter(|item| {
764                if let Some(obj) = item.as_object() {
765                    if let Some(val) = obj.get(field) {
766                        return self.compare_values(val, op, match_value);
767                    }
768                }
769                false
770            })
771            .cloned()
772            .collect();
773
774        Ok(Value::Array(filtered))
775    }
776
777    fn compare_values(&self, value: &Value, op: &str, target: &str) -> bool {
778        match op {
779            "eq" | "==" | "=" => self.value_matches(value, target),
780            "ne" | "!=" | "<>" => !self.value_matches(value, target),
781            "gt" | ">" => {
782                if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
783                    v > t
784                } else {
785                    false
786                }
787            }
788            "lt" | "<" => {
789                if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
790                    v < t
791                } else {
792                    false
793                }
794            }
795            "gte" | ">=" => {
796                if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
797                    v >= t
798                } else {
799                    false
800                }
801            }
802            "lte" | "<=" => {
803                if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
804                    v <= t
805                } else {
806                    false
807                }
808            }
809            "contains" => {
810                if let Some(s) = value.as_str() {
811                    s.contains(target)
812                } else {
813                    false
814                }
815            }
816            "starts_with" => {
817                if let Some(s) = value.as_str() {
818                    s.starts_with(target)
819                } else {
820                    false
821                }
822            }
823            "ends_with" => {
824                if let Some(s) = value.as_str() {
825                    s.ends_with(target)
826                } else {
827                    false
828                }
829            }
830            _ => false,
831        }
832    }
833
834    fn apply_offset(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
835        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
836        Ok(Value::Array(arr.iter().skip(n).cloned().collect()))
837    }
838
839    fn apply_min(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
840        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
841
842        let min = arr
843            .iter()
844            .filter_map(|item| item.get(field)?.as_number())
845            .fold(f64::INFINITY, f64::min);
846
847        if min.is_infinite() {
848            Ok(Value::Null)
849        } else {
850            Ok(Value::Number(min))
851        }
852    }
853
854    fn apply_max(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
855        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
856
857        let max = arr
858            .iter()
859            .filter_map(|item| item.get(field)?.as_number())
860            .fold(f64::NEG_INFINITY, f64::max);
861
862        if max.is_infinite() {
863            Ok(Value::Null)
864        } else {
865            Ok(Value::Number(max))
866        }
867    }
868
869    fn apply_last(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
870        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
871        let len = arr.len();
872        let skip = len.saturating_sub(n);
873        Ok(Value::Array(arr.iter().skip(skip).cloned().collect()))
874    }
875
876    fn apply_flatten(&self, value: &Value) -> Result<Value, ExecutionError> {
877        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
878
879        let mut result = Vec::new();
880        for item in arr {
881            if let Some(inner) = item.as_array() {
882                result.extend(inner.iter().cloned());
883            } else {
884                result.push(item.clone());
885            }
886        }
887
888        Ok(Value::Array(result))
889    }
890
891    fn apply_reverse(&self, value: &Value) -> Result<Value, ExecutionError> {
892        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
893        let mut reversed = arr.clone();
894        reversed.reverse();
895        Ok(Value::Array(reversed))
896    }
897
898    // =========================================================================
899    // New Transform Implementations
900    // =========================================================================
901
902    fn apply_map(&self, value: &Value, expr: &str) -> Result<Value, ExecutionError> {
903        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
904
905        // Simple expression evaluation: extract field if expr is "item.field"
906        // For complex expressions, this would need a proper expression evaluator
907        let mapped: Vec<Value> = arr
908            .iter()
909            .map(|item| {
910                // Handle simple field access like "item.field"
911                if let Some(field) = expr.strip_prefix("item.") {
912                    if let Some(obj) = item.as_object() {
913                        obj.get(field).cloned().unwrap_or(Value::Null)
914                    } else {
915                        item.clone()
916                    }
917                } else {
918                    // Return item unchanged if we can't parse the expression
919                    item.clone()
920                }
921            })
922            .collect();
923
924        Ok(Value::Array(mapped))
925    }
926
927    fn apply_reduce(
928        &self,
929        value: &Value,
930        initial: &str,
931        _expr: &str,
932    ) -> Result<Value, ExecutionError> {
933        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
934
935        // Parse initial value
936        let mut acc: f64 = initial.parse().unwrap_or(0.0);
937
938        // Simple sum reduction (a proper implementation would evaluate the expr)
939        for item in arr {
940            if let Some(n) = item.as_number() {
941                acc += n;
942            }
943        }
944
945        Ok(Value::Number(acc))
946    }
947
948    fn apply_aggregate(
949        &self,
950        value: &Value,
951        field: &str,
952        op: AggregateOp,
953    ) -> Result<Value, ExecutionError> {
954        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
955
956        // For grouped data, expect array of {key: ..., values: [...]}
957        // For ungrouped data, operate on the field directly
958
959        let values: Vec<f64> = arr
960            .iter()
961            .filter_map(|item| {
962                if let Some(obj) = item.as_object() {
963                    // If this is a group with "values" key
964                    if let Some(Value::Array(group_values)) = obj.get("values") {
965                        return Some(
966                            group_values
967                                .iter()
968                                .filter_map(|v| v.get(field)?.as_number())
969                                .collect::<Vec<_>>(),
970                        );
971                    }
972                    // Direct field access
973                    obj.get(field)?.as_number().map(|n| vec![n])
974                } else {
975                    None
976                }
977            })
978            .flatten()
979            .collect();
980
981        let result = match op {
982            AggregateOp::Sum => values.iter().sum(),
983            AggregateOp::Count => values.len() as f64,
984            AggregateOp::Mean => {
985                if values.is_empty() {
986                    0.0
987                } else {
988                    values.iter().sum::<f64>() / values.len() as f64
989                }
990            }
991            AggregateOp::Min => values.iter().cloned().fold(f64::INFINITY, f64::min),
992            AggregateOp::Max => values.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
993            AggregateOp::First => values.first().copied().unwrap_or(0.0),
994            AggregateOp::Last => values.last().copied().unwrap_or(0.0),
995        };
996
997        Ok(Value::Number(result))
998    }
999
1000    fn apply_pivot(
1001        &self,
1002        value: &Value,
1003        row_field: &str,
1004        col_field: &str,
1005        value_field: &str,
1006    ) -> Result<Value, ExecutionError> {
1007        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1008
1009        // Build pivot table
1010        let mut rows: HashMap<String, HashMap<String, f64>> = HashMap::new();
1011
1012        for item in arr {
1013            if let Some(obj) = item.as_object() {
1014                let row_key = obj
1015                    .get(row_field)
1016                    .map(|v| self.value_to_string(v))
1017                    .unwrap_or_default();
1018                let col_key = obj
1019                    .get(col_field)
1020                    .map(|v| self.value_to_string(v))
1021                    .unwrap_or_default();
1022                let val = obj
1023                    .get(value_field)
1024                    .and_then(|v| v.as_number())
1025                    .unwrap_or(0.0);
1026
1027                rows.entry(row_key)
1028                    .or_default()
1029                    .entry(col_key)
1030                    .and_modify(|v| *v += val)
1031                    .or_insert(val);
1032            }
1033        }
1034
1035        // Convert to array of objects
1036        let result: Vec<Value> = rows
1037            .into_iter()
1038            .map(|(row_key, cols)| {
1039                let mut obj = HashMap::new();
1040                obj.insert(row_field.to_string(), Value::String(row_key));
1041                for (col_key, val) in cols {
1042                    obj.insert(col_key, Value::Number(val));
1043                }
1044                Value::Object(obj)
1045            })
1046            .collect();
1047
1048        Ok(Value::Array(result))
1049    }
1050
1051    fn apply_cumsum(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
1052        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1053
1054        let mut running_sum = 0.0;
1055        let result: Vec<Value> = arr
1056            .iter()
1057            .map(|item| {
1058                if let Some(obj) = item.as_object() {
1059                    let val = obj.get(field).and_then(|v| v.as_number()).unwrap_or(0.0);
1060                    running_sum += val;
1061
1062                    let mut new_obj = obj.clone();
1063                    new_obj.insert(format!("{field}_cumsum"), Value::Number(running_sum));
1064                    Value::Object(new_obj)
1065                } else {
1066                    item.clone()
1067                }
1068            })
1069            .collect();
1070
1071        Ok(Value::Array(result))
1072    }
1073
1074    fn apply_rank(
1075        &self,
1076        value: &Value,
1077        field: &str,
1078        method: RankMethod,
1079    ) -> Result<Value, ExecutionError> {
1080        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1081
1082        // Extract values with indices
1083        let mut indexed: Vec<(usize, f64)> = arr
1084            .iter()
1085            .enumerate()
1086            .filter_map(|(i, item)| item.as_object()?.get(field)?.as_number().map(|n| (i, n)))
1087            .collect();
1088
1089        // Sort by value (descending for ranking)
1090        indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1091
1092        // Assign ranks based on method
1093        let mut ranks = vec![0.0; arr.len()];
1094        match method {
1095            RankMethod::Dense => {
1096                let mut rank = 0;
1097                let mut prev_val: Option<f64> = None;
1098                for (i, val) in indexed {
1099                    if prev_val != Some(val) {
1100                        rank += 1;
1101                    }
1102                    ranks[i] = rank as f64;
1103                    prev_val = Some(val);
1104                }
1105            }
1106            RankMethod::Ordinal => {
1107                for (rank, (i, _)) in indexed.iter().enumerate() {
1108                    ranks[*i] = (rank + 1) as f64;
1109                }
1110            }
1111            RankMethod::Average => {
1112                let mut i = 0;
1113                while i < indexed.len() {
1114                    let val = indexed[i].1;
1115                    let start = i;
1116                    while i < indexed.len() && (indexed[i].1 - val).abs() < f64::EPSILON {
1117                        i += 1;
1118                    }
1119                    let avg_rank =
1120                        (start + 1..=i).map(|r| r as f64).sum::<f64>() / (i - start) as f64;
1121                    for j in start..i {
1122                        ranks[indexed[j].0] = avg_rank;
1123                    }
1124                }
1125            }
1126        }
1127
1128        // Add rank to each object
1129        let result: Vec<Value> = arr
1130            .iter()
1131            .enumerate()
1132            .map(|(i, item)| {
1133                if let Some(obj) = item.as_object() {
1134                    let mut new_obj = obj.clone();
1135                    new_obj.insert(format!("{field}_rank"), Value::Number(ranks[i]));
1136                    Value::Object(new_obj)
1137                } else {
1138                    item.clone()
1139                }
1140            })
1141            .collect();
1142
1143        Ok(Value::Array(result))
1144    }
1145
1146    fn apply_moving_avg(
1147        &self,
1148        value: &Value,
1149        field: &str,
1150        window: usize,
1151    ) -> Result<Value, ExecutionError> {
1152        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1153
1154        let values: Vec<f64> = arr
1155            .iter()
1156            .filter_map(|item| item.as_object()?.get(field)?.as_number())
1157            .collect();
1158
1159        let result: Vec<Value> = arr
1160            .iter()
1161            .enumerate()
1162            .map(|(i, item)| {
1163                if let Some(obj) = item.as_object() {
1164                    let start = i.saturating_sub(window - 1);
1165                    let window_values = &values[start..=i.min(values.len() - 1)];
1166                    let ma = if window_values.is_empty() {
1167                        0.0
1168                    } else {
1169                        window_values.iter().sum::<f64>() / window_values.len() as f64
1170                    };
1171
1172                    let mut new_obj = obj.clone();
1173                    new_obj.insert(format!("{field}_ma{window}"), Value::Number(ma));
1174                    Value::Object(new_obj)
1175                } else {
1176                    item.clone()
1177                }
1178            })
1179            .collect();
1180
1181        Ok(Value::Array(result))
1182    }
1183
1184    fn apply_pct_change(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
1185        let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1186
1187        let values: Vec<f64> = arr
1188            .iter()
1189            .filter_map(|item| item.as_object()?.get(field)?.as_number())
1190            .collect();
1191
1192        let result: Vec<Value> = arr
1193            .iter()
1194            .enumerate()
1195            .map(|(i, item)| {
1196                if let Some(obj) = item.as_object() {
1197                    let pct = if i == 0 || values.get(i - 1).map_or(true, |&prev| prev == 0.0) {
1198                        0.0
1199                    } else {
1200                        let prev = values[i - 1];
1201                        let curr = values.get(i).copied().unwrap_or(prev);
1202                        (curr - prev) / prev * 100.0
1203                    };
1204
1205                    let mut new_obj = obj.clone();
1206                    new_obj.insert(format!("{field}_pct_change"), Value::Number(pct));
1207                    Value::Object(new_obj)
1208                } else {
1209                    item.clone()
1210                }
1211            })
1212            .collect();
1213
1214        Ok(Value::Array(result))
1215    }
1216
1217    /// Apply suggestion transform for N-gram/autocomplete models.
1218    ///
1219    /// The input `value` should be a model object with a `model_type` field.
1220    /// This is a stub implementation - actual model inference is handled by
1221    /// the runtime layer which injects results into the context.
1222    ///
1223    /// In production, the runtime loads the .apr model and provides suggestions
1224    /// through a callback or pre-computed context value.
1225    #[allow(clippy::unnecessary_wraps)] // Returns Result for API consistency with other transforms
1226    fn apply_suggest(
1227        &self,
1228        value: &Value,
1229        prefix: &str,
1230        count: usize,
1231    ) -> Result<Value, ExecutionError> {
1232        // Check if value is a model object with pre-computed suggestions
1233        if let Some(obj) = value.as_object() {
1234            // If the model has pre-computed suggestions for this prefix, use them
1235            if let Some(suggestions) = obj.get("_suggestions") {
1236                if let Some(arr) = suggestions.as_array() {
1237                    return Ok(Value::Array(arr.iter().take(count).cloned().collect()));
1238                }
1239            }
1240
1241            // If this is a model reference, return placeholder suggestions
1242            // The runtime layer should populate _suggestions before execution
1243            if obj.contains_key("model_type") || obj.contains_key("source") {
1244                // Return empty array - runtime should inject actual suggestions
1245                return Ok(Value::Array(vec![]));
1246            }
1247        }
1248
1249        // For testing/demo: if value is an array of suggestion objects, filter by prefix
1250        if let Some(arr) = value.as_array() {
1251            let filtered: Vec<Value> = arr
1252                .iter()
1253                .filter(|item| {
1254                    if let Some(obj) = item.as_object() {
1255                        if let Some(text) = obj.get("text").and_then(|v| v.as_str()) {
1256                            return text.starts_with(prefix);
1257                        }
1258                    }
1259                    false
1260                })
1261                .take(count)
1262                .cloned()
1263                .collect();
1264            return Ok(Value::Array(filtered));
1265        }
1266
1267        // Fallback: return empty suggestions
1268        Ok(Value::Array(vec![]))
1269    }
1270}
1271
1272#[cfg(test)]
1273mod tests {
1274    use super::*;
1275    use crate::expression::ExpressionParser;
1276
1277    // ===== Value Tests =====
1278
1279    #[test]
1280    fn test_value_null() {
1281        let v = Value::null();
1282        assert!(v.is_null());
1283        assert!(!v.is_bool());
1284    }
1285
1286    #[test]
1287    fn test_value_bool() {
1288        let v = Value::bool(true);
1289        assert!(v.is_bool());
1290        assert_eq!(v.as_bool(), Some(true));
1291    }
1292
1293    #[test]
1294    fn test_value_number() {
1295        let v = Value::number(42.5);
1296        assert!(v.is_number());
1297        assert_eq!(v.as_number(), Some(42.5));
1298    }
1299
1300    #[test]
1301    fn test_value_string() {
1302        let v = Value::string("hello");
1303        assert!(v.is_string());
1304        assert_eq!(v.as_str(), Some("hello"));
1305    }
1306
1307    #[test]
1308    fn test_value_array() {
1309        let v = Value::array(vec![Value::number(1.0), Value::number(2.0)]);
1310        assert!(v.is_array());
1311        assert_eq!(v.len(), 2);
1312    }
1313
1314    #[test]
1315    fn test_value_object() {
1316        let mut map = HashMap::new();
1317        map.insert("name".to_string(), Value::string("test"));
1318        let v = Value::object(map);
1319        assert!(v.is_object());
1320        assert_eq!(v.get("name").unwrap().as_str(), Some("test"));
1321    }
1322
1323    #[test]
1324    fn test_value_from_bool() {
1325        let v: Value = true.into();
1326        assert_eq!(v, Value::Bool(true));
1327    }
1328
1329    #[test]
1330    fn test_value_from_number() {
1331        let v: Value = 42.0f64.into();
1332        assert_eq!(v, Value::Number(42.0));
1333    }
1334
1335    #[test]
1336    fn test_value_from_i32() {
1337        let v: Value = 42i32.into();
1338        assert_eq!(v, Value::Number(42.0));
1339    }
1340
1341    #[test]
1342    fn test_value_from_str() {
1343        let v: Value = "hello".into();
1344        assert_eq!(v, Value::String("hello".to_string()));
1345    }
1346
1347    #[test]
1348    fn test_value_default() {
1349        assert_eq!(Value::default(), Value::Null);
1350    }
1351
1352    #[test]
1353    fn test_value_is_empty() {
1354        assert!(Value::array(vec![]).is_empty());
1355        assert!(!Value::array(vec![Value::Null]).is_empty());
1356    }
1357
1358    // ===== DataContext Tests =====
1359
1360    #[test]
1361    fn test_context_new() {
1362        let ctx = DataContext::new();
1363        assert!(!ctx.contains("foo"));
1364    }
1365
1366    #[test]
1367    fn test_context_insert_get() {
1368        let mut ctx = DataContext::new();
1369        ctx.insert("users", Value::array(vec![]));
1370        assert!(ctx.contains("users"));
1371    }
1372
1373    #[test]
1374    fn test_context_dotted_path() {
1375        let mut ctx = DataContext::new();
1376        let mut data = HashMap::new();
1377        data.insert("transactions".to_string(), Value::array(vec![]));
1378        ctx.insert("data", Value::object(data));
1379
1380        assert!(ctx.contains("data.transactions"));
1381    }
1382
1383    // ===== ExecutionError Tests =====
1384
1385    #[test]
1386    fn test_error_display() {
1387        assert_eq!(
1388            ExecutionError::SourceNotFound("foo".to_string()).to_string(),
1389            "source not found: foo"
1390        );
1391        assert_eq!(
1392            ExecutionError::ExpectedArray.to_string(),
1393            "expected an array"
1394        );
1395    }
1396
1397    // ===== Executor Tests =====
1398
1399    fn make_test_data() -> DataContext {
1400        let mut ctx = DataContext::new();
1401
1402        // Create test transactions
1403        let transactions: Vec<Value> = vec![
1404            {
1405                let mut t = HashMap::new();
1406                t.insert("id".to_string(), Value::number(1.0));
1407                t.insert("status".to_string(), Value::string("completed"));
1408                t.insert("amount".to_string(), Value::number(100.0));
1409                Value::Object(t)
1410            },
1411            {
1412                let mut t = HashMap::new();
1413                t.insert("id".to_string(), Value::number(2.0));
1414                t.insert("status".to_string(), Value::string("pending"));
1415                t.insert("amount".to_string(), Value::number(50.0));
1416                Value::Object(t)
1417            },
1418            {
1419                let mut t = HashMap::new();
1420                t.insert("id".to_string(), Value::number(3.0));
1421                t.insert("status".to_string(), Value::string("completed"));
1422                t.insert("amount".to_string(), Value::number(75.0));
1423                Value::Object(t)
1424            },
1425        ];
1426
1427        let mut data = HashMap::new();
1428        data.insert("transactions".to_string(), Value::Array(transactions));
1429        ctx.insert("data", Value::object(data));
1430
1431        ctx
1432    }
1433
1434    #[test]
1435    fn test_execute_simple_source() {
1436        let ctx = make_test_data();
1437        let parser = ExpressionParser::new();
1438        let executor = ExpressionExecutor::new();
1439
1440        let expr = parser.parse("data.transactions").unwrap();
1441        let result = executor.execute(&expr, &ctx).unwrap();
1442
1443        assert!(result.is_array());
1444        assert_eq!(result.len(), 3);
1445    }
1446
1447    #[test]
1448    fn test_execute_source_not_found() {
1449        let ctx = DataContext::new();
1450        let parser = ExpressionParser::new();
1451        let executor = ExpressionExecutor::new();
1452
1453        let expr = parser.parse("nonexistent").unwrap();
1454        let result = executor.execute(&expr, &ctx);
1455
1456        assert!(matches!(result, Err(ExecutionError::SourceNotFound(_))));
1457    }
1458
1459    #[test]
1460    fn test_execute_filter() {
1461        let ctx = make_test_data();
1462        let parser = ExpressionParser::new();
1463        let executor = ExpressionExecutor::new();
1464
1465        let expr = parser
1466            .parse("{{ data.transactions | filter(status=completed) }}")
1467            .unwrap();
1468        let result = executor.execute(&expr, &ctx).unwrap();
1469
1470        assert!(result.is_array());
1471        assert_eq!(result.len(), 2);
1472    }
1473
1474    #[test]
1475    fn test_execute_count() {
1476        let ctx = make_test_data();
1477        let parser = ExpressionParser::new();
1478        let executor = ExpressionExecutor::new();
1479
1480        let expr = parser.parse("{{ data.transactions | count }}").unwrap();
1481        let result = executor.execute(&expr, &ctx).unwrap();
1482
1483        assert_eq!(result.as_number(), Some(3.0));
1484    }
1485
1486    #[test]
1487    fn test_execute_filter_then_count() {
1488        let ctx = make_test_data();
1489        let parser = ExpressionParser::new();
1490        let executor = ExpressionExecutor::new();
1491
1492        let expr = parser
1493            .parse("{{ data.transactions | filter(status=completed) | count }}")
1494            .unwrap();
1495        let result = executor.execute(&expr, &ctx).unwrap();
1496
1497        assert_eq!(result.as_number(), Some(2.0));
1498    }
1499
1500    #[test]
1501    fn test_execute_select() {
1502        let ctx = make_test_data();
1503        let parser = ExpressionParser::new();
1504        let executor = ExpressionExecutor::new();
1505
1506        let expr = parser
1507            .parse("{{ data.transactions | select(id, status) }}")
1508            .unwrap();
1509        let result = executor.execute(&expr, &ctx).unwrap();
1510
1511        let arr = result.as_array().unwrap();
1512        assert_eq!(arr.len(), 3);
1513
1514        // First item should only have id and status
1515        let first = arr[0].as_object().unwrap();
1516        assert!(first.contains_key("id"));
1517        assert!(first.contains_key("status"));
1518        assert!(!first.contains_key("amount"));
1519    }
1520
1521    #[test]
1522    fn test_execute_sort_asc() {
1523        let ctx = make_test_data();
1524        let parser = ExpressionParser::new();
1525        let executor = ExpressionExecutor::new();
1526
1527        let expr = parser
1528            .parse("{{ data.transactions | sort(amount) }}")
1529            .unwrap();
1530        let result = executor.execute(&expr, &ctx).unwrap();
1531
1532        let arr = result.as_array().unwrap();
1533        let amounts: Vec<f64> = arr
1534            .iter()
1535            .filter_map(|v| v.get("amount")?.as_number())
1536            .collect();
1537        assert_eq!(amounts, vec![50.0, 75.0, 100.0]);
1538    }
1539
1540    #[test]
1541    fn test_execute_sort_desc() {
1542        let ctx = make_test_data();
1543        let parser = ExpressionParser::new();
1544        let executor = ExpressionExecutor::new();
1545
1546        let expr = parser
1547            .parse("{{ data.transactions | sort(amount, desc=true) }}")
1548            .unwrap();
1549        let result = executor.execute(&expr, &ctx).unwrap();
1550
1551        let arr = result.as_array().unwrap();
1552        let amounts: Vec<f64> = arr
1553            .iter()
1554            .filter_map(|v| v.get("amount")?.as_number())
1555            .collect();
1556        assert_eq!(amounts, vec![100.0, 75.0, 50.0]);
1557    }
1558
1559    #[test]
1560    fn test_execute_limit() {
1561        let ctx = make_test_data();
1562        let parser = ExpressionParser::new();
1563        let executor = ExpressionExecutor::new();
1564
1565        let expr = parser.parse("{{ data.transactions | limit(2) }}").unwrap();
1566        let result = executor.execute(&expr, &ctx).unwrap();
1567
1568        assert_eq!(result.len(), 2);
1569    }
1570
1571    #[test]
1572    fn test_execute_sum() {
1573        let ctx = make_test_data();
1574        let parser = ExpressionParser::new();
1575        let executor = ExpressionExecutor::new();
1576
1577        let expr = parser
1578            .parse("{{ data.transactions | sum(amount) }}")
1579            .unwrap();
1580        let result = executor.execute(&expr, &ctx).unwrap();
1581
1582        assert_eq!(result.as_number(), Some(225.0)); // 100 + 50 + 75
1583    }
1584
1585    #[test]
1586    fn test_execute_mean() {
1587        let ctx = make_test_data();
1588        let parser = ExpressionParser::new();
1589        let executor = ExpressionExecutor::new();
1590
1591        let expr = parser
1592            .parse("{{ data.transactions | mean(amount) }}")
1593            .unwrap();
1594        let result = executor.execute(&expr, &ctx).unwrap();
1595
1596        assert_eq!(result.as_number(), Some(75.0)); // 225 / 3
1597    }
1598
1599    #[test]
1600    fn test_execute_sample() {
1601        let ctx = make_test_data();
1602        let parser = ExpressionParser::new();
1603        let executor = ExpressionExecutor::new();
1604
1605        let expr = parser.parse("{{ data.transactions | sample(2) }}").unwrap();
1606        let result = executor.execute(&expr, &ctx).unwrap();
1607
1608        assert_eq!(result.len(), 2);
1609    }
1610
1611    #[test]
1612    fn test_execute_percentage() {
1613        let mut ctx = DataContext::new();
1614        ctx.insert("ratio", Value::number(0.75));
1615
1616        let parser = ExpressionParser::new();
1617        let executor = ExpressionExecutor::new();
1618
1619        let expr = parser.parse("{{ ratio | percentage }}").unwrap();
1620        let result = executor.execute(&expr, &ctx).unwrap();
1621
1622        assert_eq!(result.as_number(), Some(75.0));
1623    }
1624
1625    #[test]
1626    fn test_execute_chain() {
1627        let ctx = make_test_data();
1628        let parser = ExpressionParser::new();
1629        let executor = ExpressionExecutor::new();
1630
1631        let expr = parser
1632            .parse("{{ data.transactions | filter(status=completed) | sort(amount, desc=true) | limit(1) }}")
1633            .unwrap();
1634        let result = executor.execute(&expr, &ctx).unwrap();
1635
1636        let arr = result.as_array().unwrap();
1637        assert_eq!(arr.len(), 1);
1638        assert_eq!(arr[0].get("amount").unwrap().as_number(), Some(100.0));
1639    }
1640
1641    #[test]
1642    fn test_filter_numeric_match() {
1643        let ctx = make_test_data();
1644        let parser = ExpressionParser::new();
1645        let executor = ExpressionExecutor::new();
1646
1647        let expr = parser
1648            .parse("{{ data.transactions | filter(amount=100) }}")
1649            .unwrap();
1650        let result = executor.execute(&expr, &ctx).unwrap();
1651
1652        assert_eq!(result.len(), 1);
1653    }
1654
1655    #[test]
1656    fn test_execute_on_empty_array() {
1657        let mut ctx = DataContext::new();
1658        ctx.insert("items", Value::array(vec![]));
1659
1660        let parser = ExpressionParser::new();
1661        let executor = ExpressionExecutor::new();
1662
1663        let expr = parser.parse("{{ items | count }}").unwrap();
1664        let result = executor.execute(&expr, &ctx).unwrap();
1665
1666        assert_eq!(result.as_number(), Some(0.0));
1667    }
1668
1669    #[test]
1670    fn test_execute_mean_empty_array() {
1671        let mut ctx = DataContext::new();
1672        ctx.insert("items", Value::array(vec![]));
1673
1674        let parser = ExpressionParser::new();
1675        let executor = ExpressionExecutor::new();
1676
1677        let expr = parser.parse("{{ items | mean(value) }}").unwrap();
1678        let result = executor.execute(&expr, &ctx).unwrap();
1679
1680        assert_eq!(result.as_number(), Some(0.0));
1681    }
1682
1683    // ===== Rate Transform Tests =====
1684
1685    fn make_time_series_data() -> DataContext {
1686        let mut ctx = DataContext::new();
1687
1688        let events: Vec<Value> = vec![
1689            {
1690                let mut e = HashMap::new();
1691                e.insert("timestamp".to_string(), Value::number(1000.0));
1692                e.insert("value".to_string(), Value::number(10.0));
1693                Value::Object(e)
1694            },
1695            {
1696                let mut e = HashMap::new();
1697                e.insert("timestamp".to_string(), Value::number(2000.0));
1698                e.insert("value".to_string(), Value::number(20.0));
1699                Value::Object(e)
1700            },
1701            {
1702                let mut e = HashMap::new();
1703                e.insert("timestamp".to_string(), Value::number(3000.0));
1704                e.insert("value".to_string(), Value::number(30.0));
1705                Value::Object(e)
1706            },
1707        ];
1708
1709        ctx.insert("events", Value::Array(events));
1710        ctx
1711    }
1712
1713    #[test]
1714    fn test_execute_rate() {
1715        let ctx = make_time_series_data();
1716        let parser = ExpressionParser::new();
1717        let executor = ExpressionExecutor::new();
1718
1719        let expr = parser.parse("{{ events | rate(5s) }}").unwrap();
1720        let result = executor.execute(&expr, &ctx).unwrap();
1721
1722        // Rate should be sum of values in window / window in seconds
1723        // Window is 5s (5000ms), all 3 values sum to 60, rate = 60 / 5 = 12
1724        assert!(result.is_number());
1725        let rate = result.as_number().unwrap();
1726        assert!(rate > 0.0);
1727    }
1728
1729    #[test]
1730    fn test_execute_rate_minute_window() {
1731        let ctx = make_time_series_data();
1732        let parser = ExpressionParser::new();
1733        let executor = ExpressionExecutor::new();
1734
1735        let expr = parser.parse("{{ events | rate(1m) }}").unwrap();
1736        let result = executor.execute(&expr, &ctx).unwrap();
1737
1738        assert!(result.is_number());
1739    }
1740
1741    // ===== Group By Tests =====
1742
1743    #[test]
1744    fn test_execute_group_by() {
1745        let ctx = make_test_data();
1746        let parser = ExpressionParser::new();
1747        let executor = ExpressionExecutor::new();
1748
1749        let expr = parser
1750            .parse("{{ data.transactions | group_by(status) }}")
1751            .unwrap();
1752        let result = executor.execute(&expr, &ctx).unwrap();
1753
1754        let arr = result.as_array().unwrap();
1755        // Should have 2 groups: "completed" (2 items) and "pending" (1 item)
1756        assert_eq!(arr.len(), 2);
1757
1758        for group in arr {
1759            let obj = group.as_object().unwrap();
1760            assert!(obj.contains_key("key"));
1761            assert!(obj.contains_key("items"));
1762            assert!(obj.contains_key("count"));
1763        }
1764    }
1765
1766    // ===== Distinct Tests =====
1767
1768    #[test]
1769    fn test_execute_distinct_field() {
1770        let ctx = make_test_data();
1771        let parser = ExpressionParser::new();
1772        let executor = ExpressionExecutor::new();
1773
1774        let expr = parser
1775            .parse("{{ data.transactions | distinct(status) }}")
1776            .unwrap();
1777        let result = executor.execute(&expr, &ctx).unwrap();
1778
1779        // Should get first of each distinct status
1780        assert_eq!(result.len(), 2); // "completed" and "pending"
1781    }
1782
1783    #[test]
1784    fn test_execute_distinct_no_field() {
1785        let mut ctx = DataContext::new();
1786        ctx.insert(
1787            "items",
1788            Value::array(vec![
1789                Value::string("a"),
1790                Value::string("b"),
1791                Value::string("a"),
1792                Value::string("c"),
1793            ]),
1794        );
1795
1796        let parser = ExpressionParser::new();
1797        let executor = ExpressionExecutor::new();
1798
1799        let expr = parser.parse("{{ items | distinct }}").unwrap();
1800        let result = executor.execute(&expr, &ctx).unwrap();
1801
1802        assert_eq!(result.len(), 3); // "a", "b", "c"
1803    }
1804
1805    // ===== Where Tests =====
1806
1807    #[test]
1808    fn test_execute_where_gt() {
1809        let ctx = make_test_data();
1810        let parser = ExpressionParser::new();
1811        let executor = ExpressionExecutor::new();
1812
1813        let expr = parser
1814            .parse("{{ data.transactions | where(amount, gt, 60) }}")
1815            .unwrap();
1816        let result = executor.execute(&expr, &ctx).unwrap();
1817
1818        // Should get 2 items: amount=100 and amount=75
1819        assert_eq!(result.len(), 2);
1820    }
1821
1822    #[test]
1823    fn test_execute_where_lt() {
1824        let ctx = make_test_data();
1825        let parser = ExpressionParser::new();
1826        let executor = ExpressionExecutor::new();
1827
1828        let expr = parser
1829            .parse("{{ data.transactions | where(amount, lt, 80) }}")
1830            .unwrap();
1831        let result = executor.execute(&expr, &ctx).unwrap();
1832
1833        // Should get 2 items: amount=50 and amount=75
1834        assert_eq!(result.len(), 2);
1835    }
1836
1837    #[test]
1838    fn test_execute_where_eq() {
1839        let ctx = make_test_data();
1840        let parser = ExpressionParser::new();
1841        let executor = ExpressionExecutor::new();
1842
1843        let expr = parser
1844            .parse("{{ data.transactions | where(status, eq, pending) }}")
1845            .unwrap();
1846        let result = executor.execute(&expr, &ctx).unwrap();
1847
1848        assert_eq!(result.len(), 1);
1849    }
1850
1851    #[test]
1852    fn test_execute_where_ne() {
1853        let ctx = make_test_data();
1854        let parser = ExpressionParser::new();
1855        let executor = ExpressionExecutor::new();
1856
1857        let expr = parser
1858            .parse("{{ data.transactions | where(status, ne, pending) }}")
1859            .unwrap();
1860        let result = executor.execute(&expr, &ctx).unwrap();
1861
1862        assert_eq!(result.len(), 2);
1863    }
1864
1865    #[test]
1866    fn test_execute_where_contains() {
1867        let mut ctx = DataContext::new();
1868        let items: Vec<Value> = vec![
1869            {
1870                let mut t = HashMap::new();
1871                t.insert("name".to_string(), Value::string("hello world"));
1872                Value::Object(t)
1873            },
1874            {
1875                let mut t = HashMap::new();
1876                t.insert("name".to_string(), Value::string("goodbye"));
1877                Value::Object(t)
1878            },
1879        ];
1880        ctx.insert("items", Value::Array(items));
1881
1882        let parser = ExpressionParser::new();
1883        let executor = ExpressionExecutor::new();
1884
1885        let expr = parser
1886            .parse("{{ items | where(name, contains, world) }}")
1887            .unwrap();
1888        let result = executor.execute(&expr, &ctx).unwrap();
1889
1890        assert_eq!(result.len(), 1);
1891    }
1892
1893    // ===== Offset Tests =====
1894
1895    #[test]
1896    fn test_execute_offset() {
1897        let ctx = make_test_data();
1898        let parser = ExpressionParser::new();
1899        let executor = ExpressionExecutor::new();
1900
1901        let expr = parser.parse("{{ data.transactions | offset(1) }}").unwrap();
1902        let result = executor.execute(&expr, &ctx).unwrap();
1903
1904        assert_eq!(result.len(), 2); // Original 3, skip 1
1905    }
1906
1907    #[test]
1908    fn test_execute_offset_with_limit() {
1909        let ctx = make_test_data();
1910        let parser = ExpressionParser::new();
1911        let executor = ExpressionExecutor::new();
1912
1913        let expr = parser
1914            .parse("{{ data.transactions | offset(1) | limit(1) }}")
1915            .unwrap();
1916        let result = executor.execute(&expr, &ctx).unwrap();
1917
1918        assert_eq!(result.len(), 1);
1919    }
1920
1921    // ===== Min/Max Tests =====
1922
1923    #[test]
1924    fn test_execute_min() {
1925        let ctx = make_test_data();
1926        let parser = ExpressionParser::new();
1927        let executor = ExpressionExecutor::new();
1928
1929        let expr = parser
1930            .parse("{{ data.transactions | min(amount) }}")
1931            .unwrap();
1932        let result = executor.execute(&expr, &ctx).unwrap();
1933
1934        assert_eq!(result.as_number(), Some(50.0));
1935    }
1936
1937    #[test]
1938    fn test_execute_max() {
1939        let ctx = make_test_data();
1940        let parser = ExpressionParser::new();
1941        let executor = ExpressionExecutor::new();
1942
1943        let expr = parser
1944            .parse("{{ data.transactions | max(amount) }}")
1945            .unwrap();
1946        let result = executor.execute(&expr, &ctx).unwrap();
1947
1948        assert_eq!(result.as_number(), Some(100.0));
1949    }
1950
1951    #[test]
1952    fn test_execute_min_empty() {
1953        let mut ctx = DataContext::new();
1954        ctx.insert("items", Value::array(vec![]));
1955
1956        let parser = ExpressionParser::new();
1957        let executor = ExpressionExecutor::new();
1958
1959        let expr = parser.parse("{{ items | min(value) }}").unwrap();
1960        let result = executor.execute(&expr, &ctx).unwrap();
1961
1962        assert!(result.is_null());
1963    }
1964
1965    // ===== First/Last Tests =====
1966
1967    #[test]
1968    fn test_execute_first() {
1969        let ctx = make_test_data();
1970        let parser = ExpressionParser::new();
1971        let executor = ExpressionExecutor::new();
1972
1973        let expr = parser.parse("{{ data.transactions | first(2) }}").unwrap();
1974        let result = executor.execute(&expr, &ctx).unwrap();
1975
1976        assert_eq!(result.len(), 2);
1977    }
1978
1979    #[test]
1980    fn test_execute_last() {
1981        let ctx = make_test_data();
1982        let parser = ExpressionParser::new();
1983        let executor = ExpressionExecutor::new();
1984
1985        let expr = parser.parse("{{ data.transactions | last(2) }}").unwrap();
1986        let result = executor.execute(&expr, &ctx).unwrap();
1987
1988        let arr = result.as_array().unwrap();
1989        assert_eq!(arr.len(), 2);
1990
1991        // Last item should be id=3
1992        assert_eq!(arr[1].get("id").unwrap().as_number(), Some(3.0));
1993    }
1994
1995    // ===== Flatten Tests =====
1996
1997    #[test]
1998    fn test_execute_flatten() {
1999        let mut ctx = DataContext::new();
2000        ctx.insert(
2001            "nested",
2002            Value::array(vec![
2003                Value::array(vec![Value::number(1.0), Value::number(2.0)]),
2004                Value::array(vec![Value::number(3.0), Value::number(4.0)]),
2005            ]),
2006        );
2007
2008        let parser = ExpressionParser::new();
2009        let executor = ExpressionExecutor::new();
2010
2011        let expr = parser.parse("{{ nested | flatten }}").unwrap();
2012        let result = executor.execute(&expr, &ctx).unwrap();
2013
2014        let arr = result.as_array().unwrap();
2015        assert_eq!(arr.len(), 4);
2016        assert_eq!(arr[0].as_number(), Some(1.0));
2017        assert_eq!(arr[3].as_number(), Some(4.0));
2018    }
2019
2020    #[test]
2021    fn test_execute_flatten_mixed() {
2022        let mut ctx = DataContext::new();
2023        ctx.insert(
2024            "items",
2025            Value::array(vec![
2026                Value::number(1.0),
2027                Value::array(vec![Value::number(2.0), Value::number(3.0)]),
2028                Value::number(4.0),
2029            ]),
2030        );
2031
2032        let parser = ExpressionParser::new();
2033        let executor = ExpressionExecutor::new();
2034
2035        let expr = parser.parse("{{ items | flatten }}").unwrap();
2036        let result = executor.execute(&expr, &ctx).unwrap();
2037
2038        assert_eq!(result.len(), 4);
2039    }
2040
2041    // ===== Reverse Tests =====
2042
2043    #[test]
2044    fn test_execute_reverse() {
2045        let ctx = make_test_data();
2046        let parser = ExpressionParser::new();
2047        let executor = ExpressionExecutor::new();
2048
2049        let expr = parser.parse("{{ data.transactions | reverse }}").unwrap();
2050        let result = executor.execute(&expr, &ctx).unwrap();
2051
2052        let arr = result.as_array().unwrap();
2053        // First item should now be the last one (id=3)
2054        assert_eq!(arr[0].get("id").unwrap().as_number(), Some(3.0));
2055    }
2056
2057    // ===== Complex Chain Tests =====
2058
2059    #[test]
2060    fn test_execute_complex_chain() {
2061        let ctx = make_test_data();
2062        let parser = ExpressionParser::new();
2063        let executor = ExpressionExecutor::new();
2064
2065        // Filter by status, sort by amount descending, get first 1
2066        let expr = parser
2067            .parse("{{ data.transactions | where(status, eq, completed) | sort(amount, desc=true) | first(1) }}")
2068            .unwrap();
2069        let result = executor.execute(&expr, &ctx).unwrap();
2070
2071        let arr = result.as_array().unwrap();
2072        assert_eq!(arr.len(), 1);
2073        // Should be the completed transaction with highest amount (100)
2074        assert_eq!(arr[0].get("amount").unwrap().as_number(), Some(100.0));
2075    }
2076
2077    #[test]
2078    fn test_execute_group_then_count() {
2079        let ctx = make_test_data();
2080        let parser = ExpressionParser::new();
2081        let executor = ExpressionExecutor::new();
2082
2083        let expr = parser
2084            .parse("{{ data.transactions | group_by(status) | count }}")
2085            .unwrap();
2086        let result = executor.execute(&expr, &ctx).unwrap();
2087
2088        // Should count the groups (2)
2089        assert_eq!(result.as_number(), Some(2.0));
2090    }
2091
2092    // ===== Join Tests =====
2093
2094    fn make_join_test_data() -> DataContext {
2095        let mut ctx = DataContext::new();
2096
2097        // Orders dataset
2098        let orders = Value::Array(vec![
2099            {
2100                let mut obj = HashMap::new();
2101                obj.insert("id".to_string(), Value::Number(1.0));
2102                obj.insert("customer_id".to_string(), Value::Number(100.0));
2103                obj.insert("amount".to_string(), Value::Number(50.0));
2104                Value::Object(obj)
2105            },
2106            {
2107                let mut obj = HashMap::new();
2108                obj.insert("id".to_string(), Value::Number(2.0));
2109                obj.insert("customer_id".to_string(), Value::Number(101.0));
2110                obj.insert("amount".to_string(), Value::Number(75.0));
2111                Value::Object(obj)
2112            },
2113            {
2114                let mut obj = HashMap::new();
2115                obj.insert("id".to_string(), Value::Number(3.0));
2116                obj.insert("customer_id".to_string(), Value::Number(100.0));
2117                obj.insert("amount".to_string(), Value::Number(25.0));
2118                Value::Object(obj)
2119            },
2120            {
2121                let mut obj = HashMap::new();
2122                obj.insert("id".to_string(), Value::Number(4.0));
2123                obj.insert("customer_id".to_string(), Value::Number(999.0)); // No matching customer
2124                obj.insert("amount".to_string(), Value::Number(10.0));
2125                Value::Object(obj)
2126            },
2127        ]);
2128
2129        // Customers dataset
2130        let customers = Value::Array(vec![
2131            {
2132                let mut obj = HashMap::new();
2133                obj.insert("customer_id".to_string(), Value::Number(100.0));
2134                obj.insert("name".to_string(), Value::String("Alice".to_string()));
2135                obj.insert("tier".to_string(), Value::String("gold".to_string()));
2136                Value::Object(obj)
2137            },
2138            {
2139                let mut obj = HashMap::new();
2140                obj.insert("customer_id".to_string(), Value::Number(101.0));
2141                obj.insert("name".to_string(), Value::String("Bob".to_string()));
2142                obj.insert("tier".to_string(), Value::String("silver".to_string()));
2143                Value::Object(obj)
2144            },
2145        ]);
2146
2147        ctx.insert("orders", orders);
2148        ctx.insert("customers", customers);
2149        ctx
2150    }
2151
2152    #[test]
2153    fn test_execute_join_basic() {
2154        let ctx = make_join_test_data();
2155        let parser = ExpressionParser::new();
2156        let executor = ExpressionExecutor::new();
2157
2158        let expr = parser
2159            .parse("{{ orders | join(customers, on=customer_id) }}")
2160            .unwrap();
2161        let result = executor.execute(&expr, &ctx).unwrap();
2162
2163        let arr = result.as_array().unwrap();
2164        // 4 orders total - 3 with matches, 1 without
2165        assert_eq!(arr.len(), 4);
2166
2167        // Check first order (customer 100 - Alice)
2168        let first = arr[0].as_object().unwrap();
2169        assert_eq!(first.get("id").unwrap().as_number(), Some(1.0));
2170        assert_eq!(first.get("name").unwrap().as_str(), Some("Alice"));
2171        assert_eq!(first.get("tier").unwrap().as_str(), Some("gold"));
2172    }
2173
2174    #[test]
2175    fn test_execute_join_multiple_matches() {
2176        let ctx = make_join_test_data();
2177        let executor = ExpressionExecutor::new();
2178
2179        // Create expression manually for testing
2180        let expr = Expression {
2181            source: "orders".to_string(),
2182            transforms: vec![Transform::Join {
2183                other: "customers".to_string(),
2184                on: "customer_id".to_string(),
2185            }],
2186        };
2187
2188        let result = executor.execute(&expr, &ctx).unwrap();
2189        let arr = result.as_array().unwrap();
2190
2191        // Count orders for customer 100 (should have 2 orders: id 1 and id 3)
2192        let alice_orders: Vec<_> = arr
2193            .iter()
2194            .filter(|v| {
2195                v.as_object()
2196                    .and_then(|o| o.get("name"))
2197                    .and_then(|n| n.as_str())
2198                    == Some("Alice")
2199            })
2200            .collect();
2201        assert_eq!(alice_orders.len(), 2);
2202    }
2203
2204    #[test]
2205    fn test_execute_join_no_match_keeps_left() {
2206        let ctx = make_join_test_data();
2207        let executor = ExpressionExecutor::new();
2208
2209        let expr = Expression {
2210            source: "orders".to_string(),
2211            transforms: vec![Transform::Join {
2212                other: "customers".to_string(),
2213                on: "customer_id".to_string(),
2214            }],
2215        };
2216
2217        let result = executor.execute(&expr, &ctx).unwrap();
2218        let arr = result.as_array().unwrap();
2219
2220        // Find order 4 (customer 999, no match)
2221        let order4: Vec<_> = arr
2222            .iter()
2223            .filter(|v| {
2224                v.as_object()
2225                    .and_then(|o| o.get("id"))
2226                    .and_then(|n| n.as_number())
2227                    == Some(4.0)
2228            })
2229            .collect();
2230        assert_eq!(order4.len(), 1);
2231
2232        // Should still have original fields but no name/tier
2233        let obj = order4[0].as_object().unwrap();
2234        assert_eq!(obj.get("customer_id").unwrap().as_number(), Some(999.0));
2235        assert!(obj.get("name").is_none());
2236    }
2237
2238    #[test]
2239    fn test_execute_join_other_source_not_found() {
2240        let ctx = make_join_test_data();
2241        let executor = ExpressionExecutor::new();
2242
2243        let expr = Expression {
2244            source: "orders".to_string(),
2245            transforms: vec![Transform::Join {
2246                other: "nonexistent".to_string(),
2247                on: "customer_id".to_string(),
2248            }],
2249        };
2250
2251        let result = executor.execute(&expr, &ctx);
2252        assert!(result.is_err());
2253        assert!(matches!(
2254            result.unwrap_err(),
2255            ExecutionError::SourceNotFound(_)
2256        ));
2257    }
2258
2259    #[test]
2260    fn test_execute_join_chained_with_filter() {
2261        let ctx = make_join_test_data();
2262        let parser = ExpressionParser::new();
2263        let executor = ExpressionExecutor::new();
2264
2265        // Join and then filter to only gold tier customers
2266        let expr = parser
2267            .parse("{{ orders | join(customers, on=customer_id) | filter(tier=gold) }}")
2268            .unwrap();
2269        let result = executor.execute(&expr, &ctx).unwrap();
2270
2271        let arr = result.as_array().unwrap();
2272        // Only orders from customer 100 (Alice, gold tier) - 2 orders
2273        assert_eq!(arr.len(), 2);
2274    }
2275
2276    #[test]
2277    fn test_execute_join_empty_left() {
2278        let mut ctx = DataContext::new();
2279        ctx.insert("empty", Value::Array(vec![]));
2280        ctx.insert(
2281            "other",
2282            Value::Array(vec![{
2283                let mut obj = HashMap::new();
2284                obj.insert("id".to_string(), Value::Number(1.0));
2285                Value::Object(obj)
2286            }]),
2287        );
2288
2289        let executor = ExpressionExecutor::new();
2290        let expr = Expression {
2291            source: "empty".to_string(),
2292            transforms: vec![Transform::Join {
2293                other: "other".to_string(),
2294                on: "id".to_string(),
2295            }],
2296        };
2297
2298        let result = executor.execute(&expr, &ctx).unwrap();
2299        let arr = result.as_array().unwrap();
2300        assert!(arr.is_empty());
2301    }
2302
2303    #[test]
2304    fn test_execute_join_empty_right() {
2305        let mut ctx = DataContext::new();
2306        ctx.insert(
2307            "orders",
2308            Value::Array(vec![{
2309                let mut obj = HashMap::new();
2310                obj.insert("id".to_string(), Value::Number(1.0));
2311                Value::Object(obj)
2312            }]),
2313        );
2314        ctx.insert("empty", Value::Array(vec![]));
2315
2316        let executor = ExpressionExecutor::new();
2317        let expr = Expression {
2318            source: "orders".to_string(),
2319            transforms: vec![Transform::Join {
2320                other: "empty".to_string(),
2321                on: "id".to_string(),
2322            }],
2323        };
2324
2325        let result = executor.execute(&expr, &ctx).unwrap();
2326        let arr = result.as_array().unwrap();
2327        // Left join keeps left items even with no matches
2328        assert_eq!(arr.len(), 1);
2329    }
2330
2331    #[test]
2332    fn test_execute_join_conflicting_field_names() {
2333        let mut ctx = DataContext::new();
2334
2335        // Both have "value" field
2336        ctx.insert(
2337            "left",
2338            Value::Array(vec![{
2339                let mut obj = HashMap::new();
2340                obj.insert("id".to_string(), Value::Number(1.0));
2341                obj.insert("value".to_string(), Value::String("left_val".to_string()));
2342                Value::Object(obj)
2343            }]),
2344        );
2345        ctx.insert(
2346            "right",
2347            Value::Array(vec![{
2348                let mut obj = HashMap::new();
2349                obj.insert("id".to_string(), Value::Number(1.0));
2350                obj.insert("value".to_string(), Value::String("right_val".to_string()));
2351                obj.insert("extra".to_string(), Value::String("extra_val".to_string()));
2352                Value::Object(obj)
2353            }]),
2354        );
2355
2356        let executor = ExpressionExecutor::new();
2357        let expr = Expression {
2358            source: "left".to_string(),
2359            transforms: vec![Transform::Join {
2360                other: "right".to_string(),
2361                on: "id".to_string(),
2362            }],
2363        };
2364
2365        let result = executor.execute(&expr, &ctx).unwrap();
2366        let arr = result.as_array().unwrap();
2367        assert_eq!(arr.len(), 1);
2368
2369        let obj = arr[0].as_object().unwrap();
2370        // Original left value should be preserved
2371        assert_eq!(obj.get("value").unwrap().as_str(), Some("left_val"));
2372        // Right value should be prefixed
2373        assert_eq!(obj.get("right_value").unwrap().as_str(), Some("right_val"));
2374        // Non-conflicting fields should be added directly
2375        assert_eq!(obj.get("extra").unwrap().as_str(), Some("extra_val"));
2376    }
2377
2378    #[test]
2379    fn test_execute_join_with_sum() {
2380        let ctx = make_join_test_data();
2381        let parser = ExpressionParser::new();
2382        let executor = ExpressionExecutor::new();
2383
2384        // Join, filter to gold tier, sum amounts
2385        let expr = parser
2386            .parse(
2387                "{{ orders | join(customers, on=customer_id) | filter(tier=gold) | sum(amount) }}",
2388            )
2389            .unwrap();
2390        let result = executor.execute(&expr, &ctx).unwrap();
2391
2392        // Alice has orders 1 (50) and 3 (25) = 75
2393        assert_eq!(result.as_number(), Some(75.0));
2394    }
2395
2396    // ===== Suggest Transform Tests =====
2397
2398    #[test]
2399    fn test_execute_suggest_with_array() {
2400        let mut ctx = DataContext::new();
2401
2402        // Create suggestion data (simulating pre-computed suggestions)
2403        let suggestions = Value::Array(vec![
2404            {
2405                let mut obj = HashMap::new();
2406                obj.insert("text".to_string(), Value::String("git status".to_string()));
2407                obj.insert("score".to_string(), Value::Number(0.15));
2408                Value::Object(obj)
2409            },
2410            {
2411                let mut obj = HashMap::new();
2412                obj.insert("text".to_string(), Value::String("git commit".to_string()));
2413                obj.insert("score".to_string(), Value::Number(0.12));
2414                Value::Object(obj)
2415            },
2416            {
2417                let mut obj = HashMap::new();
2418                obj.insert("text".to_string(), Value::String("cargo build".to_string()));
2419                obj.insert("score".to_string(), Value::Number(0.10));
2420                Value::Object(obj)
2421            },
2422        ]);
2423        ctx.insert("suggestions", suggestions);
2424
2425        let parser = ExpressionParser::new();
2426        let executor = ExpressionExecutor::new();
2427
2428        // Filter suggestions starting with "git"
2429        let expr = parser.parse("{{ suggestions | suggest(git, 5) }}").unwrap();
2430        let result = executor.execute(&expr, &ctx).unwrap();
2431
2432        let arr = result.as_array().unwrap();
2433        assert_eq!(arr.len(), 2); // Only git status and git commit
2434    }
2435
2436    #[test]
2437    fn test_execute_suggest_with_model_object() {
2438        let mut ctx = DataContext::new();
2439
2440        // Create a model object with pre-computed suggestions
2441        let mut model = HashMap::new();
2442        model.insert(
2443            "model_type".to_string(),
2444            Value::String("ngram_lm".to_string()),
2445        );
2446        model.insert(
2447            "source".to_string(),
2448            Value::String("./model.apr".to_string()),
2449        );
2450        model.insert(
2451            "_suggestions".to_string(),
2452            Value::Array(vec![
2453                {
2454                    let mut obj = HashMap::new();
2455                    obj.insert("text".to_string(), Value::String("git status".to_string()));
2456                    obj.insert("score".to_string(), Value::Number(0.15));
2457                    Value::Object(obj)
2458                },
2459                {
2460                    let mut obj = HashMap::new();
2461                    obj.insert("text".to_string(), Value::String("git commit".to_string()));
2462                    obj.insert("score".to_string(), Value::Number(0.12));
2463                    Value::Object(obj)
2464                },
2465            ]),
2466        );
2467        ctx.insert("model", Value::Object(model));
2468
2469        let parser = ExpressionParser::new();
2470        let executor = ExpressionExecutor::new();
2471
2472        let expr = parser.parse("{{ model | suggest(git, 5) }}").unwrap();
2473        let result = executor.execute(&expr, &ctx).unwrap();
2474
2475        let arr = result.as_array().unwrap();
2476        assert_eq!(arr.len(), 2); // Pre-computed suggestions
2477    }
2478
2479    #[test]
2480    fn test_execute_suggest_empty_model() {
2481        let mut ctx = DataContext::new();
2482
2483        // Model without pre-computed suggestions
2484        let mut model = HashMap::new();
2485        model.insert(
2486            "model_type".to_string(),
2487            Value::String("ngram_lm".to_string()),
2488        );
2489        ctx.insert("model", Value::Object(model));
2490
2491        let parser = ExpressionParser::new();
2492        let executor = ExpressionExecutor::new();
2493
2494        let expr = parser.parse("{{ model | suggest(git, 5) }}").unwrap();
2495        let result = executor.execute(&expr, &ctx).unwrap();
2496
2497        let arr = result.as_array().unwrap();
2498        assert!(arr.is_empty()); // No suggestions when not pre-computed
2499    }
2500
2501    #[test]
2502    fn test_parse_suggest() {
2503        let parser = ExpressionParser::new();
2504        let expr = parser.parse("{{ model | suggest(git, 8) }}").unwrap();
2505
2506        assert_eq!(expr.source, "model");
2507        assert_eq!(expr.transforms.len(), 1);
2508        assert!(matches!(
2509            &expr.transforms[0],
2510            Transform::Suggest { prefix, count } if prefix == "git" && *count == 8
2511        ));
2512    }
2513}