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, alias.field2 [having <expr>]`.
129#[derive(Debug, Clone, PartialEq)]
130pub struct GroupByClause {
131 pub keys: Vec<GroupKey>,
132 pub having: Option<Expr>,
133}
134
135/// A single GROUP BY key.
136///
137/// `Unqualified` covers the classic single-table form `group .status`, whose
138/// field name is resolved against the input columns by an exact match first
139/// and a unique `.field` suffix match second (so it also works over a join
140/// whose output columns are named `alias.field`).
141///
142/// `Qualified` covers the join form `group u.status`; it resolves exactly to
143/// the `alias.field` output column and never suffix-matches.
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub enum GroupKey {
146 Unqualified(String),
147 Qualified { alias: String, field: String },
148}
149
150impl GroupKey {
151 /// Name of the output column this key produces. Unqualified keys keep
152 /// their bare field name; qualified keys are emitted as `alias.field` so
153 /// HAVING and downstream projections can reference them consistently.
154 pub fn output_name(&self) -> String {
155 match self {
156 GroupKey::Unqualified(field) => field.clone(),
157 GroupKey::Qualified { alias, field } => format!("{alias}.{field}"),
158 }
159 }
160}
161
162/// A join clause appended to a query's primary source.
163///
164/// Example syntax (Mission E1.1 parser accepts this; executor still errors):
165/// `User as u inner join Order as o on u.id = o.user_id filter o.total > 100`
166#[derive(Debug, Clone, PartialEq)]
167pub struct JoinClause {
168 pub kind: JoinKind,
169 pub source: String,
170 pub alias: Option<String>,
171 /// `on <expr>` — required for every kind except `Cross`.
172 pub on: Option<Expr>,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum JoinKind {
177 Inner,
178 LeftOuter,
179 RightOuter,
180 Cross,
181}
182
183#[derive(Debug, Clone, PartialEq)]
184pub struct ProjectionField {
185 pub alias: Option<String>,
186 pub expr: Expr,
187}
188
189#[derive(Debug, Clone, PartialEq)]
190pub struct OrderClause {
191 pub keys: Vec<OrderKey>,
192}
193
194#[derive(Debug, Clone, PartialEq)]
195pub struct OrderKey {
196 pub field: String,
197 pub descending: bool,
198}
199
200#[derive(Debug, Clone, PartialEq)]
201pub struct InsertExpr {
202 pub target: String,
203 /// One assignment-block per row. Always contains at least one row;
204 /// `insert T { .. }` yields one, `insert T { .. }, { .. }` yields many.
205 pub rows: Vec<Vec<Assignment>>,
206 /// `true` when the statement ends with `returning` — the executor returns
207 /// the inserted rows (all columns) instead of a modified-count.
208 pub returning: bool,
209}
210
211#[derive(Debug, Clone, PartialEq)]
212pub struct UpdateExpr {
213 pub source: String,
214 pub filter: Option<Expr>,
215 pub assignments: Vec<Assignment>,
216 /// `true` when the statement ends with `returning` — the executor returns
217 /// the post-update rows (all columns) instead of a modified-count.
218 pub returning: bool,
219}
220
221#[derive(Debug, Clone, PartialEq)]
222pub struct DeleteExpr {
223 pub source: String,
224 pub filter: Option<Expr>,
225 /// `true` when the statement ends with `returning` — the executor returns
226 /// the pre-delete rows (all columns) instead of a modified-count.
227 pub returning: bool,
228}
229
230#[derive(Debug, Clone, PartialEq)]
231pub struct Assignment {
232 pub field: String,
233 pub value: Expr,
234}
235
236#[derive(Debug, Clone, PartialEq)]
237/// `upsert User on .id { id := 1, name := "Alice" } [on conflict { name := "Alice" }]`
238pub struct UpsertExpr {
239 pub target: String,
240 pub key_column: String,
241 pub assignments: Vec<Assignment>,
242 /// Assignments to apply on conflict. If empty, all non-key assignments
243 /// from `assignments` are used as the update set.
244 pub on_conflict: Vec<Assignment>,
245}
246
247#[derive(Debug, Clone, PartialEq)]
248pub struct CreateTypeExpr {
249 pub name: String,
250 pub fields: Vec<FieldDef>,
251 /// `type X if not exists { ... }` — re-declaring an existing type is a
252 /// no-op instead of an error.
253 pub if_not_exists: bool,
254}
255
256#[derive(Debug, Clone, PartialEq)]
257pub struct FieldDef {
258 pub name: String,
259 pub type_name: String,
260 pub required: bool,
261 /// `true` when declared with the `unique` modifier — auto-creates a
262 /// unique B+Tree index on this column at table-create time.
263 pub unique: bool,
264 /// Literal default applied when an insert omits this column. `None` means
265 /// the column has no default (omitting it yields the empty set / null).
266 pub default: Option<Literal>,
267 /// `true` when declared `auto` — an integer column whose value is assigned
268 /// from a monotonic per-table sequence when an insert omits it.
269 pub auto: bool,
270}
271
272#[derive(Debug, Clone, PartialEq)]
273pub struct AggregateExpr {
274 pub function: AggFunc,
275 pub field: Option<String>,
276}
277
278#[derive(Debug, Clone, Copy, PartialEq)]
279pub enum AggFunc {
280 Count,
281 CountDistinct,
282 Avg,
283 Sum,
284 Min,
285 Max,
286}
287
288/// Window function identifier.
289#[derive(Debug, Clone, Copy, PartialEq)]
290pub enum WindowFunc {
291 RowNumber,
292 Rank,
293 DenseRank,
294 Sum,
295 Avg,
296 Count,
297 Min,
298 Max,
299}
300
301/// Scalar (non-aggregate) function — operates on single values.
302#[derive(Debug, Clone, Copy, PartialEq)]
303pub enum ScalarFn {
304 Upper,
305 Lower,
306 Length,
307 Trim,
308 Substring, // substring(expr, start, len) — 1-indexed
309 Concat, // concat(expr, expr, ...) — variadic
310 // Math
311 Abs,
312 Round, // round(expr) or round(expr, decimals)
313 Ceil,
314 Floor,
315 Sqrt,
316 Pow, // pow(base, exponent)
317 // Date/time
318 Now, // now() — returns current unix timestamp in microseconds
319 Extract, // extract("year"|"month"|..., datetime_expr)
320 DateAdd, // date_add(datetime_expr, amount, "unit")
321 DateDiff, // date_diff(dt1, dt2, "unit")
322}
323
324/// Target type for CAST expressions.
325#[derive(Debug, Clone, Copy, PartialEq)]
326pub enum CastType {
327 Int,
328 Float,
329 Str,
330 Bool,
331 DateTime,
332 Uuid,
333 Bytes,
334}
335
336/// Expressions.
337#[derive(Debug, Clone, PartialEq)]
338pub enum Expr {
339 Field(String),
340 /// A table-qualified field reference: `table.field` or `alias.field`.
341 /// Used by join queries to disambiguate columns that appear in multiple
342 /// sources. The single-table read path never emits this variant, so
343 /// existing fast paths keep matching `Expr::Field` unchanged.
344 QualifiedField {
345 qualifier: String,
346 field: String,
347 },
348 Literal(Literal),
349 Param(String),
350 BinaryOp(Box<Expr>, BinOp, Box<Expr>),
351 UnaryOp(UnaryOp, Box<Expr>),
352 FunctionCall(AggFunc, Box<Expr>),
353 /// Scalar (non-aggregate) function call.
354 ScalarFunc(ScalarFn, Vec<Expr>),
355 Coalesce(Box<Expr>, Box<Expr>),
356 /// `expr in (val1, val2, ...)` or `expr not in (val1, val2, ...)`
357 InList {
358 expr: Box<Expr>,
359 list: Vec<Expr>,
360 negated: bool,
361 },
362 /// `expr [not] in (subquery)` — the subquery is a full QueryExpr
363 /// that produces a single column.
364 InSubquery {
365 expr: Box<Expr>,
366 subquery: Box<QueryExpr>,
367 negated: bool,
368 },
369 /// `[not] exists (subquery)` — the subquery is a full QueryExpr.
370 /// Currently uncorrelated only: the executor runs the subquery once
371 /// before the scan loop and replaces this node with a Bool literal.
372 ExistsSubquery {
373 subquery: Box<QueryExpr>,
374 negated: bool,
375 },
376 /// CASE WHEN ... THEN ... [ELSE ...] END
377 Case {
378 whens: Vec<(Box<Expr>, Box<Expr>)>,
379 else_expr: Option<Box<Expr>>,
380 },
381 /// Window function: `func(args) over (partition ... order ...)`
382 Window {
383 function: WindowFunc,
384 args: Vec<Expr>,
385 partition_by: Vec<String>,
386 order_by: Vec<OrderKey>,
387 },
388 /// Type cast: `cast(expr, "int")` or `cast(expr, "str")` etc.
389 Cast(Box<Expr>, CastType),
390 /// A runtime-materialized literal carrying a concrete Value. Produced only
391 /// during subquery/correlated substitution (post-planning) for values that
392 /// have no Literal form (NULL, datetime, uuid, bytes); never emitted by the
393 /// parser/canonicalizer.
394 ValueLit(Value),
395 /// The `null` literal — produces `Value::Empty`.
396 Null,
397}
398
399#[derive(Debug, Clone, PartialEq)]
400pub enum Literal {
401 Int(i64),
402 Float(f64),
403 String(String),
404 Bool(bool),
405}
406
407/// A bound value supplied for a `$N` placeholder in
408/// [`crate::parser::parse_with_params`].
409///
410/// Unlike [`Literal`], this carries a `Null` variant so a parameter can
411/// bind PowQL `null` (substituted as `Token::Null`, not a string). Values
412/// are turned into literal *tokens* before parsing, so an injection-shaped
413/// string is inert data — it can never change the query's shape.
414#[derive(Debug, Clone, PartialEq)]
415pub enum ParamValue {
416 Null,
417 Int(i64),
418 Float(f64),
419 Bool(bool),
420 Str(String),
421}
422
423#[derive(Debug, Clone, Copy, PartialEq)]
424pub enum BinOp {
425 Eq,
426 Neq,
427 Lt,
428 Gt,
429 Lte,
430 Gte,
431 And,
432 Or,
433 Add,
434 Sub,
435 Mul,
436 Div,
437 Like,
438}
439
440#[derive(Debug, Clone, Copy, PartialEq)]
441pub enum UnaryOp {
442 Not,
443 Exists,
444 NotExists,
445 IsNull,
446 IsNotNull,
447}