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