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