velesdb_core/velesql/
ast.rs

1//! Abstract Syntax Tree (AST) for `VelesQL` queries.
2//!
3//! This module defines the data structures representing parsed `VelesQL` queries.
4
5use serde::{Deserialize, Serialize};
6
7/// A complete `VelesQL` query.
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct Query {
10    /// The SELECT statement.
11    pub select: SelectStatement,
12}
13
14/// A SELECT statement.
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct SelectStatement {
17    /// Columns to select.
18    pub columns: SelectColumns,
19    /// Collection name (FROM clause).
20    pub from: String,
21    /// WHERE conditions (optional).
22    pub where_clause: Option<Condition>,
23    /// LIMIT value (optional).
24    pub limit: Option<u64>,
25    /// OFFSET value (optional).
26    pub offset: Option<u64>,
27    /// WITH clause for query-time configuration (optional).
28    pub with_clause: Option<WithClause>,
29}
30
31/// WITH clause for query-time configuration overrides.
32///
33/// Allows overriding search parameters on a per-query basis.
34///
35/// # Example
36///
37/// ```sql
38/// SELECT * FROM docs WHERE vector NEAR $v LIMIT 10
39/// WITH (mode = 'accurate', timeout_ms = 5000)
40/// ```
41#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
42pub struct WithClause {
43    /// Configuration options as key-value pairs.
44    pub options: Vec<WithOption>,
45}
46
47impl WithClause {
48    /// Creates a new empty WITH clause.
49    #[must_use]
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    /// Adds an option to the WITH clause.
55    #[must_use]
56    pub fn with_option(mut self, key: impl Into<String>, value: WithValue) -> Self {
57        self.options.push(WithOption {
58            key: key.into(),
59            value,
60        });
61        self
62    }
63
64    /// Gets an option value by key.
65    #[must_use]
66    pub fn get(&self, key: &str) -> Option<&WithValue> {
67        self.options
68            .iter()
69            .find(|opt| opt.key.eq_ignore_ascii_case(key))
70            .map(|opt| &opt.value)
71    }
72
73    /// Gets the search mode if specified.
74    #[must_use]
75    pub fn get_mode(&self) -> Option<&str> {
76        self.get("mode").and_then(|v| v.as_str())
77    }
78
79    /// Gets `ef_search` if specified.
80    #[must_use]
81    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
82    pub fn get_ef_search(&self) -> Option<usize> {
83        self.get("ef_search")
84            .and_then(WithValue::as_integer)
85            .map(|v| v as usize)
86    }
87
88    /// Gets timeout in milliseconds if specified.
89    #[must_use]
90    #[allow(clippy::cast_sign_loss)]
91    pub fn get_timeout_ms(&self) -> Option<u64> {
92        self.get("timeout_ms")
93            .and_then(WithValue::as_integer)
94            .map(|v| v as u64)
95    }
96
97    /// Gets rerank option if specified.
98    #[must_use]
99    pub fn get_rerank(&self) -> Option<bool> {
100        self.get("rerank").and_then(WithValue::as_bool)
101    }
102}
103
104/// A single option in a WITH clause.
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct WithOption {
107    /// Option key (e.g., "mode", "`ef_search`").
108    pub key: String,
109    /// Option value.
110    pub value: WithValue,
111}
112
113/// Value type for WITH clause options.
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub enum WithValue {
116    /// String value (e.g., 'accurate').
117    String(String),
118    /// Integer value (e.g., 512).
119    Integer(i64),
120    /// Float value (e.g., 0.95).
121    Float(f64),
122    /// Boolean value (true/false).
123    Boolean(bool),
124    /// Identifier (unquoted string).
125    Identifier(String),
126}
127
128impl WithValue {
129    /// Returns the value as a string if it is a String or Identifier.
130    #[must_use]
131    pub fn as_str(&self) -> Option<&str> {
132        match self {
133            Self::String(s) | Self::Identifier(s) => Some(s),
134            _ => None,
135        }
136    }
137
138    /// Returns the value as an integer if it is an Integer.
139    #[must_use]
140    pub fn as_integer(&self) -> Option<i64> {
141        match self {
142            Self::Integer(i) => Some(*i),
143            _ => None,
144        }
145    }
146
147    /// Returns the value as a float if it is a Float or Integer.
148    #[must_use]
149    pub fn as_float(&self) -> Option<f64> {
150        match self {
151            Self::Float(f) => Some(*f),
152            #[allow(clippy::cast_precision_loss)]
153            Self::Integer(i) => Some(*i as f64),
154            _ => None,
155        }
156    }
157
158    /// Returns the value as a boolean if it is a Boolean.
159    #[must_use]
160    pub fn as_bool(&self) -> Option<bool> {
161        match self {
162            Self::Boolean(b) => Some(*b),
163            _ => None,
164        }
165    }
166}
167
168/// Columns in a SELECT statement.
169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
170pub enum SelectColumns {
171    /// Select all columns (*).
172    All,
173    /// Select specific columns.
174    Columns(Vec<Column>),
175}
176
177/// A column reference.
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
179pub struct Column {
180    /// Column name (e.g., "id", "payload.title").
181    pub name: String,
182    /// Optional alias (AS clause).
183    pub alias: Option<String>,
184}
185
186impl Column {
187    /// Creates a new column reference.
188    #[must_use]
189    pub fn new(name: impl Into<String>) -> Self {
190        Self {
191            name: name.into(),
192            alias: None,
193        }
194    }
195
196    /// Creates a column with an alias.
197    #[must_use]
198    pub fn with_alias(name: impl Into<String>, alias: impl Into<String>) -> Self {
199        Self {
200            name: name.into(),
201            alias: Some(alias.into()),
202        }
203    }
204}
205
206/// A condition in a WHERE clause.
207#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
208pub enum Condition {
209    /// Vector similarity search: `vector NEAR [metric] $param`
210    VectorSearch(VectorSearch),
211    /// Multi-vector fused search: `vector NEAR_FUSED [$v1, $v2] USING FUSION 'rrf'`
212    VectorFusedSearch(VectorFusedSearch),
213    /// Comparison: column op value
214    Comparison(Comparison),
215    /// IN operator: column IN (values)
216    In(InCondition),
217    /// BETWEEN operator: column BETWEEN a AND b
218    Between(BetweenCondition),
219    /// LIKE operator: column LIKE pattern
220    Like(LikeCondition),
221    /// IS NULL / IS NOT NULL
222    IsNull(IsNullCondition),
223    /// Full-text search: column MATCH 'query'
224    Match(MatchCondition),
225    /// Logical AND
226    And(Box<Condition>, Box<Condition>),
227    /// Logical OR
228    Or(Box<Condition>, Box<Condition>),
229    /// Logical NOT
230    Not(Box<Condition>),
231    /// Grouped condition (parentheses)
232    Group(Box<Condition>),
233}
234
235/// Vector similarity search condition.
236///
237/// Note: The distance metric is defined at collection creation time,
238/// not per-query. The search uses the collection's configured metric.
239#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
240pub struct VectorSearch {
241    /// Vector expression (literal or parameter).
242    pub vector: VectorExpr,
243}
244
245/// Multi-vector fused search condition.
246///
247/// Allows searching with multiple vectors and fusing results.
248///
249/// # Example
250///
251/// ```sql
252/// SELECT * FROM docs WHERE vector NEAR_FUSED [$v1, $v2, $v3]
253///     USING FUSION 'rrf' (k = 60)
254/// ```
255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
256pub struct VectorFusedSearch {
257    /// List of vector expressions (literals or parameters).
258    pub vectors: Vec<VectorExpr>,
259    /// Fusion strategy configuration.
260    pub fusion: FusionConfig,
261}
262
263/// Configuration for multi-vector fusion.
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
265pub struct FusionConfig {
266    /// Fusion strategy name: "average", "maximum", "rrf", "weighted".
267    pub strategy: String,
268    /// Strategy-specific parameters.
269    pub params: std::collections::HashMap<String, f64>,
270}
271
272impl Default for FusionConfig {
273    fn default() -> Self {
274        Self {
275            strategy: "rrf".to_string(),
276            params: std::collections::HashMap::new(),
277        }
278    }
279}
280
281impl FusionConfig {
282    /// Creates a new RRF fusion config with default k=60.
283    #[must_use]
284    pub fn rrf() -> Self {
285        let mut params = std::collections::HashMap::new();
286        params.insert("k".to_string(), 60.0);
287        Self {
288            strategy: "rrf".to_string(),
289            params,
290        }
291    }
292
293    /// Creates a weighted fusion config.
294    #[must_use]
295    pub fn weighted(avg_weight: f64, max_weight: f64, hit_weight: f64) -> Self {
296        let mut params = std::collections::HashMap::new();
297        params.insert("avg_weight".to_string(), avg_weight);
298        params.insert("max_weight".to_string(), max_weight);
299        params.insert("hit_weight".to_string(), hit_weight);
300        Self {
301            strategy: "weighted".to_string(),
302            params,
303        }
304    }
305}
306
307/// Vector expression in a NEAR clause.
308#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
309pub enum VectorExpr {
310    /// Literal vector: [0.1, 0.2, ...]
311    Literal(Vec<f32>),
312    /// Parameter reference: `$param_name`
313    Parameter(String),
314}
315
316/// Comparison condition.
317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318pub struct Comparison {
319    /// Column name.
320    pub column: String,
321    /// Comparison operator.
322    pub operator: CompareOp,
323    /// Value to compare against.
324    pub value: Value,
325}
326
327/// Comparison operators.
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
329pub enum CompareOp {
330    /// Equal (=)
331    Eq,
332    /// Not equal (!= or <>)
333    NotEq,
334    /// Greater than (>)
335    Gt,
336    /// Greater than or equal (>=)
337    Gte,
338    /// Less than (<)
339    Lt,
340    /// Less than or equal (<=)
341    Lte,
342}
343
344/// IN condition: column IN (value1, value2, ...)
345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346pub struct InCondition {
347    /// Column name.
348    pub column: String,
349    /// List of values.
350    pub values: Vec<Value>,
351}
352
353/// BETWEEN condition: column BETWEEN low AND high
354#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
355pub struct BetweenCondition {
356    /// Column name.
357    pub column: String,
358    /// Low value.
359    pub low: Value,
360    /// High value.
361    pub high: Value,
362}
363
364/// LIKE/ILIKE condition: column LIKE pattern or column ILIKE pattern
365#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
366pub struct LikeCondition {
367    /// Column name.
368    pub column: String,
369    /// Pattern (with % and _ wildcards).
370    pub pattern: String,
371    /// True for ILIKE (case-insensitive), false for LIKE (case-sensitive).
372    #[serde(default)]
373    pub case_insensitive: bool,
374}
375
376/// IS NULL condition.
377#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
378pub struct IsNullCondition {
379    /// Column name.
380    pub column: String,
381    /// True for IS NULL, false for IS NOT NULL.
382    pub is_null: bool,
383}
384
385/// MATCH condition for full-text search.
386#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
387pub struct MatchCondition {
388    /// Column name.
389    pub column: String,
390    /// Search query.
391    pub query: String,
392}
393
394/// A value in `VelesQL`.
395#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
396pub enum Value {
397    /// Integer value.
398    Integer(i64),
399    /// Float value.
400    Float(f64),
401    /// String value.
402    String(String),
403    /// Boolean value.
404    Boolean(bool),
405    /// Null value.
406    Null,
407    /// Parameter reference.
408    Parameter(String),
409}
410
411impl From<i64> for Value {
412    fn from(v: i64) -> Self {
413        Self::Integer(v)
414    }
415}
416
417impl From<f64> for Value {
418    fn from(v: f64) -> Self {
419        Self::Float(v)
420    }
421}
422
423impl From<&str> for Value {
424    fn from(v: &str) -> Self {
425        Self::String(v.to_string())
426    }
427}
428
429impl From<String> for Value {
430    fn from(v: String) -> Self {
431        Self::String(v)
432    }
433}
434
435impl From<bool> for Value {
436    fn from(v: bool) -> Self {
437        Self::Boolean(v)
438    }
439}