use std::collections::HashMap;
use crate::planning::plan::core::nodes::base::plan_node_traits::{
MultipleInputNode, SingleInputNode,
};
use crate::planning::plan::logical_plan::LogicalPlan;
use crate::planning::plan::PlanNodeEnum;
use crate::planning::planner::PlannerError;
pub use crate::planning::plan::partition_spec::{
PartitionSource, PartitionSpec, PartitionSpecError,
};
#[derive(Debug, Clone)]
pub struct ExecutionPlan {
pub root: Option<PlanNodeEnum>,
pub id: i64,
pub optimize_time_in_us: u64,
pub format: String,
pub partition_spec: Option<PartitionSpec>,
pub max_workers: usize,
pub max_buffered_chunks: usize,
pub parallel_fallback_reason: String,
pub cbo_notes: Vec<String>,
pub row_estimates: HashMap<i64, u64>,
pub join_algorithms: HashMap<i64, crate::optimizer::JoinAlgorithm>,
pub logical_plan: Option<LogicalPlan>,
}
impl ExecutionPlan {
pub fn new(root: Option<PlanNodeEnum>) -> Self {
Self {
root,
id: -1,
optimize_time_in_us: 0,
format: "default".to_string(),
partition_spec: None,
max_workers: 1,
max_buffered_chunks: 10,
parallel_fallback_reason: String::new(),
cbo_notes: Vec::new(),
row_estimates: HashMap::new(),
join_algorithms: HashMap::new(),
logical_plan: None,
}
}
pub fn set_root(&mut self, root: PlanNodeEnum) {
self.root = Some(root);
}
pub fn root(&self) -> &Option<PlanNodeEnum> {
&self.root
}
pub fn root_mut(&mut self) -> &mut Option<PlanNodeEnum> {
&mut self.root
}
pub fn set_id(&mut self, id: i64) {
self.id = id;
}
pub fn set_optimize_time(&mut self, time_us: u64) {
self.optimize_time_in_us = time_us;
}
pub fn set_format(&mut self, format: String) {
self.format = format;
}
pub fn set_partition_spec(&mut self, partition_spec: PartitionSpec) {
self.partition_spec = Some(partition_spec);
}
pub fn clear_partition_spec(&mut self) {
self.partition_spec = None;
}
pub fn partition_spec(&self) -> Option<&PartitionSpec> {
self.partition_spec.as_ref()
}
pub fn set_max_workers(&mut self, max_workers: usize) {
self.max_workers = max_workers;
}
pub fn set_max_buffered_chunks(&mut self, max_buffered_chunks: usize) {
self.max_buffered_chunks = max_buffered_chunks;
}
pub fn set_logical_plan(&mut self, logical_plan: LogicalPlan) {
self.logical_plan = Some(logical_plan);
}
pub fn logical_plan(&self) -> Option<&LogicalPlan> {
self.logical_plan.as_ref()
}
pub fn node_count(&self) -> usize {
let Some(root) = self.root.as_ref() else {
return 0;
};
let mut count = 0;
let mut stack = vec![root];
while let Some(node) = stack.pop() {
count += 1;
stack.extend(node.children());
}
count
}
}
#[derive(Debug, Clone)]
pub struct SubPlan {
pub root: Option<PlanNodeEnum>,
pub tail: Option<PlanNodeEnum>,
pub logical_root: Option<crate::planning::plan::logical::LogicalNodeEnum>,
}
impl SubPlan {
pub fn new(root: Option<PlanNodeEnum>, tail: Option<PlanNodeEnum>) -> Self {
Self {
root,
tail,
logical_root: None,
}
}
pub fn from_logical_root(
logical_root: crate::planning::plan::logical::LogicalNodeEnum,
) -> Self {
let physical_root =
crate::planning::physical_planner::convert_logical_to_physical(logical_root.clone());
Self {
root: Some(physical_root),
tail: None,
logical_root: Some(logical_root),
}
}
pub fn logical_root(&self) -> Option<&crate::planning::plan::logical::LogicalNodeEnum> {
self.logical_root.as_ref()
}
pub fn from_root(root: PlanNodeEnum) -> Self {
Self {
root: Some(root.clone()),
tail: Some(root),
logical_root: None,
}
}
pub fn from_single_node(node: PlanNodeEnum) -> Self {
Self {
root: Some(node.clone()),
tail: Some(node),
logical_root: None,
}
}
pub fn root(&self) -> &Option<PlanNodeEnum> {
&self.root
}
pub fn tail(&self) -> &Option<PlanNodeEnum> {
&self.tail
}
pub fn set_root(&mut self, root: PlanNodeEnum) {
self.root = Some(root);
}
pub fn set_tail(&mut self, tail: PlanNodeEnum) {
self.tail = Some(tail);
}
pub fn is_empty(&self) -> bool {
self.root.is_none()
}
pub fn collect_nodes(&self) -> Vec<PlanNodeEnum> {
let mut nodes = Vec::new();
if let Some(root) = &self.root {
nodes.push(root.clone());
}
if let Some(tail) = &self.tail {
nodes.push(tail.clone());
}
nodes
}
pub fn connect_upstream(
mut downstream: SubPlan,
upstream: SubPlan,
) -> Result<SubPlan, PlannerError> {
let down_root = downstream.root.take().ok_or_else(|| {
PlannerError::PlanGenerationFailed("downstream has no root".to_string())
})?;
let up_root = upstream.root.ok_or_else(|| {
PlannerError::PlanGenerationFailed("upstream has no root".to_string())
})?;
let mut connected = down_root;
connect_node_input(&mut connected, up_root)?;
downstream.root = Some(connected);
downstream.tail = upstream.tail;
Ok(downstream)
}
}
fn connect_node_input(node: &mut PlanNodeEnum, input: PlanNodeEnum) -> Result<(), PlannerError> {
match node {
PlanNodeEnum::Project(n) => n.set_input(input),
PlanNodeEnum::Filter(n) => n.set_input(input),
PlanNodeEnum::Sort(n) => n.set_input(input),
PlanNodeEnum::Limit(n) => n.set_input(input),
PlanNodeEnum::TopN(n) => n.set_input(input),
PlanNodeEnum::Sample(n) => n.set_input(input),
PlanNodeEnum::Dedup(n) => n.set_input(input),
PlanNodeEnum::DataCollect(n) => n.set_input(input),
PlanNodeEnum::Aggregate(n) => n.set_input(input),
PlanNodeEnum::Window(n) => n.set_input(input),
PlanNodeEnum::Unwind(n) => n.set_input(input),
PlanNodeEnum::Assign(n) => n.set_input(input),
PlanNodeEnum::PatternApply(n) => n.set_input(input),
PlanNodeEnum::CorrelatedApply(n) => n.set_input(input),
PlanNodeEnum::RollUpApply(n) => n.set_input(input),
PlanNodeEnum::Materialize(n) => n.set_input(input),
PlanNodeEnum::Traverse(n) => n.set_input(input),
PlanNodeEnum::PipeDeleteVertices(n) => n.set_input(input),
PlanNodeEnum::PipeDeleteEdges(n) => n.set_input(input),
PlanNodeEnum::Union(n) => n.set_input(input),
PlanNodeEnum::Minus(n) => n.set_input(input),
PlanNodeEnum::Intersect(n) => n.set_input(input),
PlanNodeEnum::Expand(n) => n.add_input(input),
PlanNodeEnum::ExpandAll(n) => n.add_input(input),
PlanNodeEnum::AppendVertices(n) => n.add_input(input),
_ => {
return Err(PlannerError::PlanGenerationFailed(format!(
"Cannot connect upstream to node of type {}",
node.name()
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn execution_plan_max_workers_defaults() {
let plan = ExecutionPlan::new(None);
assert_eq!(plan.max_workers, 1);
assert_eq!(plan.max_buffered_chunks, 10);
}
#[test]
fn execution_plan_set_max_workers() {
let mut plan = ExecutionPlan::new(None);
plan.set_max_workers(4);
plan.set_max_buffered_chunks(20);
assert_eq!(plan.max_workers, 4);
assert_eq!(plan.max_buffered_chunks, 20);
}
#[test]
fn connect_upstream_attaches_input_structurally() {
use crate::planning::plan::core::nodes::{ExpandAllNode, StartNode};
let downstream = SubPlan::from_single_node(PlanNodeEnum::ExpandAll(ExpandAllNode::new(
1,
vec![],
"out",
)));
let upstream = SubPlan::from_single_node(PlanNodeEnum::Start(StartNode::new()));
let connected = SubPlan::connect_upstream(downstream, upstream)
.expect("connect_upstream should succeed");
let root = connected.root.expect("connected plan should have a root");
let children = root.children();
assert_eq!(children.len(), 1, "expand node must own exactly one input");
assert!(
matches!(children[0], PlanNodeEnum::Start(_)),
"expand node input should be the upstream root"
);
assert!(
connected.tail.is_some(),
"connected plan tail should carry the upstream tail"
);
}
#[test]
fn connect_upstream_requires_both_roots() {
use crate::planning::plan::core::nodes::StartNode;
let empty = SubPlan::new(None, None);
let populated = SubPlan::from_single_node(PlanNodeEnum::Start(StartNode::new()));
assert!(SubPlan::connect_upstream(empty.clone(), populated.clone()).is_err());
assert!(SubPlan::connect_upstream(populated, empty).is_err());
}
#[test]
fn connect_upstream_rejects_leaf_downstream() {
use crate::planning::plan::core::nodes::StartNode;
let downstream = SubPlan::from_single_node(PlanNodeEnum::Start(StartNode::new()));
let upstream = SubPlan::from_single_node(PlanNodeEnum::Start(StartNode::new()));
assert!(
SubPlan::connect_upstream(downstream, upstream).is_err(),
"a leaf node cannot host a unary upstream"
);
}
}