Skip to main content

powdb_query/
plan.rs

1use crate::ast::{
2    AggFunc, AggregateMode, AlterAction, Assignment, Expr, GroupKey, JoinKind, Literal, WindowFunc,
3};
4use powdb_storage::stored_json_path::StoredJsonPathV1;
5
6/// A column definition carried by `PlanNode::CreateTable`. Replaces the
7/// old `(name, type_name, required)` tuple so the `unique` modifier can
8/// flow from the parser through to the executor's DDL arm.
9#[derive(Debug, Clone)]
10pub struct CreateField {
11    pub name: String,
12    pub type_name: String,
13    pub required: bool,
14    pub unique: bool,
15    /// Literal default applied when an insert omits this column.
16    pub default: Option<Literal>,
17    /// `true` when declared `auto` — integer column auto-assigned from a
18    /// per-table sequence when an insert omits it.
19    pub auto: bool,
20}
21
22/// Physical plan nodes — what the executor actually runs.
23#[derive(Debug, Clone)]
24pub enum PlanNode {
25    SeqScan {
26        table: String,
27    },
28    /// Mission E1.2: sequential scan that renames output columns to
29    /// `alias.field`. Used exclusively as the leaves of a join plan so
30    /// downstream `NestedLoopJoin` + `Filter` + `Project` nodes can resolve
31    /// `Expr::QualifiedField` lookups by direct column-name match. Kept
32    /// separate from `SeqScan` so the single-table fast paths (which match
33    /// on `PlanNode::SeqScan { .. }` in many places) stay untouched.
34    AliasScan {
35        table: String,
36        alias: String,
37    },
38    IndexScan {
39        table: String,
40        column: String,
41        key: Expr,
42    },
43    /// B+tree range scan: returns rows where the indexed column falls within
44    /// the given bounds. Generated by the planner when it detects inequality
45    /// predicates (>, >=, <, <=, BETWEEN) on an indexed column. The executor
46    /// falls back to SeqScan+Filter if no index exists on the column.
47    RangeScan {
48        table: String,
49        column: String,
50        /// Lower bound: (expr, inclusive). None = unbounded below.
51        start: Option<(Expr, bool)>,
52        /// Upper bound: (expr, inclusive). None = unbounded above.
53        end: Option<(Expr, bool)>,
54    },
55    /// Speculative equality lookup through an expression index. The planner is
56    /// catalog-pure; the executor resolves `path` at runtime and reconstructs
57    /// the equivalent path predicate if no matching index exists.
58    ExprIndexScan {
59        table: String,
60        path: StoredJsonPathV1,
61        key: Expr,
62    },
63    /// Speculative bounded lookup through an expression index. `path` is the
64    /// persisted table-local identity, never a runtime catalog identifier.
65    ExprRangeScan {
66        table: String,
67        path: StoredJsonPathV1,
68        start: Option<(Expr, bool)>,
69        end: Option<(Expr, bool)>,
70    },
71    /// Exact single-path ORDER BY + LIMIT/OFFSET candidate. Missing or
72    /// incompatible runtime indexes fall back to a normal scan/sort/slice.
73    OrderedExprIndexScan {
74        table: String,
75        path: StoredJsonPathV1,
76        descending: bool,
77        limit: Expr,
78        offset: Option<Expr>,
79    },
80    Filter {
81        input: Box<PlanNode>,
82        predicate: Expr,
83    },
84    Project {
85        input: Box<PlanNode>,
86        fields: Vec<ProjectField>,
87    },
88    Sort {
89        input: Box<PlanNode>,
90        keys: Vec<SortKey>,
91    },
92    Limit {
93        input: Box<PlanNode>,
94        count: Expr,
95    },
96    Offset {
97        input: Box<PlanNode>,
98        count: Expr,
99    },
100    Aggregate {
101        input: Box<PlanNode>,
102        function: AggFunc,
103        argument: Option<Expr>,
104        mode: AggregateMode,
105        /// Source alias whose physical row identity controls symmetric
106        /// de-duplication. `None` means raw aggregation semantics.
107        provenance_alias: Option<String>,
108    },
109    /// Mission E1.2: nested-loop join. Correctness-first implementation —
110    /// O(L × R) scan for every join. E1.3 will add a hash-join fast path
111    /// for equijoins (the common case). The executor handles `Inner`,
112    /// `Cross`, and `LeftOuter`; `RightOuter` is rewritten by the planner
113    /// into a `LeftOuter` with swapped inputs.
114    NestedLoopJoin {
115        left: Box<PlanNode>,
116        right: Box<PlanNode>,
117        /// Join predicate. `None` for `Cross` joins (emit every pair).
118        on: Option<Expr>,
119        kind: JoinKind,
120    },
121    Distinct {
122        input: Box<PlanNode>,
123    },
124    /// Mission E2b: grouped aggregation. Output columns are
125    /// `keys ++ [agg.output_name for agg in aggregates]`. The optional
126    /// `having` predicate is evaluated against each output row *after*
127    /// aggregation — it can reference both key columns and aggregate
128    /// output names (the planner rewrites `FunctionCall` nodes in the
129    /// HAVING expression into `Field("__agg_N")` references).
130    GroupBy {
131        input: Box<PlanNode>,
132        keys: Vec<GroupKey>,
133        aggregates: Vec<GroupAgg>,
134        having: Option<Expr>,
135    },
136    AlterTable {
137        table: String,
138        action: AlterAction,
139    },
140    DropTable {
141        name: String,
142        /// `drop if exists` — a missing table is a no-op instead of an error.
143        if_exists: bool,
144    },
145    Insert {
146        table: String,
147        /// One assignment-block per row to insert. Always at least one.
148        rows: Vec<Vec<Assignment>>,
149        /// `true` when `returning` was requested — executor returns the
150        /// inserted rows instead of a modified-count.
151        returning: bool,
152    },
153    /// UPSERT: probe index on `key_column` — if miss, insert; if hit, update.
154    Upsert {
155        table: String,
156        key_column: String,
157        assignments: Vec<Assignment>,
158        on_conflict: Vec<Assignment>,
159    },
160    Update {
161        input: Box<PlanNode>,
162        table: String,
163        assignments: Vec<Assignment>,
164        /// `true` when `returning` was requested — executor returns the
165        /// post-update rows instead of a modified-count.
166        returning: bool,
167    },
168    Delete {
169        input: Box<PlanNode>,
170        table: String,
171        /// `true` when `returning` was requested — executor returns the
172        /// pre-delete rows instead of a modified-count.
173        returning: bool,
174    },
175    CreateTable {
176        name: String,
177        fields: Vec<CreateField>,
178        /// `type X if not exists { ... }` — re-declaring an existing type is
179        /// a no-op instead of an error.
180        if_not_exists: bool,
181    },
182    /// `schema` — list every type (table) in the catalog. Read-only; reads
183    /// live catalog state at execution time, so a cached plan is never stale.
184    ListTypes,
185    /// `describe <Type>` — the columns and indexes of one type. Read-only.
186    Describe {
187        table: String,
188    },
189    /// Create a materialized view: execute query, store results, register.
190    CreateView {
191        name: String,
192        query_text: String,
193    },
194    /// Explicitly refresh a materialized view.
195    RefreshView {
196        name: String,
197    },
198    /// Drop a materialized view (backing table + registry entry).
199    DropView {
200        name: String,
201        /// `drop view if exists` — a missing view is a no-op instead of an error.
202        if_exists: bool,
203    },
204    /// Window function computation layer.
205    Window {
206        input: Box<PlanNode>,
207        windows: Vec<WindowDef>,
208    },
209    /// `UNION [ALL]`: execute both sides, concatenate (ALL) or deduplicate.
210    Union {
211        left: Box<PlanNode>,
212        right: Box<PlanNode>,
213        all: bool,
214    },
215    /// EXPLAIN: format the inner plan tree as a text result without executing.
216    Explain {
217        input: Box<PlanNode>,
218    },
219    Begin,
220    Commit,
221    Rollback,
222}
223
224#[derive(Debug, Clone)]
225pub struct ProjectField {
226    pub alias: Option<String>,
227    pub expr: Expr,
228}
229
230#[derive(Debug, Clone)]
231pub struct SortKey {
232    pub expr: Expr,
233    pub descending: bool,
234}
235
236/// One aggregate computation inside a `PlanNode::GroupBy`.
237#[derive(Debug, Clone)]
238pub struct GroupAgg {
239    pub function: AggFunc,
240    pub argument: Expr,
241    pub mode: AggregateMode,
242    /// Source alias whose physical row identity controls symmetric
243    /// de-duplication. `None` means raw aggregation semantics.
244    pub provenance_alias: Option<String>,
245    /// Synthetic output column name (`__agg_0`, `__agg_1`, …).
246    pub output_name: String,
247}
248
249/// One window function definition inside a `PlanNode::Window`.
250#[derive(Debug, Clone)]
251pub struct WindowDef {
252    pub function: WindowFunc,
253    pub args: Vec<Expr>,
254    pub mode: AggregateMode,
255    pub partition_by: Vec<Expr>,
256    pub order_by: Vec<SortKey>,
257    pub output_name: String,
258}