linkrs_query/planning/plan/
execution_plan.rs1use std::collections::HashMap;
5
6use crate::planning::plan::core::nodes::base::plan_node_traits::{
7 MultipleInputNode, SingleInputNode,
8};
9use crate::planning::plan::logical_plan::LogicalPlan;
10use crate::planning::plan::PlanNodeEnum;
11use crate::planning::planner::PlannerError;
12
13pub use crate::planning::plan::partition_spec::{
18 PartitionSource, PartitionSpec, PartitionSpecError,
19};
20
21#[derive(Debug, Clone)]
24pub struct ExecutionPlan {
25 pub root: Option<PlanNodeEnum>,
27
28 pub id: i64,
30
31 pub optimize_time_in_us: u64,
33
34 pub format: String,
36
37 pub partition_spec: Option<PartitionSpec>,
40
41 pub max_workers: usize,
44
45 pub max_buffered_chunks: usize,
47
48 pub parallel_fallback_reason: String,
51
52 pub cbo_notes: Vec<String>,
55
56 pub row_estimates: HashMap<i64, u64>,
60
61 pub join_algorithms: HashMap<i64, crate::optimizer::JoinAlgorithm>,
68
69 pub logical_plan: Option<LogicalPlan>,
72}
73
74impl ExecutionPlan {
75 pub fn new(root: Option<PlanNodeEnum>) -> Self {
77 Self {
78 root,
79 id: -1,
80 optimize_time_in_us: 0,
81 format: "default".to_string(),
82 partition_spec: None,
83 max_workers: 1,
84 max_buffered_chunks: 10,
85 parallel_fallback_reason: String::new(),
86 cbo_notes: Vec::new(),
87 row_estimates: HashMap::new(),
88 join_algorithms: HashMap::new(),
89 logical_plan: None,
90 }
91 }
92
93 pub fn set_root(&mut self, root: PlanNodeEnum) {
95 self.root = Some(root);
96 }
97
98 pub fn root(&self) -> &Option<PlanNodeEnum> {
100 &self.root
101 }
102
103 pub fn root_mut(&mut self) -> &mut Option<PlanNodeEnum> {
105 &mut self.root
106 }
107
108 pub fn set_id(&mut self, id: i64) {
110 self.id = id;
111 }
112
113 pub fn set_optimize_time(&mut self, time_us: u64) {
115 self.optimize_time_in_us = time_us;
116 }
117
118 pub fn set_format(&mut self, format: String) {
120 self.format = format;
121 }
122
123 pub fn set_partition_spec(&mut self, partition_spec: PartitionSpec) {
125 self.partition_spec = Some(partition_spec);
126 }
127
128 pub fn clear_partition_spec(&mut self) {
130 self.partition_spec = None;
131 }
132
133 pub fn partition_spec(&self) -> Option<&PartitionSpec> {
134 self.partition_spec.as_ref()
135 }
136
137 pub fn set_max_workers(&mut self, max_workers: usize) {
138 self.max_workers = max_workers;
139 }
140
141 pub fn set_max_buffered_chunks(&mut self, max_buffered_chunks: usize) {
142 self.max_buffered_chunks = max_buffered_chunks;
143 }
144
145 pub fn set_logical_plan(&mut self, logical_plan: LogicalPlan) {
147 self.logical_plan = Some(logical_plan);
148 }
149
150 pub fn logical_plan(&self) -> Option<&LogicalPlan> {
152 self.logical_plan.as_ref()
153 }
154
155 pub fn node_count(&self) -> usize {
159 let Some(root) = self.root.as_ref() else {
160 return 0;
161 };
162 let mut count = 0;
163 let mut stack = vec![root];
164 while let Some(node) = stack.pop() {
165 count += 1;
166 stack.extend(node.children());
167 }
168 count
169 }
170}
171
172#[derive(Debug, Clone)]
176pub struct SubPlan {
177 pub root: Option<PlanNodeEnum>,
179
180 pub tail: Option<PlanNodeEnum>,
183
184 pub logical_root: Option<crate::planning::plan::logical::LogicalNodeEnum>,
192}
193
194impl SubPlan {
195 pub fn new(root: Option<PlanNodeEnum>, tail: Option<PlanNodeEnum>) -> Self {
203 Self {
204 root,
205 tail,
206 logical_root: None,
207 }
208 }
209
210 pub fn from_logical_root(
216 logical_root: crate::planning::plan::logical::LogicalNodeEnum,
217 ) -> Self {
218 let physical_root =
219 crate::planning::physical_planner::convert_logical_to_physical(logical_root.clone());
220 Self {
221 root: Some(physical_root),
222 tail: None,
223 logical_root: Some(logical_root),
224 }
225 }
226
227 pub fn logical_root(&self) -> Option<&crate::planning::plan::logical::LogicalNodeEnum> {
229 self.logical_root.as_ref()
230 }
231
232 pub fn from_root(root: PlanNodeEnum) -> Self {
238 Self {
239 root: Some(root.clone()),
240 tail: Some(root),
241 logical_root: None,
242 }
243 }
244
245 pub fn from_single_node(node: PlanNodeEnum) -> Self {
251 Self {
252 root: Some(node.clone()),
253 tail: Some(node),
254 logical_root: None,
255 }
256 }
257
258 pub fn root(&self) -> &Option<PlanNodeEnum> {
260 &self.root
261 }
262
263 pub fn tail(&self) -> &Option<PlanNodeEnum> {
265 &self.tail
266 }
267
268 pub fn set_root(&mut self, root: PlanNodeEnum) {
270 self.root = Some(root);
271 }
272
273 pub fn set_tail(&mut self, tail: PlanNodeEnum) {
275 self.tail = Some(tail);
276 }
277
278 pub fn is_empty(&self) -> bool {
280 self.root.is_none()
281 }
282
283 pub fn collect_nodes(&self) -> Vec<PlanNodeEnum> {
285 let mut nodes = Vec::new();
286
287 if let Some(root) = &self.root {
288 nodes.push(root.clone());
289 }
290
291 if let Some(tail) = &self.tail {
292 nodes.push(tail.clone());
293 }
294
295 nodes
296 }
297
298 pub fn connect_upstream(
310 mut downstream: SubPlan,
311 upstream: SubPlan,
312 ) -> Result<SubPlan, PlannerError> {
313 let down_root = downstream.root.take().ok_or_else(|| {
314 PlannerError::PlanGenerationFailed("downstream has no root".to_string())
315 })?;
316 let up_root = upstream.root.ok_or_else(|| {
317 PlannerError::PlanGenerationFailed("upstream has no root".to_string())
318 })?;
319
320 let mut connected = down_root;
321 connect_node_input(&mut connected, up_root)?;
322 downstream.root = Some(connected);
323 downstream.tail = upstream.tail;
324 Ok(downstream)
325 }
326}
327
328fn connect_node_input(node: &mut PlanNodeEnum, input: PlanNodeEnum) -> Result<(), PlannerError> {
336 match node {
337 PlanNodeEnum::Project(n) => n.set_input(input),
338 PlanNodeEnum::Filter(n) => n.set_input(input),
339 PlanNodeEnum::Sort(n) => n.set_input(input),
340 PlanNodeEnum::Limit(n) => n.set_input(input),
341 PlanNodeEnum::TopN(n) => n.set_input(input),
342 PlanNodeEnum::Sample(n) => n.set_input(input),
343 PlanNodeEnum::Dedup(n) => n.set_input(input),
344 PlanNodeEnum::DataCollect(n) => n.set_input(input),
345 PlanNodeEnum::Aggregate(n) => n.set_input(input),
346 PlanNodeEnum::Window(n) => n.set_input(input),
347 PlanNodeEnum::Unwind(n) => n.set_input(input),
348 PlanNodeEnum::Assign(n) => n.set_input(input),
349 PlanNodeEnum::PatternApply(n) => n.set_input(input),
350 PlanNodeEnum::CorrelatedApply(n) => n.set_input(input),
351 PlanNodeEnum::RollUpApply(n) => n.set_input(input),
352 PlanNodeEnum::Materialize(n) => n.set_input(input),
353 PlanNodeEnum::Traverse(n) => n.set_input(input),
354 PlanNodeEnum::PipeDeleteVertices(n) => n.set_input(input),
355 PlanNodeEnum::PipeDeleteEdges(n) => n.set_input(input),
356 PlanNodeEnum::Union(n) => n.set_input(input),
357 PlanNodeEnum::Minus(n) => n.set_input(input),
358 PlanNodeEnum::Intersect(n) => n.set_input(input),
359 PlanNodeEnum::Expand(n) => n.add_input(input),
360 PlanNodeEnum::ExpandAll(n) => n.add_input(input),
361 PlanNodeEnum::AppendVertices(n) => n.add_input(input),
362 _ => {
363 return Err(PlannerError::PlanGenerationFailed(format!(
364 "Cannot connect upstream to node of type {}",
365 node.name()
366 )));
367 }
368 }
369 Ok(())
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn execution_plan_max_workers_defaults() {
378 let plan = ExecutionPlan::new(None);
379 assert_eq!(plan.max_workers, 1);
380 assert_eq!(plan.max_buffered_chunks, 10);
381 }
382
383 #[test]
384 fn execution_plan_set_max_workers() {
385 let mut plan = ExecutionPlan::new(None);
386 plan.set_max_workers(4);
387 plan.set_max_buffered_chunks(20);
388 assert_eq!(plan.max_workers, 4);
389 assert_eq!(plan.max_buffered_chunks, 20);
390 }
391
392 #[test]
393 fn connect_upstream_attaches_input_structurally() {
394 use crate::planning::plan::core::nodes::{ExpandAllNode, StartNode};
395
396 let downstream = SubPlan::from_single_node(PlanNodeEnum::ExpandAll(ExpandAllNode::new(
397 1,
398 vec![],
399 "out",
400 )));
401 let upstream = SubPlan::from_single_node(PlanNodeEnum::Start(StartNode::new()));
402
403 let connected = SubPlan::connect_upstream(downstream, upstream)
404 .expect("connect_upstream should succeed");
405 let root = connected.root.expect("connected plan should have a root");
406 let children = root.children();
407 assert_eq!(children.len(), 1, "expand node must own exactly one input");
408 assert!(
409 matches!(children[0], PlanNodeEnum::Start(_)),
410 "expand node input should be the upstream root"
411 );
412 assert!(
413 connected.tail.is_some(),
414 "connected plan tail should carry the upstream tail"
415 );
416 }
417
418 #[test]
419 fn connect_upstream_requires_both_roots() {
420 use crate::planning::plan::core::nodes::StartNode;
421
422 let empty = SubPlan::new(None, None);
423 let populated = SubPlan::from_single_node(PlanNodeEnum::Start(StartNode::new()));
424
425 assert!(SubPlan::connect_upstream(empty.clone(), populated.clone()).is_err());
426 assert!(SubPlan::connect_upstream(populated, empty).is_err());
427 }
428
429 #[test]
430 fn connect_upstream_rejects_leaf_downstream() {
431 use crate::planning::plan::core::nodes::StartNode;
432
433 let downstream = SubPlan::from_single_node(PlanNodeEnum::Start(StartNode::new()));
434 let upstream = SubPlan::from_single_node(PlanNodeEnum::Start(StartNode::new()));
435
436 assert!(
437 SubPlan::connect_upstream(downstream, upstream).is_err(),
438 "a leaf node cannot host a unary upstream"
439 );
440 }
441}