powdb_query/plan.rs
1use crate::ast::{AggFunc, AlterAction, Assignment, Expr, GroupKey, 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<GroupKey>,
101 aggregates: Vec<GroupAgg>,
102 having: Option<Expr>,
103 },
104 AlterTable {
105 table: String,
106 action: AlterAction,
107 },
108 DropTable {
109 name: String,
110 /// `drop if exists` — a missing table is a no-op instead of an error.
111 if_exists: bool,
112 },
113 Insert {
114 table: String,
115 /// One assignment-block per row to insert. Always at least one.
116 rows: Vec<Vec<Assignment>>,
117 /// `true` when `returning` was requested — executor returns the
118 /// inserted rows instead of a modified-count.
119 returning: bool,
120 },
121 /// UPSERT: probe index on `key_column` — if miss, insert; if hit, update.
122 Upsert {
123 table: String,
124 key_column: String,
125 assignments: Vec<Assignment>,
126 on_conflict: Vec<Assignment>,
127 },
128 Update {
129 input: Box<PlanNode>,
130 table: String,
131 assignments: Vec<Assignment>,
132 /// `true` when `returning` was requested — executor returns the
133 /// post-update rows instead of a modified-count.
134 returning: bool,
135 },
136 Delete {
137 input: Box<PlanNode>,
138 table: String,
139 /// `true` when `returning` was requested — executor returns the
140 /// pre-delete rows instead of a modified-count.
141 returning: bool,
142 },
143 CreateTable {
144 name: String,
145 fields: Vec<CreateField>,
146 /// `type X if not exists { ... }` — re-declaring an existing type is
147 /// a no-op instead of an error.
148 if_not_exists: bool,
149 },
150 /// `schema` — list every type (table) in the catalog. Read-only; reads
151 /// live catalog state at execution time, so a cached plan is never stale.
152 ListTypes,
153 /// `describe <Type>` — the columns and indexes of one type. Read-only.
154 Describe {
155 table: String,
156 },
157 /// Create a materialized view: execute query, store results, register.
158 CreateView {
159 name: String,
160 query_text: String,
161 },
162 /// Explicitly refresh a materialized view.
163 RefreshView {
164 name: String,
165 },
166 /// Drop a materialized view (backing table + registry entry).
167 DropView {
168 name: String,
169 /// `drop view if exists` — a missing view is a no-op instead of an error.
170 if_exists: bool,
171 },
172 /// Window function computation layer.
173 Window {
174 input: Box<PlanNode>,
175 windows: Vec<WindowDef>,
176 },
177 /// `UNION [ALL]`: execute both sides, concatenate (ALL) or deduplicate.
178 Union {
179 left: Box<PlanNode>,
180 right: Box<PlanNode>,
181 all: bool,
182 },
183 /// EXPLAIN: format the inner plan tree as a text result without executing.
184 Explain {
185 input: Box<PlanNode>,
186 },
187 Begin,
188 Commit,
189 Rollback,
190}
191
192#[derive(Debug, Clone)]
193pub struct ProjectField {
194 pub alias: Option<String>,
195 pub expr: Expr,
196}
197
198#[derive(Debug, Clone)]
199pub struct SortKey {
200 pub field: String,
201 pub descending: bool,
202}
203
204/// One aggregate computation inside a `PlanNode::GroupBy`.
205#[derive(Debug, Clone)]
206pub struct GroupAgg {
207 pub function: AggFunc,
208 /// Source column name to aggregate over.
209 pub field: String,
210 /// Synthetic output column name (`__agg_0`, `__agg_1`, …).
211 pub output_name: String,
212}
213
214/// One window function definition inside a `PlanNode::Window`.
215#[derive(Debug, Clone)]
216pub struct WindowDef {
217 pub function: WindowFunc,
218 pub args: Vec<Expr>,
219 pub partition_by: Vec<String>,
220 pub order_by: Vec<SortKey>,
221 pub output_name: String,
222}