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