Skip to main content

vantage_csv/
expr_data_source.rs

1//! ExprDataSource implementation for CSV
2//!
3//! Enables executing expressions against CSV by resolving DeferredFn closures.
4//! This is used by `column_values_expression` and other deferred-based expressions.
5
6use vantage_expressions::Expression;
7use vantage_expressions::traits::datasource::ExprDataSource;
8use vantage_expressions::traits::expressive::{DeferredFn, ExpressiveEnum};
9
10use crate::Csv;
11use crate::condition::resolve_param;
12use crate::type_system::AnyCsvType;
13
14impl ExprDataSource<AnyCsvType> for Csv {
15    async fn execute(&self, expr: &Expression<AnyCsvType>) -> vantage_core::Result<AnyCsvType> {
16        // For CSV, execution means resolving deferred params.
17        // An expression with a single param resolves to that param's value.
18        // Multi-param expressions resolve each param and join as comma-separated.
19        if expr.parameters.len() == 1 {
20            resolve_param(&expr.parameters[0]).await
21        } else if expr.parameters.is_empty() {
22            Ok(AnyCsvType::new(expr.template.clone()))
23        } else {
24            // Resolve all params, join with commas
25            let mut results = Vec::new();
26            for param in &expr.parameters {
27                let resolved = resolve_param(param).await?;
28                results.push(resolved.value().clone());
29            }
30            Ok(AnyCsvType::new(results.join(",")))
31        }
32    }
33
34    fn defer(&self, expr: Expression<AnyCsvType>) -> DeferredFn<AnyCsvType>
35    where
36        AnyCsvType: Clone + Send + Sync + 'static,
37    {
38        let csv = self.clone();
39        DeferredFn::new(move || {
40            let csv = csv.clone();
41            let expr = expr.clone();
42            Box::pin(async move {
43                let result = csv.execute(&expr).await?;
44                Ok(ExpressiveEnum::Scalar(result))
45            })
46        })
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use vantage_table::table::Table;
54    use vantage_table::traits::table_source::TableSource;
55    use vantage_types::EmptyEntity;
56
57    fn test_csv() -> Csv {
58        Csv::new(format!("{}/data", env!("CARGO_MANIFEST_DIR")))
59    }
60
61    #[tokio::test]
62    async fn test_execute_column_table_values_expr() {
63        let csv = test_csv();
64        let table = Table::<Csv, EmptyEntity>::new("client", csv.clone())
65            .with_column_of::<String>("name")
66            .with_column_of::<bool>("is_paying_client");
67
68        let name_col = csv.create_column::<String>("name");
69        let associated = csv.column_table_values_expr(&table, &name_col);
70        let result = csv.execute(associated.expression()).await.unwrap();
71
72        // Result is a List of AnyCsvType values
73        let names = result.try_get::<Vec<AnyCsvType>>().unwrap();
74        let name_strings: Vec<&str> = names.iter().map(|v| v.value().as_str()).collect();
75        assert!(name_strings.contains(&"Marty McFly"));
76        assert!(name_strings.contains(&"Doc Brown"));
77        assert!(name_strings.contains(&"Biff Tannen"));
78    }
79
80    #[tokio::test]
81    async fn test_execute_column_values_with_condition() {
82        use crate::operation::CsvOperation;
83
84        let csv = test_csv();
85        let mut table = Table::<Csv, EmptyEntity>::new("client", csv.clone())
86            .with_column_of::<String>("name")
87            .with_column_of::<bool>("is_paying_client");
88
89        table.add_condition(table["is_paying_client"].eq(AnyCsvType::new(true)));
90
91        let name_col = csv.create_column::<String>("name");
92        let associated = csv.column_table_values_expr(&table, &name_col);
93        let result = csv.execute(associated.expression()).await.unwrap();
94
95        let names = result.try_get::<Vec<AnyCsvType>>().unwrap();
96        let name_strings: Vec<&str> = names.iter().map(|v| v.value().as_str()).collect();
97        assert!(name_strings.contains(&"Marty McFly"));
98        assert!(name_strings.contains(&"Doc Brown"));
99        assert!(!name_strings.contains(&"Biff Tannen"));
100    }
101}