use std::sync::Arc;
use crate::binder::BoundStatement;
use crate::parser::ast::Stmt;
use crate::planning::plan::core::node_id_generator::next_node_id;
use crate::planning::plan::core::nodes::{IntersectNode, MinusNode, UnionNode};
use crate::planning::plan::logical::logical_nodes::graph_ops::{
LogicalIntersectNode, LogicalMinusNode, LogicalUnionNode,
};
use crate::planning::plan::logical::LogicalNodeEnum;
use crate::planning::plan::{PlanNodeEnum, SubPlan};
use crate::planning::planner::{Planner, PlannerEnum, PlannerError, ValidatedStatement};
use crate::QueryContext;
#[derive(Debug, Clone)]
pub struct SetOperationPlanner {
max_depth: usize,
}
impl SetOperationPlanner {
pub fn new() -> Self {
Self { max_depth: 100 }
}
pub fn with_max_depth(max_depth: usize) -> Self {
Self { max_depth }
}
fn side_context<'b>(
ctx: &'b crate::planning::context::PlanContext<'b>,
side: &'b BoundStatement,
store: &'b mut Option<ValidatedStatement>,
) -> crate::planning::context::PlanContext<'b> {
*store = ctx.derive_validated(side);
let validated = store.as_ref().unwrap_or(ctx.validated);
crate::planning::context::PlanContext {
bound: side,
qctx: ctx.qctx.clone(),
metadata: ctx.metadata,
validated,
}
}
fn validate_column_compatibility(
&self,
left_plan: &SubPlan,
right_plan: &SubPlan,
) -> Result<(), PlannerError> {
let left_cols = left_plan
.root()
.as_ref()
.map(|n| n.col_names())
.unwrap_or_default();
let right_cols = right_plan
.root()
.as_ref()
.map(|n| n.col_names())
.unwrap_or_default();
if left_cols.len() != right_cols.len() {
return Err(PlannerError::PlanGenerationFailed(format!(
"Column count mismatch: left has {} columns, right has {} columns",
left_cols.len(),
right_cols.len()
)));
}
Ok(())
}
fn combine(
&self,
left_plan: SubPlan,
right_plan: SubPlan,
operation: crate::binder::bound::SetOperationKind,
) -> Result<SubPlan, PlannerError> {
self.validate_column_compatibility(&left_plan, &right_plan)?;
let left_root = left_plan.root().clone().ok_or_else(|| {
PlannerError::PlanGenerationFailed("Left plan has no root node".to_string())
})?;
let right_root = right_plan.root().clone().ok_or_else(|| {
PlannerError::PlanGenerationFailed("Right plan has no root node".to_string())
})?;
let final_node = match operation {
crate::binder::bound::SetOperationKind::Union => {
let union_node = UnionNode::new(left_root, right_root, true).map_err(|e| {
PlannerError::PlanGenerationFailed(format!("Failed to create UnionNode: {}", e))
})?;
PlanNodeEnum::Union(union_node)
}
crate::binder::bound::SetOperationKind::UnionAll => {
let union_node = UnionNode::new(left_root, right_root, false).map_err(|e| {
PlannerError::PlanGenerationFailed(format!("Failed to create UnionNode: {}", e))
})?;
PlanNodeEnum::Union(union_node)
}
crate::binder::bound::SetOperationKind::Intersect => {
let intersect_node = IntersectNode::new(left_root, right_root).map_err(|e| {
PlannerError::PlanGenerationFailed(format!(
"Failed to create IntersectNode: {}",
e
))
})?;
PlanNodeEnum::Intersect(intersect_node)
}
crate::binder::bound::SetOperationKind::Minus => {
let minus_node = MinusNode::new(left_root, right_root).map_err(|e| {
PlannerError::PlanGenerationFailed(format!("Failed to create MinusNode: {}", e))
})?;
PlanNodeEnum::Minus(minus_node)
}
};
let logical_root = match (
left_plan.logical_root().cloned(),
right_plan.logical_root().cloned(),
) {
(Some(left), Some(right)) => {
let col_names = final_node.col_names().to_vec();
let mirror = match operation {
crate::binder::bound::SetOperationKind::Union => {
LogicalNodeEnum::Union(LogicalUnionNode {
id: next_node_id(),
deps: vec![left, right],
distinct: true,
output_var: None,
col_names,
column_types: vec![],
})
}
crate::binder::bound::SetOperationKind::UnionAll => {
LogicalNodeEnum::Union(LogicalUnionNode {
id: next_node_id(),
deps: vec![left, right],
distinct: false,
output_var: None,
col_names,
column_types: vec![],
})
}
crate::binder::bound::SetOperationKind::Intersect => {
LogicalNodeEnum::Intersect(LogicalIntersectNode {
id: next_node_id(),
deps: vec![left, right],
output_var: None,
col_names,
column_types: vec![],
})
}
crate::binder::bound::SetOperationKind::Minus => {
LogicalNodeEnum::Minus(LogicalMinusNode {
id: next_node_id(),
deps: vec![left, right],
output_var: None,
col_names,
column_types: vec![],
})
}
};
Some(mirror)
}
_ => None,
};
let tail = left_plan.tail().clone().unwrap_or(final_node.clone());
Ok(SubPlan {
root: Some(final_node),
tail: Some(tail),
logical_root,
})
}
}
impl Planner for SetOperationPlanner {
fn transform(
&mut self,
_validated: &ValidatedStatement,
_qctx: Arc<QueryContext>,
) -> Result<SubPlan, PlannerError> {
Err(PlannerError::InvalidOperation(
"SetOperationPlanner::transform is not supported; use plan_bound with BoundStatement."
.to_string(),
))
}
fn plan_bound(
&mut self,
ctx: &crate::planning::context::PlanContext<'_>,
) -> Result<SubPlan, PlannerError> {
let bound = ctx.bound;
let _ = &bound;
let set_op = match bound {
BoundStatement::SetOperation(s) => s,
_ => {
return Err(PlannerError::InvalidOperation(
"SetOperationPlanner requires SetOperation statement".to_string(),
));
}
};
let mut left_store = None;
let mut right_store = None;
let left_ctx = Self::side_context(ctx, &set_op.left, &mut left_store);
let right_ctx = Self::side_context(ctx, &set_op.right, &mut right_store);
let left_plan = self.plan_bound_subquery(&left_ctx, 1)?;
let right_plan = self.plan_bound_subquery(&right_ctx, 1)?;
self.combine(left_plan, right_plan, set_op.operation)
}
fn match_planner(&self, stmt: &Stmt) -> bool {
matches!(stmt, Stmt::SetOperation(_))
}
}
impl SetOperationPlanner {
fn plan_bound_subquery(
&mut self,
ctx: &crate::planning::context::PlanContext<'_>,
depth: usize,
) -> Result<SubPlan, PlannerError> {
if depth > self.max_depth {
return Err(PlannerError::PlanGenerationFailed(format!(
"Maximum set operation nesting depth ({}) exceeded",
self.max_depth
)));
}
match ctx.bound {
BoundStatement::SetOperation(set_op_stmt) => {
self.plan_bound_set_op(set_op_stmt, ctx, depth + 1)
}
_ => {
let Some(mut planner) = PlannerEnum::from_bound_statement(ctx.bound) else {
return Err(PlannerError::InvalidOperation(format!(
"Unsupported subquery type in set operation: {}",
ctx.bound.kind()
)));
};
planner.plan_bound(ctx)
}
}
}
fn plan_bound_set_op(
&mut self,
set_op: &crate::binder::bound::BoundSetOperationStatement,
ctx: &crate::planning::context::PlanContext<'_>,
depth: usize,
) -> Result<SubPlan, PlannerError> {
let mut left_store = None;
let mut right_store = None;
let left_ctx = Self::side_context(ctx, &set_op.left, &mut left_store);
let right_ctx = Self::side_context(ctx, &set_op.right, &mut right_store);
let left_plan = self.plan_bound_subquery(&left_ctx, depth)?;
let right_plan = self.plan_bound_subquery(&right_ctx, depth)?;
self.combine(left_plan, right_plan, set_op.operation)
}
}
impl Default for SetOperationPlanner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_operation_planner_creation() {
let planner = SetOperationPlanner::new();
assert_eq!(planner.max_depth, 100);
}
#[test]
fn test_set_operation_planner_with_max_depth() {
let planner = SetOperationPlanner::with_max_depth(50);
assert_eq!(planner.max_depth, 50);
}
}