chryso_adapter_velox/
lib.rs1mod 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}