Skip to main content

vecdb_core/planner/sql/
converter.rs

1use crate::errors::{Result, VecDbError};
2use crate::planner::plan::{PhysicalPlan, QueryPlanner};
3use crate::types::{SearchRequest, Vector};
4
5use super::ast::{Condition, Literal, Operator, ScalarCondition, SelectStatement};
6
7// ─────────────────────────────────────────────────────────────────
8// AstConverter
9// ─────────────────────────────────────────────────────────────────
10
11/// Converts a parsed [`SelectStatement`] into a [`PhysicalPlan`] by delegating
12/// to `QueryPlanner::plan_search`.
13pub struct AstConverter {
14    pub planner: QueryPlanner,
15}
16
17impl AstConverter {
18    pub fn new(planner: QueryPlanner) -> Self {
19        Self { planner }
20    }
21
22    /// Convert a statement into a physical plan.
23    ///
24    /// - Extracts the first `VECTOR_SIM` condition as the query vector.
25    /// - Collects ALL AND-connected scalar conditions into structured filter JSON.
26    /// - Sets `output_columns` from the SELECT column list.
27    /// - Falls back to `k = 10` when no LIMIT clause is present.
28    pub fn convert(&self, stmt: SelectStatement, vector_count: usize) -> Result<PhysicalPlan> {
29        // Capture projection columns before stmt fields are moved.
30        let output_columns: Vec<String> = stmt.columns.clone();
31
32        // Extract query vector from the WHERE clause.
33        let query_vector: Option<Vector> = stmt
34            .where_clause
35            .as_ref()
36            .and_then(find_vector_condition)
37            .map(|vc| vc.vector.clone());
38
39        if query_vector.is_none() {
40            return Err(VecDbError::InvalidQuery(
41                "SQL query must contain a VECTOR_SIM condition".into(),
42            ));
43        }
44
45        // Build structured scalar filter from the WHERE clause (all operators).
46        let scalar_filter: Option<serde_json::Value> =
47            stmt.where_clause.as_ref().and_then(collect_all_conditions);
48
49        let k = stmt.limit.unwrap_or(10);
50
51        let request = SearchRequest {
52            vector: query_vector,
53            query_text: None,
54            k,
55            alpha: 0.7,
56            collection: stmt.table,
57            filter: scalar_filter,
58        };
59
60        let mut plan = self.planner.plan_search(&request, vector_count)?;
61
62        // Propagate SELECT column list into the plan.
63        // Empty list means SELECT * — keep output_columns empty (no projection).
64        plan.output_columns = output_columns;
65
66        Ok(plan)
67    }
68
69    /// Extract the query vector from a `SelectStatement`'s WHERE clause.
70    pub fn extract_query_vector(stmt: &SelectStatement) -> Option<Vector> {
71        stmt.where_clause
72            .as_ref()
73            .and_then(find_vector_condition)
74            .map(|vc| vc.vector.clone())
75    }
76}
77
78// ─────────────────────────────────────────────────────────────────
79// Private helpers
80// ─────────────────────────────────────────────────────────────────
81
82/// Recursively find the first `VECTOR_SIM` condition in the tree.
83pub fn find_vector_condition(cond: &Condition) -> Option<&super::ast::VectorCondition> {
84    match cond {
85        Condition::Vector(vc) => Some(vc),
86        Condition::And(l, r) | Condition::Or(l, r) => {
87            find_vector_condition(l).or_else(|| find_vector_condition(r))
88        }
89        Condition::Not(inner) => find_vector_condition(inner),
90        Condition::Scalar(_) => None,
91    }
92}
93
94/// Walk the condition tree and collect ALL AND-reachable scalar conditions into
95/// a `serde_json::Value`.
96///
97/// - Single condition → `{"field":"…","op":"…","value":…}`
98/// - Multiple AND conditions → `[cond1, cond2, …]`
99/// - `OR` branches and `NOT` are skipped (never over-filter).
100fn collect_all_conditions(cond: &Condition) -> Option<serde_json::Value> {
101    let mut items: Vec<serde_json::Value> = Vec::new();
102    collect_scalar_into(cond, &mut items);
103    match items.len() {
104        0 => None,
105        1 => Some(items.remove(0)),
106        _ => Some(serde_json::Value::Array(items)),
107    }
108}
109
110fn collect_scalar_into(cond: &Condition, out: &mut Vec<serde_json::Value>) {
111    match cond {
112        Condition::Scalar(sc) => {
113            if let Some(obj) = scalar_to_json(sc) {
114                out.push(obj);
115            }
116        }
117        Condition::And(l, r) => {
118            collect_scalar_into(l, out);
119            collect_scalar_into(r, out);
120        }
121        // Vector, Or, Not → skip
122        _ => {}
123    }
124}
125
126fn scalar_to_json(sc: &ScalarCondition) -> Option<serde_json::Value> {
127    let op_str = match sc.op {
128        Operator::Eq => "=",
129        Operator::NotEq => "!=",
130        Operator::Lt => "<",
131        Operator::LtEq => "<=",
132        Operator::Gt => ">",
133        Operator::GtEq => ">=",
134        Operator::Like => "LIKE",
135    };
136    Some(serde_json::json!({
137        "field": sc.field,
138        "op": op_str,
139        "value": literal_to_json(&sc.value),
140    }))
141}
142
143fn literal_to_json(lit: &Literal) -> serde_json::Value {
144    match lit {
145        Literal::String(s) => serde_json::Value::String(s.clone()),
146        Literal::Float(f) => serde_json::json!(f),
147        Literal::Integer(i) => serde_json::json!(i),
148        Literal::Bool(b) => serde_json::Value::Bool(*b),
149    }
150}