Skip to main content

powdb_query/
ast.rs

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