Skip to main content

chryso_adapter_velox/
lib.rs

1mod ffi;
2mod plan;
3
4use chryso_adapter::{AdapterCapabilities, ExecutorAdapter, QueryResult};
5#[cfg(not(feature = "velox-ffi"))]
6use chryso_core::error::ChrysoError;
7use chryso_core::error::ChrysoResult;
8use chryso_planner::PhysicalPlan;
9
10#[derive(Debug)]
11pub struct VeloxAdapter {
12    capabilities: AdapterCapabilities,
13}
14
15impl VeloxAdapter {
16    pub fn try_new() -> ChrysoResult<Self> {
17        Ok(Self {
18            capabilities: AdapterCapabilities {
19                joins: false,
20                aggregates: false,
21                distinct: false,
22                topn: false,
23                sort: false,
24                limit: true,
25                offset: false,
26            },
27        })
28    }
29
30    pub fn execute_arrow(&self, plan: &PhysicalPlan) -> ChrysoResult<Vec<u8>> {
31        self.validate_plan(plan)?;
32        let plan_ir = plan::plan_to_ir(plan);
33        #[cfg(feature = "velox-ffi")]
34        {
35            ffi::execute_plan_arrow(&plan_ir)
36        }
37        #[cfg(not(feature = "velox-ffi"))]
38        {
39            let _ = plan_ir;
40            Err(ChrysoError::new(
41                "velox adapter requires feature \"velox-ffi\" (or workspace feature \"velox\")",
42            ))
43        }
44    }
45}
46
47impl ExecutorAdapter for VeloxAdapter {
48    fn execute(&self, plan: &PhysicalPlan) -> ChrysoResult<QueryResult> {
49        self.validate_plan(plan)?;
50        let plan_ir = plan::plan_to_ir(plan);
51        #[cfg(feature = "velox-ffi")]
52        {
53            ffi::execute_plan(&plan_ir)
54        }
55        #[cfg(not(feature = "velox-ffi"))]
56        {
57            let _ = plan_ir;
58            Err(ChrysoError::new(
59                "velox adapter requires feature \"velox-ffi\" (or workspace feature \"velox\")",
60            ))
61        }
62    }
63
64    fn capabilities(&self) -> AdapterCapabilities {
65        self.capabilities.clone()
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::plan::plan_to_ir;
72    use chryso_core::ast::{BinaryOperator, Expr, Literal};
73    use chryso_planner::PhysicalPlan;
74    use serde_json::Value;
75
76    #[test]
77    fn plan_to_ir_renders_table_scan() {
78        let plan = PhysicalPlan::TableScan {
79            table: "t\"\\name".to_string(),
80        };
81        let ir = plan_to_ir(&plan);
82        let parsed: Value = serde_json::from_str(&ir).expect("ir should be valid JSON");
83        assert_eq!(parsed["type"], "TableScan");
84        assert_eq!(parsed["table"], "t\"\\name");
85    }
86
87    #[test]
88    fn plan_to_ir_handles_nested_plan_and_escaping() {
89        let predicate = Expr::BinaryOp {
90            left: Box::new(Expr::Identifier("col\"a".to_string())),
91            op: BinaryOperator::Eq,
92            right: Box::new(Expr::Literal(Literal::String("v\\\"".to_string()))),
93        };
94        let plan = PhysicalPlan::Filter {
95            predicate,
96            input: Box::new(PhysicalPlan::TableScan {
97                table: "tab\"le".to_string(),
98            }),
99        };
100        let ir = plan_to_ir(&plan);
101        let parsed: Value = serde_json::from_str(&ir).expect("ir should be valid JSON");
102        assert_eq!(parsed["type"], "Filter");
103        assert_eq!(parsed["input"]["type"], "TableScan");
104        assert_eq!(parsed["input"]["table"], "tab\"le");
105        assert!(parsed["predicate"].as_str().unwrap().contains("col\"a"));
106    }
107}