Skip to main content

velesdb_core/velesql/ast/
with_clause.rs

1//! WITH clause types for query-time configuration.
2//!
3//! This module defines WITH clause options for overriding
4//! search parameters on a per-query basis.
5
6use serde::{Deserialize, Serialize};
7
8/// Quantization mode for vector search (EPIC-055 US-005).
9///
10/// Controls the precision/speed tradeoff for similarity search.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
12#[non_exhaustive]
13pub enum QuantizationMode {
14    /// Use full f32 precision (exact, slower).
15    F32,
16    /// Use int8 quantization only (fast, approximate).
17    Int8,
18    /// Use dual-precision: int8 for candidate selection, f32 for reranking.
19    Dual,
20    /// Let the system decide based on index configuration.
21    #[default]
22    Auto,
23}
24
25impl QuantizationMode {
26    /// Parses a quantization mode from a string (case-insensitive).
27    #[must_use]
28    pub fn parse(s: &str) -> Option<Self> {
29        match s.to_lowercase().as_str() {
30            "f32" | "full" | "exact" => Some(Self::F32),
31            "int8" | "sq8" | "quantized" => Some(Self::Int8),
32            "dual" | "hybrid" => Some(Self::Dual),
33            "auto" | "default" => Some(Self::Auto),
34            _ => None,
35        }
36    }
37
38    /// Returns the string representation.
39    #[must_use]
40    pub const fn as_str(&self) -> &'static str {
41        match self {
42            Self::F32 => "f32",
43            Self::Int8 => "int8",
44            Self::Dual => "dual",
45            Self::Auto => "auto",
46        }
47    }
48}
49
50/// WITH clause for query-time configuration overrides.
51#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
52pub struct WithClause {
53    /// Configuration options as key-value pairs.
54    pub options: Vec<WithOption>,
55}
56
57impl WithClause {
58    /// Creates a new empty WITH clause.
59    #[must_use]
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    /// Adds an option to the WITH clause.
65    #[must_use]
66    pub fn with_option(mut self, key: impl Into<String>, value: WithValue) -> Self {
67        self.options.push(WithOption {
68            key: key.into(),
69            value,
70        });
71        self
72    }
73
74    /// Gets an option value by key.
75    #[must_use]
76    pub fn get(&self, key: &str) -> Option<&WithValue> {
77        self.options
78            .iter()
79            .find(|opt| opt.key.eq_ignore_ascii_case(key))
80            .map(|opt| &opt.value)
81    }
82
83    /// Gets the search mode if specified.
84    ///
85    /// Checks both `mode` and `quality` keys (VelesQL v3.5 Phase 4).
86    /// `quality` is an alias for `mode`; if both are set, `mode` takes precedence.
87    #[must_use]
88    pub fn get_mode(&self) -> Option<&str> {
89        self.get("mode")
90            .or_else(|| self.get("quality"))
91            .and_then(|v| v.as_str())
92    }
93
94    /// Gets ef_search if specified.
95    #[must_use]
96    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
97    pub fn get_ef_search(&self) -> Option<usize> {
98        self.get("ef_search")
99            .and_then(WithValue::as_integer)
100            .map(|v| v as usize)
101    }
102
103    /// Gets timeout in milliseconds if specified.
104    #[must_use]
105    #[allow(clippy::cast_sign_loss)]
106    pub fn get_timeout_ms(&self) -> Option<u64> {
107        self.get("timeout_ms")
108            .and_then(WithValue::as_integer)
109            .map(|v| v as u64)
110    }
111
112    /// Gets rerank option if specified.
113    #[must_use]
114    pub fn get_rerank(&self) -> Option<bool> {
115        self.get("rerank").and_then(WithValue::as_bool)
116    }
117
118    /// Gets quantization mode if specified (EPIC-055 US-005).
119    ///
120    /// Supported values: 'f32', 'int8', 'dual', 'auto'.
121    #[must_use]
122    pub fn get_quantization(&self) -> Option<QuantizationMode> {
123        self.get("quantization")
124            .and_then(WithValue::as_str)
125            .and_then(QuantizationMode::parse)
126    }
127
128    /// Gets oversampling ratio if specified (EPIC-055 US-005).
129    ///
130    /// Used with dual-precision mode to control candidate pool size.
131    #[must_use]
132    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
133    pub fn get_oversampling(&self) -> Option<usize> {
134        self.get("oversampling")
135            .and_then(WithValue::as_integer)
136            .map(|v| v.max(1) as usize)
137    }
138}
139
140/// A single option in a WITH clause.
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142pub struct WithOption {
143    /// Option key.
144    pub key: String,
145    /// Option value.
146    pub value: WithValue,
147}
148
149/// Value type for WITH clause options.
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151#[non_exhaustive]
152pub enum WithValue {
153    /// String value.
154    String(String),
155    /// Integer value.
156    Integer(i64),
157    /// Float value.
158    Float(f64),
159    /// Boolean value.
160    Boolean(bool),
161    /// Identifier (unquoted string).
162    Identifier(String),
163}
164
165impl WithValue {
166    /// Returns the value as a string if applicable.
167    #[must_use]
168    pub fn as_str(&self) -> Option<&str> {
169        match self {
170            Self::String(s) | Self::Identifier(s) => Some(s),
171            _ => None,
172        }
173    }
174
175    /// Returns the value as an integer.
176    #[must_use]
177    pub fn as_integer(&self) -> Option<i64> {
178        match self {
179            Self::Integer(i) => Some(*i),
180            _ => None,
181        }
182    }
183
184    /// Returns the value as a float.
185    #[must_use]
186    pub fn as_float(&self) -> Option<f64> {
187        match self {
188            Self::Float(f) => Some(*f),
189            #[allow(clippy::cast_precision_loss)]
190            Self::Integer(i) => Some(*i as f64),
191            _ => None,
192        }
193    }
194
195    /// Returns the value as a boolean.
196    #[must_use]
197    pub fn as_bool(&self) -> Option<bool> {
198        match self {
199            Self::Boolean(b) => Some(*b),
200            _ => None,
201        }
202    }
203}