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 assignments: Vec<Assignment>,
98 },
99 /// UPSERT: probe index on `key_column` — if miss, insert; if hit, update.
100 Upsert {
101 table: String,
102 key_column: String,
103 assignments: Vec<Assignment>,
104 on_conflict: Vec<Assignment>,
105 },
106 Update {
107 input: Box<PlanNode>,
108 table: String,
109 assignments: Vec<Assignment>,
110 },
111 Delete {
112 input: Box<PlanNode>,
113 table: String,
114 },
115 CreateTable {
116 name: String,
117 fields: Vec<(String, String, bool)>,
118 },
119 /// Create a materialized view: execute query, store results, register.
120 CreateView {
121 name: String,
122 query_text: String,
123 },
124 /// Explicitly refresh a materialized view.
125 RefreshView {
126 name: String,
127 },
128 /// Drop a materialized view (backing table + registry entry).
129 DropView {
130 name: String,
131 },
132 /// Window function computation layer.
133 Window {
134 input: Box<PlanNode>,
135 windows: Vec<WindowDef>,
136 },
137 /// UNION [ALL]: execute both sides, concatenate (ALL) or deduplicate.
138 Union {
139 left: Box<PlanNode>,
140 right: Box<PlanNode>,
141 all: bool,
142 },
143 /// EXPLAIN: format the inner plan tree as a text result without executing.
144 Explain {
145 input: Box<PlanNode>,
146 },
147}
148
149#[derive(Debug, Clone)]
150pub struct ProjectField {
151 pub alias: Option<String>,
152 pub expr: Expr,
153}
154
155#[derive(Debug, Clone)]
156pub struct SortKey {
157 pub field: String,
158 pub descending: bool,
159}
160
161/// One aggregate computation inside a `PlanNode::GroupBy`.
162#[derive(Debug, Clone)]
163pub struct GroupAgg {
164 pub function: AggFunc,
165 /// Source column name to aggregate over.
166 pub field: String,
167 /// Synthetic output column name (`__agg_0`, `__agg_1`, …).
168 pub output_name: String,
169}
170
171/// One window function definition inside a `PlanNode::Window`.
172#[derive(Debug, Clone)]
173pub struct WindowDef {
174 pub function: WindowFunc,
175 pub args: Vec<Expr>,
176 pub partition_by: Vec<String>,
177 pub order_by: Vec<SortKey>,
178 pub output_name: String,
179}