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    /// One assignment-block per row. Always contains at least one row;
154    /// `insert T { .. }` yields one, `insert T { .. }, { .. }` yields many.
155    pub rows: Vec<Vec<Assignment>>,
156}
157
158#[derive(Debug, Clone, PartialEq)]
159pub struct UpdateExpr {
160    pub source: String,
161    pub filter: Option<Expr>,
162    pub assignments: Vec<Assignment>,
163}
164
165#[derive(Debug, Clone, PartialEq)]
166pub struct DeleteExpr {
167    pub source: String,
168    pub filter: Option<Expr>,
169}
170
171#[derive(Debug, Clone, PartialEq)]
172pub struct Assignment {
173    pub field: String,
174    pub value: Expr,
175}
176
177#[derive(Debug, Clone, PartialEq)]
178/// `upsert User on .id { id := 1, name := "Alice" } [on conflict { name := "Alice" }]`
179pub struct UpsertExpr {
180    pub target: String,
181    pub key_column: String,
182    pub assignments: Vec<Assignment>,
183    /// Assignments to apply on conflict. If empty, all non-key assignments
184    /// from `assignments` are used as the update set.
185    pub on_conflict: Vec<Assignment>,
186}
187
188#[derive(Debug, Clone, PartialEq)]
189pub struct CreateTypeExpr {
190    pub name: String,
191    pub fields: Vec<FieldDef>,
192}
193
194#[derive(Debug, Clone, PartialEq)]
195pub struct FieldDef {
196    pub name: String,
197    pub type_name: String,
198    pub required: bool,
199}
200
201#[derive(Debug, Clone, PartialEq)]
202pub struct AggregateExpr {
203    pub function: AggFunc,
204    pub field: Option<String>,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq)]
208pub enum AggFunc {
209    Count,
210    CountDistinct,
211    Avg,
212    Sum,
213    Min,
214    Max,
215}
216
217/// Window function identifier.
218#[derive(Debug, Clone, Copy, PartialEq)]
219pub enum WindowFunc {
220    RowNumber,
221    Rank,
222    DenseRank,
223    Sum,
224    Avg,
225    Count,
226    Min,
227    Max,
228}
229
230/// Scalar (non-aggregate) function — operates on single values.
231#[derive(Debug, Clone, Copy, PartialEq)]
232pub enum ScalarFn {
233    Upper,
234    Lower,
235    Length,
236    Trim,
237    Substring, // substring(expr, start, len) — 1-indexed
238    Concat,    // concat(expr, expr, ...) — variadic
239    // Math
240    Abs,
241    Round, // round(expr) or round(expr, decimals)
242    Ceil,
243    Floor,
244    Sqrt,
245    Pow, // pow(base, exponent)
246    // Date/time
247    Now,      // now() — returns current unix timestamp in microseconds
248    Extract,  // extract("year"|"month"|..., datetime_expr)
249    DateAdd,  // date_add(datetime_expr, amount, "unit")
250    DateDiff, // date_diff(dt1, dt2, "unit")
251}
252
253/// Target type for CAST expressions.
254#[derive(Debug, Clone, Copy, PartialEq)]
255pub enum CastType {
256    Int,
257    Float,
258    Str,
259    Bool,
260    DateTime,
261}
262
263/// Expressions.
264#[derive(Debug, Clone, PartialEq)]
265pub enum Expr {
266    Field(String),
267    /// A table-qualified field reference: `table.field` or `alias.field`.
268    /// Used by join queries to disambiguate columns that appear in multiple
269    /// sources. The single-table read path never emits this variant, so
270    /// existing fast paths keep matching `Expr::Field` unchanged.
271    QualifiedField {
272        qualifier: String,
273        field: String,
274    },
275    Literal(Literal),
276    Param(String),
277    BinaryOp(Box<Expr>, BinOp, Box<Expr>),
278    UnaryOp(UnaryOp, Box<Expr>),
279    FunctionCall(AggFunc, Box<Expr>),
280    /// Scalar (non-aggregate) function call.
281    ScalarFunc(ScalarFn, Vec<Expr>),
282    Coalesce(Box<Expr>, Box<Expr>),
283    /// `expr in (val1, val2, ...)` or `expr not in (val1, val2, ...)`
284    InList {
285        expr: Box<Expr>,
286        list: Vec<Expr>,
287        negated: bool,
288    },
289    /// `expr [not] in (subquery)` — the subquery is a full QueryExpr
290    /// that produces a single column.
291    InSubquery {
292        expr: Box<Expr>,
293        subquery: Box<QueryExpr>,
294        negated: bool,
295    },
296    /// `[not] exists (subquery)` — the subquery is a full QueryExpr.
297    /// Currently uncorrelated only: the executor runs the subquery once
298    /// before the scan loop and replaces this node with a Bool literal.
299    ExistsSubquery {
300        subquery: Box<QueryExpr>,
301        negated: bool,
302    },
303    /// CASE WHEN ... THEN ... [ELSE ...] END
304    Case {
305        whens: Vec<(Box<Expr>, Box<Expr>)>,
306        else_expr: Option<Box<Expr>>,
307    },
308    /// Window function: `func(args) over (partition ... order ...)`
309    Window {
310        function: WindowFunc,
311        args: Vec<Expr>,
312        partition_by: Vec<String>,
313        order_by: Vec<OrderKey>,
314    },
315    /// Type cast: `cast(expr, "int")` or `cast(expr, "str")` etc.
316    Cast(Box<Expr>, CastType),
317    /// The `null` literal — produces `Value::Empty`.
318    Null,
319}
320
321#[derive(Debug, Clone, PartialEq)]
322pub enum Literal {
323    Int(i64),
324    Float(f64),
325    String(String),
326    Bool(bool),
327}
328
329#[derive(Debug, Clone, Copy, PartialEq)]
330pub enum BinOp {
331    Eq,
332    Neq,
333    Lt,
334    Gt,
335    Lte,
336    Gte,
337    And,
338    Or,
339    Add,
340    Sub,
341    Mul,
342    Div,
343    Like,
344}
345
346#[derive(Debug, Clone, Copy, PartialEq)]
347pub enum UnaryOp {
348    Not,
349    Exists,
350    NotExists,
351    IsNull,
352    IsNotNull,
353}