Skip to main content

nodedb_sql/
types.rs

1//! SqlPlan intermediate representation types.
2//!
3//! These types represent the output of the nodedb-sql planner. Both Origin
4//! (server) and Lite (embedded) map these to their own execution model.
5
6/// The top-level plan produced by the SQL planner.
7#[derive(Debug, Clone)]
8pub enum SqlPlan {
9    // ── Constant ──
10    /// Query with no FROM clause: SELECT 1, SELECT 'hello' AS name, etc.
11    /// Produces a single row with evaluated constant expressions.
12    ConstantResult {
13        columns: Vec<String>,
14        values: Vec<SqlValue>,
15    },
16
17    // ── Reads ──
18    Scan {
19        collection: String,
20        alias: Option<String>,
21        engine: EngineType,
22        filters: Vec<Filter>,
23        projection: Vec<Projection>,
24        sort_keys: Vec<SortKey>,
25        limit: Option<usize>,
26        offset: usize,
27        distinct: bool,
28        window_functions: Vec<WindowSpec>,
29    },
30    PointGet {
31        collection: String,
32        alias: Option<String>,
33        engine: EngineType,
34        key_column: String,
35        key_value: SqlValue,
36    },
37    RangeScan {
38        collection: String,
39        field: String,
40        lower: Option<SqlValue>,
41        upper: Option<SqlValue>,
42        limit: usize,
43    },
44
45    // ── Writes ──
46    Insert {
47        collection: String,
48        engine: EngineType,
49        rows: Vec<Vec<(String, SqlValue)>>,
50        /// Column defaults from schema: `(column_name, default_expr)`.
51        /// Used to auto-generate values for missing columns (e.g. `id` with `UUID_V7`).
52        column_defaults: Vec<(String, String)>,
53    },
54    /// KV INSERT: key and value are fundamentally separate.
55    /// Each entry is `(key, value_columns)`.
56    KvInsert {
57        collection: String,
58        entries: Vec<(SqlValue, Vec<(String, SqlValue)>)>,
59        /// TTL in seconds (0 = no expiry). Extracted from `ttl` column if present.
60        ttl_secs: u64,
61    },
62    /// UPSERT: insert or merge if document exists.
63    Upsert {
64        collection: String,
65        engine: EngineType,
66        rows: Vec<Vec<(String, SqlValue)>>,
67        column_defaults: Vec<(String, String)>,
68    },
69    InsertSelect {
70        target: String,
71        source: Box<SqlPlan>,
72        limit: usize,
73    },
74    Update {
75        collection: String,
76        engine: EngineType,
77        assignments: Vec<(String, SqlExpr)>,
78        filters: Vec<Filter>,
79        target_keys: Vec<SqlValue>,
80        returning: bool,
81    },
82    Delete {
83        collection: String,
84        engine: EngineType,
85        filters: Vec<Filter>,
86        target_keys: Vec<SqlValue>,
87    },
88    Truncate {
89        collection: String,
90        restart_identity: bool,
91    },
92
93    // ── Joins ──
94    Join {
95        left: Box<SqlPlan>,
96        right: Box<SqlPlan>,
97        on: Vec<(String, String)>,
98        join_type: JoinType,
99        condition: Option<SqlExpr>,
100        limit: usize,
101        /// Post-join projection: column names to keep (empty = all columns).
102        projection: Vec<Projection>,
103        /// Post-join filters (from WHERE clause).
104        filters: Vec<Filter>,
105    },
106
107    // ── Aggregation ──
108    Aggregate {
109        input: Box<SqlPlan>,
110        group_by: Vec<SqlExpr>,
111        aggregates: Vec<AggregateExpr>,
112        having: Vec<Filter>,
113        limit: usize,
114    },
115
116    // ── Timeseries ──
117    TimeseriesScan {
118        collection: String,
119        time_range: (i64, i64),
120        bucket_interval_ms: i64,
121        group_by: Vec<String>,
122        aggregates: Vec<AggregateExpr>,
123        filters: Vec<Filter>,
124        projection: Vec<Projection>,
125        gap_fill: String,
126        limit: usize,
127        tiered: bool,
128    },
129    TimeseriesIngest {
130        collection: String,
131        rows: Vec<Vec<(String, SqlValue)>>,
132    },
133
134    // ── Search (first-class) ──
135    VectorSearch {
136        collection: String,
137        field: String,
138        query_vector: Vec<f32>,
139        top_k: usize,
140        ef_search: usize,
141        filters: Vec<Filter>,
142    },
143    MultiVectorSearch {
144        collection: String,
145        query_vector: Vec<f32>,
146        top_k: usize,
147        ef_search: usize,
148    },
149    TextSearch {
150        collection: String,
151        query: String,
152        top_k: usize,
153        fuzzy: bool,
154        filters: Vec<Filter>,
155    },
156    HybridSearch {
157        collection: String,
158        query_vector: Vec<f32>,
159        query_text: String,
160        top_k: usize,
161        ef_search: usize,
162        vector_weight: f32,
163        fuzzy: bool,
164    },
165    SpatialScan {
166        collection: String,
167        field: String,
168        predicate: SpatialPredicate,
169        query_geometry: Vec<u8>,
170        distance_meters: f64,
171        attribute_filters: Vec<Filter>,
172        limit: usize,
173        projection: Vec<Projection>,
174    },
175
176    // ── Composite ──
177    Union {
178        inputs: Vec<SqlPlan>,
179        distinct: bool,
180    },
181    Intersect {
182        left: Box<SqlPlan>,
183        right: Box<SqlPlan>,
184        all: bool,
185    },
186    Except {
187        left: Box<SqlPlan>,
188        right: Box<SqlPlan>,
189        all: bool,
190    },
191    RecursiveScan {
192        collection: String,
193        base_filters: Vec<Filter>,
194        recursive_filters: Vec<Filter>,
195        max_iterations: usize,
196        distinct: bool,
197        limit: usize,
198    },
199
200    /// Non-recursive CTE: execute each definition, then the outer query.
201    Cte {
202        /// CTE definitions: `(name, subquery_plan)`.
203        definitions: Vec<(String, SqlPlan)>,
204        /// The outer query that references CTE names.
205        outer: Box<SqlPlan>,
206    },
207}
208
209/// Database engine type for a collection.
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum EngineType {
212    DocumentSchemaless,
213    DocumentStrict,
214    KeyValue,
215    Columnar,
216    Timeseries,
217    Spatial,
218}
219
220/// SQL join type.
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub enum JoinType {
223    Inner,
224    Left,
225    Right,
226    Full,
227    Semi,
228    Anti,
229    Cross,
230}
231
232impl JoinType {
233    pub fn as_str(&self) -> &'static str {
234        match self {
235            Self::Inner => "inner",
236            Self::Left => "left",
237            Self::Right => "right",
238            Self::Full => "full",
239            Self::Semi => "semi",
240            Self::Anti => "anti",
241            Self::Cross => "cross",
242        }
243    }
244}
245
246/// Spatial predicate types.
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum SpatialPredicate {
249    DWithin,
250    Contains,
251    Intersects,
252    Within,
253}
254
255/// A filter predicate.
256#[derive(Debug, Clone)]
257pub struct Filter {
258    pub expr: FilterExpr,
259}
260
261/// Filter expression tree.
262#[derive(Debug, Clone)]
263pub enum FilterExpr {
264    Comparison {
265        field: String,
266        op: CompareOp,
267        value: SqlValue,
268    },
269    Like {
270        field: String,
271        pattern: String,
272    },
273    InList {
274        field: String,
275        values: Vec<SqlValue>,
276    },
277    Between {
278        field: String,
279        low: SqlValue,
280        high: SqlValue,
281    },
282    IsNull {
283        field: String,
284    },
285    IsNotNull {
286        field: String,
287    },
288    And(Vec<Filter>),
289    Or(Vec<Filter>),
290    Not(Box<Filter>),
291    /// Raw expression filter (for complex predicates that don't fit simple patterns).
292    Expr(SqlExpr),
293}
294
295/// Comparison operators.
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub enum CompareOp {
298    Eq,
299    Ne,
300    Gt,
301    Ge,
302    Lt,
303    Le,
304}
305
306/// Projection item in SELECT.
307#[derive(Debug, Clone)]
308pub enum Projection {
309    /// Simple column reference: `SELECT name`
310    Column(String),
311    /// All columns: `SELECT *`
312    Star,
313    /// Qualified star: `SELECT t.*`
314    QualifiedStar(String),
315    /// Computed expression: `SELECT price * qty AS total`
316    Computed { expr: SqlExpr, alias: String },
317}
318
319/// Sort key for ORDER BY.
320#[derive(Debug, Clone)]
321pub struct SortKey {
322    pub expr: SqlExpr,
323    pub ascending: bool,
324    pub nulls_first: bool,
325}
326
327/// Aggregate expression: `COUNT(*)`, `SUM(amount)`, etc.
328#[derive(Debug, Clone)]
329pub struct AggregateExpr {
330    pub function: String,
331    pub args: Vec<SqlExpr>,
332    pub alias: String,
333    pub distinct: bool,
334}
335
336/// Window function specification.
337#[derive(Debug, Clone)]
338pub struct WindowSpec {
339    pub function: String,
340    pub args: Vec<SqlExpr>,
341    pub partition_by: Vec<SqlExpr>,
342    pub order_by: Vec<SortKey>,
343    pub alias: String,
344}
345
346/// SQL value literal.
347#[derive(Debug, Clone, PartialEq)]
348pub enum SqlValue {
349    Int(i64),
350    Float(f64),
351    String(String),
352    Bool(bool),
353    Null,
354    Bytes(Vec<u8>),
355    Array(Vec<SqlValue>),
356}
357
358/// SQL expression tree.
359#[derive(Debug, Clone)]
360pub enum SqlExpr {
361    /// Column reference, optionally qualified: `name` or `users.name`
362    Column { table: Option<String>, name: String },
363    /// Literal value.
364    Literal(SqlValue),
365    /// Binary operation: `a + b`, `x > 5`
366    BinaryOp {
367        left: Box<SqlExpr>,
368        op: BinaryOp,
369        right: Box<SqlExpr>,
370    },
371    /// Unary operation: `-x`, `NOT flag`
372    UnaryOp { op: UnaryOp, expr: Box<SqlExpr> },
373    /// Function call: `COUNT(*)`, `vector_distance(field, ARRAY[...])`
374    Function {
375        name: String,
376        args: Vec<SqlExpr>,
377        distinct: bool,
378    },
379    /// CASE WHEN ... THEN ... ELSE ... END
380    Case {
381        operand: Option<Box<SqlExpr>>,
382        when_then: Vec<(SqlExpr, SqlExpr)>,
383        else_expr: Option<Box<SqlExpr>>,
384    },
385    /// CAST(expr AS type)
386    Cast { expr: Box<SqlExpr>, to_type: String },
387    /// Subquery expression (IN, EXISTS, scalar)
388    Subquery(Box<SqlPlan>),
389    /// Wildcard `*`
390    Wildcard,
391    /// `IS NULL` / `IS NOT NULL`
392    IsNull { expr: Box<SqlExpr>, negated: bool },
393    /// `expr IN (values)`
394    InList {
395        expr: Box<SqlExpr>,
396        list: Vec<SqlExpr>,
397        negated: bool,
398    },
399    /// `expr BETWEEN low AND high`
400    Between {
401        expr: Box<SqlExpr>,
402        low: Box<SqlExpr>,
403        high: Box<SqlExpr>,
404        negated: bool,
405    },
406    /// `expr LIKE pattern`
407    Like {
408        expr: Box<SqlExpr>,
409        pattern: Box<SqlExpr>,
410        negated: bool,
411    },
412    /// Array literal: `ARRAY[1.0, 2.0, 3.0]`
413    ArrayLiteral(Vec<SqlExpr>),
414}
415
416/// Binary operators.
417#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub enum BinaryOp {
419    // Arithmetic
420    Add,
421    Sub,
422    Mul,
423    Div,
424    Mod,
425    // Comparison
426    Eq,
427    Ne,
428    Gt,
429    Ge,
430    Lt,
431    Le,
432    // Logical
433    And,
434    Or,
435    // String
436    Concat,
437}
438
439/// Unary operators.
440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
441pub enum UnaryOp {
442    Neg,
443    Not,
444}
445
446/// SQL data type for schema resolution.
447#[derive(Debug, Clone, PartialEq, Eq)]
448pub enum SqlDataType {
449    Int64,
450    Float64,
451    String,
452    Bool,
453    Bytes,
454    Timestamp,
455    Decimal,
456    Uuid,
457    Vector(usize),
458    Geometry,
459}
460
461// ── Catalog trait ──
462
463/// Trait for looking up collection metadata during planning.
464///
465/// Both Origin (via CredentialStore) and Lite (via redb catalog)
466/// implement this trait.
467pub trait SqlCatalog {
468    fn get_collection(&self, name: &str) -> Option<CollectionInfo>;
469}
470
471/// Metadata about a collection for query planning.
472#[derive(Debug, Clone)]
473pub struct CollectionInfo {
474    pub name: String,
475    pub engine: EngineType,
476    pub columns: Vec<ColumnInfo>,
477    pub primary_key: Option<String>,
478    pub has_auto_tier: bool,
479}
480
481/// Metadata about a single column.
482#[derive(Debug, Clone)]
483pub struct ColumnInfo {
484    pub name: String,
485    pub data_type: SqlDataType,
486    pub nullable: bool,
487    pub is_primary_key: bool,
488    /// Default value expression (e.g. "UUID_V7", "ULID", "NANOID(10)", "0", "'active'").
489    pub default: Option<String>,
490}