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