Skip to main content

velesdb_core/velesql/ast/
values.rs

1//! Value types for VelesQL expressions.
2//!
3//! This module defines values, vectors, temporal expressions,
4//! and subquery types used in VelesQL queries.
5
6use serde::{Deserialize, Serialize};
7
8/// Vector expression in a NEAR clause.
9#[non_exhaustive]
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub enum VectorExpr {
12    /// Literal vector: [0.1, 0.2, ...]
13    Literal(Vec<f32>),
14    /// Parameter reference: `$param_name`
15    Parameter(String),
16}
17
18/// A value in VelesQL.
19#[non_exhaustive]
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub enum Value {
22    /// Integer value.
23    Integer(i64),
24    /// Unsigned integer value for values exceeding `i64::MAX` (issue #486).
25    UnsignedInteger(u64),
26    /// Float value.
27    Float(f64),
28    /// String value.
29    String(String),
30    /// Boolean value.
31    Boolean(bool),
32    /// Null value.
33    Null,
34    /// Parameter reference.
35    Parameter(String),
36    /// Temporal function (EPIC-038).
37    Temporal(TemporalExpr),
38    /// Scalar subquery (EPIC-039).
39    Subquery(Box<Subquery>),
40}
41
42impl From<i64> for Value {
43    fn from(v: i64) -> Self {
44        Self::Integer(v)
45    }
46}
47
48impl From<u64> for Value {
49    fn from(v: u64) -> Self {
50        Self::UnsignedInteger(v)
51    }
52}
53
54impl From<f64> for Value {
55    fn from(v: f64) -> Self {
56        Self::Float(v)
57    }
58}
59
60impl From<&str> for Value {
61    fn from(v: &str) -> Self {
62        Self::String(v.to_string())
63    }
64}
65
66impl From<String> for Value {
67    fn from(v: String) -> Self {
68        Self::String(v)
69    }
70}
71
72impl From<bool> for Value {
73    fn from(v: bool) -> Self {
74        Self::Boolean(v)
75    }
76}
77
78/// Scalar subquery expression (EPIC-039).
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub struct Subquery {
81    /// The SELECT statement of the subquery.
82    pub select: super::select::SelectStatement,
83    /// Correlated columns (references to outer query).
84    #[serde(default)]
85    pub correlations: Vec<CorrelatedColumn>,
86}
87
88impl Subquery {
89    /// Returns `true` if this subquery is **genuinely correlated** against the
90    /// given outer table names/aliases.
91    ///
92    /// Parsing records a candidate [`CorrelatedColumn`] for every dotted inner
93    /// field whose prefix differs from the subquery's own `FROM`. But VelesQL
94    /// dotted fields are payload paths (`meta.amount`), not table-qualified
95    /// columns, so a candidate is only a *real* outer reference when its prefix
96    /// matches a table/alias visible in the outer query (e.g. `docs.id` where the
97    /// outer query is `... FROM docs ...`). Payload paths whose prefix is not an
98    /// outer table (e.g. `meta.cat`) are therefore **not** correlated.
99    #[must_use]
100    pub fn references_outer_table(&self, outer_tables: &[&str]) -> bool {
101        self.correlations.iter().any(|c| {
102            outer_tables
103                .iter()
104                .any(|t| t.eq_ignore_ascii_case(&c.outer_table))
105        })
106    }
107}
108
109/// A correlated column reference in a subquery (EPIC-039).
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111pub struct CorrelatedColumn {
112    /// Outer query table/alias reference.
113    pub outer_table: String,
114    /// Column name in outer query.
115    pub outer_column: String,
116    /// Column in subquery that references it.
117    pub inner_column: String,
118}
119
120/// Temporal expression for date/time operations (EPIC-038).
121#[non_exhaustive]
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub enum TemporalExpr {
124    /// Current timestamp: `NOW()`
125    Now,
126    /// Interval expression: `INTERVAL '7 days'`
127    Interval(IntervalValue),
128    /// Arithmetic: `NOW() - INTERVAL '7 days'`
129    Subtract(Box<TemporalExpr>, Box<TemporalExpr>),
130    /// Arithmetic: `NOW() + INTERVAL '1 hour'`
131    Add(Box<TemporalExpr>, Box<TemporalExpr>),
132}
133
134impl TemporalExpr {
135    /// Evaluates the temporal expression to epoch seconds.
136    #[must_use]
137    pub fn to_epoch_seconds(&self) -> i64 {
138        use std::time::{SystemTime, UNIX_EPOCH};
139
140        // Reason: Current Unix timestamps fit in i64 until year 292 billion.
141        // Use saturating conversion for theoretical future-proofing.
142        let now = SystemTime::now()
143            .duration_since(UNIX_EPOCH)
144            .map_or(0, |d| i64::try_from(d.as_secs()).unwrap_or(i64::MAX));
145
146        match self {
147            Self::Now => now,
148            Self::Interval(iv) => iv.to_seconds(),
149            Self::Subtract(left, right) => left.to_epoch_seconds() - right.to_epoch_seconds(),
150            Self::Add(left, right) => left.to_epoch_seconds() + right.to_epoch_seconds(),
151        }
152    }
153}
154
155/// Interval value with magnitude and unit (EPIC-038).
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157pub struct IntervalValue {
158    /// Numeric magnitude.
159    pub magnitude: i64,
160    /// Time unit.
161    pub unit: IntervalUnit,
162}
163
164impl IntervalValue {
165    /// Converts the interval to seconds.
166    #[must_use]
167    pub fn to_seconds(&self) -> i64 {
168        match self.unit {
169            IntervalUnit::Seconds => self.magnitude,
170            IntervalUnit::Minutes => self.magnitude * 60,
171            IntervalUnit::Hours => self.magnitude * 3600,
172            IntervalUnit::Days => self.magnitude * 86400,
173            IntervalUnit::Weeks => self.magnitude * 604_800,
174            IntervalUnit::Months => self.magnitude * 2_592_000,
175        }
176    }
177}
178
179impl Value {
180    /// Returns `true` if this value is a subquery.
181    ///
182    /// Scalar (non-correlated) subqueries are executed and substituted before
183    /// the outer query runs (EPIC-039); callers use this to locate the leaves
184    /// that need resolution.
185    #[must_use]
186    pub fn is_subquery(&self) -> bool {
187        matches!(self, Self::Subquery(_))
188    }
189
190    /// Returns `true` if this value is a **correlated** subquery (one that
191    /// references an outer column).
192    ///
193    /// Correlated subqueries are not yet executable; validation rejects them
194    /// while accepting scalar (non-correlated) subqueries, which the executor
195    /// resolves into literals.
196    #[must_use]
197    pub fn is_correlated_subquery(&self) -> bool {
198        matches!(self, Self::Subquery(sq) if !sq.correlations.is_empty())
199    }
200
201    /// Returns `true` if this value is a subquery that is **genuinely
202    /// correlated** against `outer_tables` (its inner WHERE references one of the
203    /// outer query's tables/aliases). See [`Subquery::references_outer_table`].
204    #[must_use]
205    pub fn is_correlated_subquery_with(&self, outer_tables: &[&str]) -> bool {
206        matches!(self, Self::Subquery(sq) if sq.references_outer_table(outer_tables))
207    }
208
209    /// Converts this VelesQL value to a JSON value.
210    ///
211    /// Literal values (integer, float, string, boolean, null) map directly
212    /// to their JSON equivalents. Parameters serialize as `"$name"` strings.
213    /// Temporal expressions evaluate to epoch-seconds integers.
214    /// Subqueries are not serializable and produce `Value::Null`.
215    #[must_use]
216    pub fn to_json(&self) -> serde_json::Value {
217        match self {
218            Self::Integer(i) => serde_json::json!(i),
219            Self::UnsignedInteger(u) => serde_json::json!(u),
220            Self::Float(f) => serde_json::json!(f),
221            Self::String(s) => serde_json::json!(s),
222            Self::Boolean(b) => serde_json::json!(b),
223            Self::Parameter(p) => serde_json::json!(format!("${p}")),
224            Self::Temporal(t) => serde_json::json!(t.to_epoch_seconds()),
225            Self::Null | Self::Subquery(_) => serde_json::Value::Null,
226        }
227    }
228}
229
230/// Time unit for INTERVAL expressions (EPIC-038).
231#[non_exhaustive]
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
233pub enum IntervalUnit {
234    /// Seconds.
235    Seconds,
236    /// Minutes.
237    Minutes,
238    /// Hours.
239    Hours,
240    /// Days.
241    Days,
242    /// Weeks.
243    Weeks,
244    /// Months.
245    Months,
246}