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}