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        /// 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}