1use crate::ast::JoinType;
4use crate::ast::{AggregateFunc, Expr, SortOrder};
5use alloc::boxed::Box;
6use alloc::string::String;
7use alloc::vec::Vec;
8use cynos_core::Value;
9
10#[derive(Clone, Debug)]
12pub enum LogicalPlan {
13 Scan { table: String },
15
16 IndexScan {
18 table: String,
19 index: String,
20 range_start: Option<Value>,
21 range_end: Option<Value>,
22 include_start: bool,
23 include_end: bool,
24 },
25
26 IndexGet {
28 table: String,
29 index: String,
30 key: Value,
31 },
32
33 IndexInGet {
36 table: String,
37 index: String,
38 keys: Vec<Value>,
39 },
40
41 GinIndexScan {
43 table: String,
44 index: String,
45 column: String,
47 column_index: usize,
49 path: String,
51 value: Option<Value>,
53 query_type: String,
55 },
56
57 GinIndexScanMulti {
60 table: String,
61 index: String,
62 column: String,
64 pairs: Vec<(String, Value)>,
66 },
67
68 Filter {
70 input: Box<LogicalPlan>,
71 predicate: Expr,
72 },
73
74 Project {
76 input: Box<LogicalPlan>,
77 columns: Vec<Expr>,
78 },
79
80 Join {
82 left: Box<LogicalPlan>,
83 right: Box<LogicalPlan>,
84 condition: Expr,
85 join_type: JoinType,
86 },
87
88 Aggregate {
90 input: Box<LogicalPlan>,
91 group_by: Vec<Expr>,
92 aggregates: Vec<(AggregateFunc, Expr)>,
93 },
94
95 Sort {
97 input: Box<LogicalPlan>,
98 order_by: Vec<(Expr, SortOrder)>,
99 },
100
101 Limit {
103 input: Box<LogicalPlan>,
104 limit: usize,
105 offset: usize,
106 },
107
108 CrossProduct {
110 left: Box<LogicalPlan>,
111 right: Box<LogicalPlan>,
112 },
113
114 Union {
116 left: Box<LogicalPlan>,
117 right: Box<LogicalPlan>,
118 all: bool,
119 },
120
121 Empty,
123}
124
125impl LogicalPlan {
126 pub fn scan(table: impl Into<String>) -> Self {
128 LogicalPlan::Scan {
129 table: table.into(),
130 }
131 }
132
133 pub fn filter(input: LogicalPlan, predicate: Expr) -> Self {
135 LogicalPlan::Filter {
136 input: Box::new(input),
137 predicate,
138 }
139 }
140
141 pub fn project(input: LogicalPlan, columns: Vec<Expr>) -> Self {
143 LogicalPlan::Project {
144 input: Box::new(input),
145 columns,
146 }
147 }
148
149 pub fn join(
151 left: LogicalPlan,
152 right: LogicalPlan,
153 condition: Expr,
154 join_type: JoinType,
155 ) -> Self {
156 LogicalPlan::Join {
157 left: Box::new(left),
158 right: Box::new(right),
159 condition,
160 join_type,
161 }
162 }
163
164 pub fn inner_join(left: LogicalPlan, right: LogicalPlan, condition: Expr) -> Self {
166 Self::join(left, right, condition, JoinType::Inner)
167 }
168
169 pub fn left_join(left: LogicalPlan, right: LogicalPlan, condition: Expr) -> Self {
171 Self::join(left, right, condition, JoinType::LeftOuter)
172 }
173
174 pub fn aggregate(
176 input: LogicalPlan,
177 group_by: Vec<Expr>,
178 aggregates: Vec<(AggregateFunc, Expr)>,
179 ) -> Self {
180 LogicalPlan::Aggregate {
181 input: Box::new(input),
182 group_by,
183 aggregates,
184 }
185 }
186
187 pub fn sort(input: LogicalPlan, order_by: Vec<(Expr, SortOrder)>) -> Self {
189 LogicalPlan::Sort {
190 input: Box::new(input),
191 order_by,
192 }
193 }
194
195 pub fn limit(input: LogicalPlan, limit: usize, offset: usize) -> Self {
197 LogicalPlan::Limit {
198 input: Box::new(input),
199 limit,
200 offset,
201 }
202 }
203
204 pub fn cross_product(left: LogicalPlan, right: LogicalPlan) -> Self {
206 LogicalPlan::CrossProduct {
207 left: Box::new(left),
208 right: Box::new(right),
209 }
210 }
211
212 pub fn inputs(&self) -> Vec<&LogicalPlan> {
214 match self {
215 LogicalPlan::Scan { .. }
216 | LogicalPlan::IndexScan { .. }
217 | LogicalPlan::IndexGet { .. }
218 | LogicalPlan::IndexInGet { .. }
219 | LogicalPlan::GinIndexScan { .. }
220 | LogicalPlan::GinIndexScanMulti { .. }
221 | LogicalPlan::Empty => alloc::vec![],
222 LogicalPlan::Filter { input, .. }
223 | LogicalPlan::Project { input, .. }
224 | LogicalPlan::Aggregate { input, .. }
225 | LogicalPlan::Sort { input, .. }
226 | LogicalPlan::Limit { input, .. } => alloc::vec![input.as_ref()],
227 LogicalPlan::Join { left, right, .. }
228 | LogicalPlan::CrossProduct { left, right }
229 | LogicalPlan::Union { left, right, .. } => alloc::vec![left.as_ref(), right.as_ref()],
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::ast::Expr;
238
239 #[test]
240 fn test_logical_plan_builders() {
241 let scan = LogicalPlan::scan("users");
242 assert!(matches!(scan, LogicalPlan::Scan { table } if table == "users"));
243
244 let filter = LogicalPlan::filter(
245 LogicalPlan::scan("users"),
246 Expr::eq(Expr::column("users", "id", 0), Expr::literal(1i64)),
247 );
248 assert!(matches!(filter, LogicalPlan::Filter { .. }));
249
250 let project = LogicalPlan::project(
251 LogicalPlan::scan("users"),
252 alloc::vec![Expr::column("users", "name", 1)],
253 );
254 assert!(matches!(project, LogicalPlan::Project { .. }));
255 }
256
257 #[test]
258 fn test_logical_plan_inputs() {
259 let scan = LogicalPlan::scan("users");
260 assert!(scan.inputs().is_empty());
261
262 let filter = LogicalPlan::filter(
263 LogicalPlan::scan("users"),
264 Expr::eq(Expr::column("users", "id", 0), Expr::literal(1i64)),
265 );
266 assert_eq!(filter.inputs().len(), 1);
267
268 let join = LogicalPlan::inner_join(
269 LogicalPlan::scan("a"),
270 LogicalPlan::scan("b"),
271 Expr::eq(Expr::column("a", "id", 0), Expr::column("b", "a_id", 0)),
272 );
273 assert_eq!(join.inputs().len(), 2);
274 }
275}