Skip to main content

powdb_query/
plan.rs

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