use std::sync::Arc;
use crate::optimizer::error::CostResult;
use crate::optimizer::stats::{StatisticsManager, StatsView};
use crate::planning::plan::{ExecutionPlan, PlanNodeEnum};
use super::{
child_accessor::ChildAccessor,
estimate::NodeCostEstimate,
node_estimators::{
ControlFlowEstimator, DataProcessingEstimator, GraphAlgorithmEstimator,
GraphTraversalEstimator, JoinEstimator, NodeEstimator, ScanEstimator, SortLimitEstimator,
},
CostCalculator, CostModelConfig, SelectivityEstimator,
};
#[derive(Debug, Clone)]
pub struct CostAssigner {
cost_calculator: CostCalculator,
selectivity_estimator: SelectivityEstimator,
config: CostModelConfig,
}
impl CostAssigner {
pub fn with_config(stats_manager: Arc<StatisticsManager>, config: CostModelConfig) -> Self {
Self {
cost_calculator: CostCalculator::with_config(stats_manager.clone(), config),
selectivity_estimator: SelectivityEstimator::new(stats_manager),
config,
}
}
pub fn cost_calculator(&self) -> &CostCalculator {
&self.cost_calculator
}
pub fn selectivity_estimator(&self) -> &SelectivityEstimator {
&self.selectivity_estimator
}
pub fn config(&self) -> &CostModelConfig {
&self.config
}
pub fn assign_costs(&self, stats: &StatsView, plan: &mut ExecutionPlan) -> CostResult<f64> {
match plan.root_mut() {
Some(root) => {
let estimate = self.assign_node_costs_recursive(stats, root)?;
Ok(estimate.total_cost)
}
None => Ok(0.0),
}
}
pub fn assign_costs_with_estimate(
&self,
stats: &StatsView,
plan: &mut ExecutionPlan,
) -> CostResult<NodeCostEstimate> {
match plan.root_mut() {
Some(root) => self.assign_node_costs_recursive(stats, root),
None => Ok(NodeCostEstimate::new(0.0, 0.0, 0)),
}
}
fn assign_node_costs_recursive(
&self,
stats: &StatsView,
node: &mut PlanNodeEnum,
) -> CostResult<NodeCostEstimate> {
let child_estimates = self.calculate_child_estimates(stats, node)?;
let estimate = self.calculate_node_estimate(stats, node, &child_estimates)?;
Ok(estimate)
}
fn calculate_child_estimates(
&self,
stats: &StatsView,
node: &mut PlanNodeEnum,
) -> CostResult<Vec<NodeCostEstimate>> {
let mut estimates = Vec::new();
let child_count = node.child_count();
for i in 0..child_count {
if let Some(child) = node.get_child_mut(i) {
let estimate = self.assign_node_costs_recursive(stats, child)?;
estimates.push(estimate);
}
}
Ok(estimates)
}
fn calculate_node_estimate(
&self,
stats: &StatsView,
node: &PlanNodeEnum,
child_estimates: &[NodeCostEstimate],
) -> CostResult<NodeCostEstimate> {
let child_total_cost: f64 = child_estimates.iter().map(|e| e.total_cost).sum();
let (node_cost, output_rows) = self.estimate_by_node_type(stats, node, child_estimates)?;
let total_cost = node_cost + child_total_cost;
Ok(NodeCostEstimate::new(node_cost, total_cost, output_rows))
}
fn estimate_by_node_type(
&self,
stats: &StatsView,
node: &PlanNodeEnum,
child_estimates: &[NodeCostEstimate],
) -> CostResult<(f64, u64)> {
match node {
PlanNodeEnum::ScanVertices(_)
| PlanNodeEnum::ScanEdges(_)
| PlanNodeEnum::IndexScan(_) => {
let estimator = ScanEstimator::new(&self.cost_calculator);
estimator.estimate(stats, node, child_estimates)
}
PlanNodeEnum::Expand(_)
| PlanNodeEnum::ExpandAll(_)
| PlanNodeEnum::Traverse(_)
| PlanNodeEnum::AppendVertices(_)
| PlanNodeEnum::GetNeighbors(_)
| PlanNodeEnum::GetVertices(_)
| PlanNodeEnum::GetEdges(_) => {
let estimator = GraphTraversalEstimator::new(&self.cost_calculator);
estimator.estimate(stats, node, child_estimates)
}
PlanNodeEnum::InnerJoin(_)
| PlanNodeEnum::LeftJoin(_)
| PlanNodeEnum::CrossJoin(_)
| PlanNodeEnum::FullOuterJoin(_) => {
let estimator = JoinEstimator::new(&self.cost_calculator);
estimator.estimate(stats, node, child_estimates)
}
PlanNodeEnum::Sort(_)
| PlanNodeEnum::Limit(_)
| PlanNodeEnum::TopN(_)
| PlanNodeEnum::Aggregate(_)
| PlanNodeEnum::Dedup(_)
| PlanNodeEnum::Sample(_) => {
let estimator = SortLimitEstimator::new(&self.cost_calculator);
estimator.estimate(stats, node, child_estimates)
}
PlanNodeEnum::Union(_) | PlanNodeEnum::Minus(_) | PlanNodeEnum::Intersect(_) => {
let input_rows: u64 = child_estimates.iter().map(|e| e.output_rows).sum();
Ok((1.0, input_rows.max(1)))
}
PlanNodeEnum::Loop(_)
| PlanNodeEnum::RecursiveCte(_)
| PlanNodeEnum::Select(_)
| PlanNodeEnum::PassThrough(_)
| PlanNodeEnum::Argument(_) => {
let estimator = ControlFlowEstimator::new(&self.cost_calculator, self.config);
estimator.estimate(stats, node, child_estimates)
}
PlanNodeEnum::ShortestPath(_)
| PlanNodeEnum::AllPaths(_)
| PlanNodeEnum::MultiShortestPath(_)
| PlanNodeEnum::BFSShortest(_) => {
let estimator = GraphAlgorithmEstimator::new(&self.cost_calculator);
estimator.estimate(stats, node, child_estimates)
}
PlanNodeEnum::Filter(_)
| PlanNodeEnum::Project(_)
| PlanNodeEnum::Unwind(_)
| PlanNodeEnum::DataCollect(_)
| PlanNodeEnum::Start(_) => {
let estimator = DataProcessingEstimator::new(
&self.cost_calculator,
&self.selectivity_estimator,
self.config,
);
estimator.estimate(stats, node, child_estimates)
}
_ => {
Ok((1.0, 1))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cost_assigner_creation() {
let stats_manager = Arc::new(StatisticsManager::new());
let assigner = CostAssigner::with_config(stats_manager, CostModelConfig::default());
assert_eq!(assigner.cost_calculator().config().seq_page_cost, 1.0);
}
#[test]
fn test_cost_assigner_with_config() {
let stats_manager = Arc::new(StatisticsManager::new());
let config = CostModelConfig::for_ssd();
let assigner = CostAssigner::with_config(stats_manager, config);
assert_eq!(assigner.cost_calculator().config().random_page_cost, 1.1);
}
}