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}