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}
46
47/// `drop User`
48#[derive(Debug, Clone, PartialEq)]
49pub struct DropTableExpr {
50    pub table: String,
51}
52
53/// `create [materialized] view ActiveUsers as User filter .active = true`
54#[derive(Debug, Clone, PartialEq)]
55pub struct CreateViewExpr {
56    pub name: String,
57    pub query: QueryExpr,
58    /// The original source query text, stored for re-execution on refresh.
59    pub query_text: String,
60}
61
62/// `refresh ActiveUsers`
63#[derive(Debug, Clone, PartialEq)]
64pub struct RefreshViewExpr {
65    pub name: String,
66}
67
68/// `drop view ActiveUsers`
69#[derive(Debug, Clone, PartialEq)]
70pub struct DropViewExpr {
71    pub name: String,
72}
73
74/// `User filter .age > 30 union User filter .status = "vip"`
75#[derive(Debug, Clone, PartialEq)]
76pub struct UnionExpr {
77    pub left: Box<Statement>,
78    pub right: Box<Statement>,
79    /// `true` for `union all` (keep duplicates), `false` for `union` (deduplicate).
80    pub all: bool,
81}
82
83/// A query expression: Type [join ...]* [filter ...] [order ...] [limit ...] [{ projection }]
84#[derive(Debug, Clone, PartialEq)]
85pub struct QueryExpr {
86    pub source: String,
87    /// Optional alias for the primary source (e.g. `User as u`). Used to
88    /// disambiguate qualified column references in join queries. `None` for
89    /// single-table queries.
90    pub alias: Option<String>,
91    /// Zero or more join clauses chained to the primary source. For a
92    /// single-table query this is always empty so existing code paths are
93    /// untouched.
94    pub joins: Vec<JoinClause>,
95    pub filter: Option<Expr>,
96    pub order: Option<OrderClause>,
97    pub limit: Option<Expr>,
98    pub offset: Option<Expr>,
99    pub projection: Option<Vec<ProjectionField>>,
100    pub aggregation: Option<AggregateExpr>,
101    pub distinct: bool,
102    pub group_by: Option<GroupByClause>,
103}
104
105/// GROUP BY clause: `group .field1, .field2 [having <expr>]`.
106#[derive(Debug, Clone, PartialEq)]
107pub struct GroupByClause {
108    pub keys: Vec<String>,
109    pub having: Option<Expr>,
110}
111
112/// A join clause appended to a query's primary source.
113///
114/// Example syntax (Mission E1.1 parser accepts this; executor still errors):
115///   `User as u inner join Order as o on u.id = o.user_id filter o.total > 100`
116#[derive(Debug, Clone, PartialEq)]
117pub struct JoinClause {
118    pub kind: JoinKind,
119    pub source: String,
120    pub alias: Option<String>,
121    /// `on <expr>` — required for every kind except `Cross`.
122    pub on: Option<Expr>,
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum JoinKind {
127    Inner,
128    LeftOuter,
129    RightOuter,
130    Cross,
131}
132
133#[derive(Debug, Clone, PartialEq)]
134pub struct ProjectionField {
135    pub alias: Option<String>,
136    pub expr: Expr,
137}
138
139#[derive(Debug, Clone, PartialEq)]
140pub struct OrderClause {
141    pub keys: Vec<OrderKey>,
142}
143
144#[derive(Debug, Clone, PartialEq)]
145pub struct OrderKey {
146    pub field: String,
147    pub descending: bool,
148}
149
150#[derive(Debug, Clone, PartialEq)]
151pub struct InsertExpr {
152    pub target: String,
153    pub assignments: Vec<Assignment>,
154}
155
156#[derive(Debug, Clone, PartialEq)]
157pub struct UpdateExpr {
158    pub source: String,
159    pub filter: Option<Expr>,
160    pub assignments: Vec<Assignment>,
161}
162
163#[derive(Debug, Clone, PartialEq)]
164pub struct DeleteExpr {
165    pub source: String,
166    pub filter: Option<Expr>,
167}
168
169#[derive(Debug, Clone, PartialEq)]
170pub struct Assignment {
171    pub field: String,
172    pub value: Expr,
173}
174
175#[derive(Debug, Clone, PartialEq)]
176/// `upsert User on .id { id := 1, name := "Alice" } [on conflict { name := "Alice" }]`
177pub struct UpsertExpr {
178    pub target: String,
179    pub key_column: String,
180    pub assignments: Vec<Assignment>,
181    /// Assignments to apply on conflict. If empty, all non-key assignments
182    /// from `assignments` are used as the update set.
183    pub on_conflict: Vec<Assignment>,
184}
185
186#[derive(Debug, Clone, PartialEq)]
187pub struct CreateTypeExpr {
188    pub name: String,
189    pub fields: Vec<FieldDef>,
190}
191
192#[derive(Debug, Clone, PartialEq)]
193pub struct FieldDef {
194    pub name: String,
195    pub type_name: String,
196    pub required: bool,
197}
198
199#[derive(Debug, Clone, PartialEq)]
200pub struct AggregateExpr {
201    pub function: AggFunc,
202    pub field: Option<String>,
203}
204
205#[derive(Debug, Clone, Copy, PartialEq)]
206pub enum AggFunc {
207    Count,
208    CountDistinct,
209    Avg,
210    Sum,
211    Min,
212    Max,
213}
214
215/// Window function identifier.
216#[derive(Debug, Clone, Copy, PartialEq)]
217pub enum WindowFunc {
218    RowNumber,
219    Rank,
220    DenseRank,
221    Sum,
222    Avg,
223    Count,
224    Min,
225    Max,
226}
227
228/// Scalar (non-aggregate) function — operates on single values.
229#[derive(Debug, Clone, Copy, PartialEq)]
230pub enum ScalarFn {
231    Upper,
232    Lower,
233    Length,
234    Trim,
235    Substring, // substring(expr, start, len) — 1-indexed
236    Concat,    // concat(expr, expr, ...) — variadic
237    // Math
238    Abs,
239    Round, // round(expr) or round(expr, decimals)
240    Ceil,
241    Floor,
242    Sqrt,
243    Pow, // pow(base, exponent)
244    // Date/time
245    Now,      // now() — returns current unix timestamp in microseconds
246    Extract,  // extract("year"|"month"|..., datetime_expr)
247    DateAdd,  // date_add(datetime_expr, amount, "unit")
248    DateDiff, // date_diff(dt1, dt2, "unit")
249}
250
251/// Target type for CAST expressions.
252#[derive(Debug, Clone, Copy, PartialEq)]
253pub enum CastType {
254    Int,
255    Float,
256    Str,
257    Bool,
258    DateTime,
259}
260
261/// Expressions.
262#[derive(Debug, Clone, PartialEq)]
263pub enum Expr {
264    Field(String),
265    /// A table-qualified field reference: `table.field` or `alias.field`.
266    /// Used by join queries to disambiguate columns that appear in multiple
267    /// sources. The single-table read path never emits this variant, so
268    /// existing fast paths keep matching `Expr::Field` unchanged.
269    QualifiedField {
270        qualifier: String,
271        field: String,
272    },
273    Literal(Literal),
274    Param(String),
275    BinaryOp(Box<Expr>, BinOp, Box<Expr>),
276    UnaryOp(UnaryOp, Box<Expr>),
277    FunctionCall(AggFunc, Box<Expr>),
278    /// Scalar (non-aggregate) function call.
279    ScalarFunc(ScalarFn, Vec<Expr>),
280    Coalesce(Box<Expr>, Box<Expr>),
281    /// `expr in (val1, val2, ...)` or `expr not in (val1, val2, ...)`
282    InList {
283        expr: Box<Expr>,
284        list: Vec<Expr>,
285        negated: bool,
286    },
287    /// `expr [not] in (subquery)` — the subquery is a full QueryExpr
288    /// that produces a single column.
289    InSubquery {
290        expr: Box<Expr>,
291        subquery: Box<QueryExpr>,
292        negated: bool,
293    },
294    /// `[not] exists (subquery)` — the subquery is a full QueryExpr.
295    /// Currently uncorrelated only: the executor runs the subquery once
296    /// before the scan loop and replaces this node with a Bool literal.
297    ExistsSubquery {
298        subquery: Box<QueryExpr>,
299        negated: bool,
300    },
301    /// CASE WHEN ... THEN ... [ELSE ...] END
302    Case {
303        whens: Vec<(Box<Expr>, Box<Expr>)>,
304        else_expr: Option<Box<Expr>>,
305    },
306    /// Window function: `func(args) over (partition ... order ...)`
307    Window {
308        function: WindowFunc,
309        args: Vec<Expr>,
310        partition_by: Vec<String>,
311        order_by: Vec<OrderKey>,
312    },
313    /// Type cast: `cast(expr, "int")` or `cast(expr, "str")` etc.
314    Cast(Box<Expr>, CastType),
315    /// The `null` literal — produces `Value::Empty`.
316    Null,
317}
318
319#[derive(Debug, Clone, PartialEq)]
320pub enum Literal {
321    Int(i64),
322    Float(f64),
323    String(String),
324    Bool(bool),
325}
326
327#[derive(Debug, Clone, Copy, PartialEq)]
328pub enum BinOp {
329    Eq,
330    Neq,
331    Lt,
332    Gt,
333    Lte,
334    Gte,
335    And,
336    Or,
337    Add,
338    Sub,
339    Mul,
340    Div,
341    Like,
342}
343
344#[derive(Debug, Clone, Copy, PartialEq)]
345pub enum UnaryOp {
346    Not,
347    Exists,
348    NotExists,
349    IsNull,
350    IsNotNull,
351}