Skip to main content

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    Begin,
148    Commit,
149    Rollback,
150}
151
152#[derive(Debug, Clone)]
153pub struct ProjectField {
154    pub alias: Option<String>,
155    pub expr: Expr,
156}
157
158#[derive(Debug, Clone)]
159pub struct SortKey {
160    pub field: String,
161    pub descending: bool,
162}
163
164/// One aggregate computation inside a `PlanNode::GroupBy`.
165#[derive(Debug, Clone)]
166pub struct GroupAgg {
167    pub function: AggFunc,
168    /// Source column name to aggregate over.
169    pub field: String,
170    /// Synthetic output column name (`__agg_0`, `__agg_1`, …).
171    pub output_name: String,
172}
173
174/// One window function definition inside a `PlanNode::Window`.
175#[derive(Debug, Clone)]
176pub struct WindowDef {
177    pub function: WindowFunc,
178    pub args: Vec<Expr>,
179    pub partition_by: Vec<String>,
180    pub order_by: Vec<SortKey>,
181    pub output_name: String,
182}