Skip to main content

powdb_query/
ast.rs

1use powdb_storage::types::Value;
2
3/// Top-level PowQL statement.
4#[derive(Debug, Clone, PartialEq)]
5pub enum Statement {
6    Query(QueryExpr),
7    Insert(InsertExpr),
8    UpdateQuery(UpdateExpr),
9    DeleteQuery(DeleteExpr),
10    CreateType(CreateTypeExpr),
11    AlterTable(AlterTableExpr),
12    DropTable(DropTableExpr),
13    CreateView(CreateViewExpr),
14    RefreshView(RefreshViewExpr),
15    DropView(DropViewExpr),
16    Union(UnionExpr),
17    Upsert(UpsertExpr),
18    Explain(Box<Statement>),
19    Begin,
20    Commit,
21    Rollback,
22    /// `schema` — introspection: list every type (table) in the catalog.
23    ListTypes,
24    /// `describe <Type>` / `schema <Type>` — introspection: the columns and
25    /// indexes of one type.
26    Describe(String),
27}
28
29/// `alter User add column status: str` / `alter User drop column status`
30#[derive(Debug, Clone, PartialEq)]
31pub struct AlterTableExpr {
32    pub table: String,
33    pub action: AlterAction,
34}
35
36/// An individual ALTER TABLE action.
37#[derive(Debug, Clone, PartialEq)]
38pub enum AlterAction {
39    AddColumn {
40        name: String,
41        type_name: String,
42        required: bool,
43    },
44    DropColumn {
45        name: String,
46        /// `drop column if exists` — a missing column is a no-op instead of
47        /// an error.
48        if_exists: bool,
49    },
50    /// `alter <Table> add index [if not exists] .<column>` — creates a
51    /// B+Tree index on `column`. No-op if the index already exists.
52    AddIndex {
53        column: String,
54        /// Parsed for symmetry with the other DDL; `add index` is already
55        /// idempotent, so this does not change behavior.
56        if_not_exists: bool,
57    },
58    /// `alter <Table> add unique [if not exists] .<column>` — creates a
59    /// UNIQUE B+Tree index on `column`. Scans existing data first and fails
60    /// if any duplicate (non-null) value is present. Without `if not exists`
61    /// it errors when the column is already indexed (no in-place upgrade);
62    /// with it, an existing index is a no-op.
63    AddUnique { column: String, if_not_exists: bool },
64}
65
66/// `drop [if exists] User`
67#[derive(Debug, Clone, PartialEq)]
68pub struct DropTableExpr {
69    pub table: String,
70    /// `drop if exists` — a missing table is a no-op instead of an error.
71    pub if_exists: bool,
72}
73
74/// `create [materialized] view ActiveUsers as User filter .active = true`
75#[derive(Debug, Clone, PartialEq)]
76pub struct CreateViewExpr {
77    pub name: String,
78    pub query: QueryExpr,
79    /// The original source query text, stored for re-execution on refresh.
80    pub query_text: String,
81}
82
83/// `refresh ActiveUsers`
84#[derive(Debug, Clone, PartialEq)]
85pub struct RefreshViewExpr {
86    pub name: String,
87}
88
89/// `drop view [if exists] ActiveUsers`
90#[derive(Debug, Clone, PartialEq)]
91pub struct DropViewExpr {
92    pub name: String,
93    /// `drop view if exists` — a missing view is a no-op instead of an error.
94    pub if_exists: bool,
95}
96
97/// `User filter .age > 30 union User filter .status = "vip"`
98#[derive(Debug, Clone, PartialEq)]
99pub struct UnionExpr {
100    pub left: Box<Statement>,
101    pub right: Box<Statement>,
102    /// `true` for `union all` (keep duplicates), `false` for `union` (deduplicate).
103    pub all: bool,
104}
105
106/// A query expression: Type [join ...]* [filter ...] [order ...] [limit ...] [{ projection }]
107#[derive(Debug, Clone, PartialEq)]
108pub struct QueryExpr {
109    pub source: String,
110    /// Optional alias for the primary source (e.g. `User as u`). Used to
111    /// disambiguate qualified column references in join queries. `None` for
112    /// single-table queries.
113    pub alias: Option<String>,
114    /// Zero or more join clauses chained to the primary source. For a
115    /// single-table query this is always empty so existing code paths are
116    /// untouched.
117    pub joins: Vec<JoinClause>,
118    pub filter: Option<Expr>,
119    pub order: Option<OrderClause>,
120    pub limit: Option<Expr>,
121    pub offset: Option<Expr>,
122    pub projection: Option<Vec<ProjectionField>>,
123    pub aggregation: Option<AggregateExpr>,
124    pub distinct: bool,
125    pub group_by: Option<GroupByClause>,
126}
127
128/// GROUP BY clause: `group .field1, .field2 [having <expr>]`.
129#[derive(Debug, Clone, PartialEq)]
130pub struct GroupByClause {
131    pub keys: Vec<String>,
132    pub having: Option<Expr>,
133}
134
135/// A join clause appended to a query's primary source.
136///
137/// Example syntax (Mission E1.1 parser accepts this; executor still errors):
138///   `User as u inner join Order as o on u.id = o.user_id filter o.total > 100`
139#[derive(Debug, Clone, PartialEq)]
140pub struct JoinClause {
141    pub kind: JoinKind,
142    pub source: String,
143    pub alias: Option<String>,
144    /// `on <expr>` — required for every kind except `Cross`.
145    pub on: Option<Expr>,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum JoinKind {
150    Inner,
151    LeftOuter,
152    RightOuter,
153    Cross,
154}
155
156#[derive(Debug, Clone, PartialEq)]
157pub struct ProjectionField {
158    pub alias: Option<String>,
159    pub expr: Expr,
160}
161
162#[derive(Debug, Clone, PartialEq)]
163pub struct OrderClause {
164    pub keys: Vec<OrderKey>,
165}
166
167#[derive(Debug, Clone, PartialEq)]
168pub struct OrderKey {
169    pub field: String,
170    pub descending: bool,
171}
172
173#[derive(Debug, Clone, PartialEq)]
174pub struct InsertExpr {
175    pub target: String,
176    /// One assignment-block per row. Always contains at least one row;
177    /// `insert T { .. }` yields one, `insert T { .. }, { .. }` yields many.
178    pub rows: Vec<Vec<Assignment>>,
179    /// `true` when the statement ends with `returning` — the executor returns
180    /// the inserted rows (all columns) instead of a modified-count.
181    pub returning: bool,
182}
183
184#[derive(Debug, Clone, PartialEq)]
185pub struct UpdateExpr {
186    pub source: String,
187    pub filter: Option<Expr>,
188    pub assignments: Vec<Assignment>,
189    /// `true` when the statement ends with `returning` — the executor returns
190    /// the post-update rows (all columns) instead of a modified-count.
191    pub returning: bool,
192}
193
194#[derive(Debug, Clone, PartialEq)]
195pub struct DeleteExpr {
196    pub source: String,
197    pub filter: Option<Expr>,
198    /// `true` when the statement ends with `returning` — the executor returns
199    /// the pre-delete rows (all columns) instead of a modified-count.
200    pub returning: bool,
201}
202
203#[derive(Debug, Clone, PartialEq)]
204pub struct Assignment {
205    pub field: String,
206    pub value: Expr,
207}
208
209#[derive(Debug, Clone, PartialEq)]
210/// `upsert User on .id { id := 1, name := "Alice" } [on conflict { name := "Alice" }]`
211pub struct UpsertExpr {
212    pub target: String,
213    pub key_column: String,
214    pub assignments: Vec<Assignment>,
215    /// Assignments to apply on conflict. If empty, all non-key assignments
216    /// from `assignments` are used as the update set.
217    pub on_conflict: Vec<Assignment>,
218}
219
220#[derive(Debug, Clone, PartialEq)]
221pub struct CreateTypeExpr {
222    pub name: String,
223    pub fields: Vec<FieldDef>,
224    /// `type X if not exists { ... }` — re-declaring an existing type is a
225    /// no-op instead of an error.
226    pub if_not_exists: bool,
227}
228
229#[derive(Debug, Clone, PartialEq)]
230pub struct FieldDef {
231    pub name: String,
232    pub type_name: String,
233    pub required: bool,
234    /// `true` when declared with the `unique` modifier — auto-creates a
235    /// unique B+Tree index on this column at table-create time.
236    pub unique: bool,
237    /// Literal default applied when an insert omits this column. `None` means
238    /// the column has no default (omitting it yields the empty set / null).
239    pub default: Option<Literal>,
240    /// `true` when declared `auto` — an integer column whose value is assigned
241    /// from a monotonic per-table sequence when an insert omits it.
242    pub auto: bool,
243}
244
245#[derive(Debug, Clone, PartialEq)]
246pub struct AggregateExpr {
247    pub function: AggFunc,
248    pub field: Option<String>,
249}
250
251#[derive(Debug, Clone, Copy, PartialEq)]
252pub enum AggFunc {
253    Count,
254    CountDistinct,
255    Avg,
256    Sum,
257    Min,
258    Max,
259}
260
261/// Window function identifier.
262#[derive(Debug, Clone, Copy, PartialEq)]
263pub enum WindowFunc {
264    RowNumber,
265    Rank,
266    DenseRank,
267    Sum,
268    Avg,
269    Count,
270    Min,
271    Max,
272}
273
274/// Scalar (non-aggregate) function — operates on single values.
275#[derive(Debug, Clone, Copy, PartialEq)]
276pub enum ScalarFn {
277    Upper,
278    Lower,
279    Length,
280    Trim,
281    Substring, // substring(expr, start, len) — 1-indexed
282    Concat,    // concat(expr, expr, ...) — variadic
283    // Math
284    Abs,
285    Round, // round(expr) or round(expr, decimals)
286    Ceil,
287    Floor,
288    Sqrt,
289    Pow, // pow(base, exponent)
290    // Date/time
291    Now,      // now() — returns current unix timestamp in microseconds
292    Extract,  // extract("year"|"month"|..., datetime_expr)
293    DateAdd,  // date_add(datetime_expr, amount, "unit")
294    DateDiff, // date_diff(dt1, dt2, "unit")
295}
296
297/// Target type for CAST expressions.
298#[derive(Debug, Clone, Copy, PartialEq)]
299pub enum CastType {
300    Int,
301    Float,
302    Str,
303    Bool,
304    DateTime,
305    Uuid,
306    Bytes,
307}
308
309/// Expressions.
310#[derive(Debug, Clone, PartialEq)]
311pub enum Expr {
312    Field(String),
313    /// A table-qualified field reference: `table.field` or `alias.field`.
314    /// Used by join queries to disambiguate columns that appear in multiple
315    /// sources. The single-table read path never emits this variant, so
316    /// existing fast paths keep matching `Expr::Field` unchanged.
317    QualifiedField {
318        qualifier: String,
319        field: String,
320    },
321    Literal(Literal),
322    Param(String),
323    BinaryOp(Box<Expr>, BinOp, Box<Expr>),
324    UnaryOp(UnaryOp, Box<Expr>),
325    FunctionCall(AggFunc, Box<Expr>),
326    /// Scalar (non-aggregate) function call.
327    ScalarFunc(ScalarFn, Vec<Expr>),
328    Coalesce(Box<Expr>, Box<Expr>),
329    /// `expr in (val1, val2, ...)` or `expr not in (val1, val2, ...)`
330    InList {
331        expr: Box<Expr>,
332        list: Vec<Expr>,
333        negated: bool,
334    },
335    /// `expr [not] in (subquery)` — the subquery is a full QueryExpr
336    /// that produces a single column.
337    InSubquery {
338        expr: Box<Expr>,
339        subquery: Box<QueryExpr>,
340        negated: bool,
341    },
342    /// `[not] exists (subquery)` — the subquery is a full QueryExpr.
343    /// Currently uncorrelated only: the executor runs the subquery once
344    /// before the scan loop and replaces this node with a Bool literal.
345    ExistsSubquery {
346        subquery: Box<QueryExpr>,
347        negated: bool,
348    },
349    /// CASE WHEN ... THEN ... [ELSE ...] END
350    Case {
351        whens: Vec<(Box<Expr>, Box<Expr>)>,
352        else_expr: Option<Box<Expr>>,
353    },
354    /// Window function: `func(args) over (partition ... order ...)`
355    Window {
356        function: WindowFunc,
357        args: Vec<Expr>,
358        partition_by: Vec<String>,
359        order_by: Vec<OrderKey>,
360    },
361    /// Type cast: `cast(expr, "int")` or `cast(expr, "str")` etc.
362    Cast(Box<Expr>, CastType),
363    /// A runtime-materialized literal carrying a concrete Value. Produced only
364    /// during subquery/correlated substitution (post-planning) for values that
365    /// have no Literal form (NULL, datetime, uuid, bytes); never emitted by the
366    /// parser/canonicalizer.
367    ValueLit(Value),
368    /// The `null` literal — produces `Value::Empty`.
369    Null,
370}
371
372#[derive(Debug, Clone, PartialEq)]
373pub enum Literal {
374    Int(i64),
375    Float(f64),
376    String(String),
377    Bool(bool),
378}
379
380/// A bound value supplied for a `$N` placeholder in
381/// [`crate::parser::parse_with_params`].
382///
383/// Unlike [`Literal`], this carries a `Null` variant so a parameter can
384/// bind PowQL `null` (substituted as `Token::Null`, not a string). Values
385/// are turned into literal *tokens* before parsing, so an injection-shaped
386/// string is inert data — it can never change the query's shape.
387#[derive(Debug, Clone, PartialEq)]
388pub enum ParamValue {
389    Null,
390    Int(i64),
391    Float(f64),
392    Bool(bool),
393    Str(String),
394}
395
396#[derive(Debug, Clone, Copy, PartialEq)]
397pub enum BinOp {
398    Eq,
399    Neq,
400    Lt,
401    Gt,
402    Lte,
403    Gte,
404    And,
405    Or,
406    Add,
407    Sub,
408    Mul,
409    Div,
410    Like,
411}
412
413#[derive(Debug, Clone, Copy, PartialEq)]
414pub enum UnaryOp {
415    Not,
416    Exists,
417    NotExists,
418    IsNull,
419    IsNotNull,
420}