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}