use crate::optimizer::cost::config::{
DEDUP_SELECTIVITY, GET_EDGES_DEFAULT_ROWS, GET_VERTICES_DEFAULT_ROWS, UNKNOWN_SCAN_ROWS,
};
use crate::optimizer::cost::SelectivityEstimator;
use crate::optimizer::cost_based::ndv as factor_cost;
use crate::optimizer::cost_based::ndv::DEFAULT_JOIN_SELECTIVITY;
use crate::optimizer::stats::feedback::cardinality::CardinalityFeedbackManager;
use crate::optimizer::stats::StatsView;
use crate::planning::plan::core::nodes::base::plan_node_traits::SingleInputNode;
use crate::planning::plan::PlanNodeEnum;
use super::cardinality::corrected_rows;
use super::stats::{stats_fanout_skewed, DEFAULT_FILTER_SELECTIVITY, DEFAULT_NEIGHBORHOOD_FANOUT};
use crate::executor::streaming::chunk::VECTORIZED_BATCH_SIZE;
pub fn estimate_node_output_rows(
node: &PlanNodeEnum,
stats: &StatsView,
selectivity: &SelectivityEstimator,
) -> u64 {
estimate_node_output_rows_impl(node, stats, selectivity, None)
}
pub fn estimate_node_output_rows_corrected(
node: &PlanNodeEnum,
stats: &StatsView,
selectivity: &SelectivityEstimator,
cardinality: &CardinalityFeedbackManager,
) -> u64 {
estimate_node_output_rows_impl(node, stats, selectivity, Some(cardinality))
}
fn estimate_node_output_rows_impl(
node: &PlanNodeEnum,
stats: &StatsView,
selectivity: &SelectivityEstimator,
cardinality: Option<&CardinalityFeedbackManager>,
) -> u64 {
use PlanNodeEnum::*;
match node {
ScanVertices(n) => {
let tag_rows = n
.tag()
.map(|tag| stats.vertex_count(tag))
.unwrap_or(UNKNOWN_SCAN_ROWS);
let raw = n
.limit()
.map(|limit| tag_rows.min(limit as u64))
.unwrap_or(tag_rows);
corrected_rows(node, raw, stats.space(), cardinality)
}
ScanEdges(n) => {
let edge_rows = n
.edge_type()
.map(|edge_type| stats.edge_count(&edge_type))
.unwrap_or(UNKNOWN_SCAN_ROWS);
let raw = n
.limit()
.map(|limit| edge_rows.min(limit as u64))
.unwrap_or(edge_rows);
corrected_rows(node, raw, stats.space(), cardinality)
}
GetVertices(n) => corrected_rows(
node,
n.limit().unwrap_or(GET_VERTICES_DEFAULT_ROWS as i64).max(1) as u64,
stats.space(),
cardinality,
),
GetEdges(n) => corrected_rows(
node,
n.limit().unwrap_or(GET_EDGES_DEFAULT_ROWS as i64).max(1) as u64,
stats.space(),
cardinality,
),
GetNeighbors(n) => {
let fanout = n
.edge_types()
.iter()
.find_map(|edge_type| {
stats
.edge_stats(edge_type)
.map(|s| (s.avg_out_degree.max(0.0)) as u64)
})
.unwrap_or(DEFAULT_NEIGHBORHOOD_FANOUT);
let estimated = fanout.max(1);
let raw = match n.limit() {
Some(limit) => (limit.max(1) as u64).min(estimated),
None => estimated,
};
corrected_rows(node, raw, stats.space(), cardinality)
}
IndexScan(n) => corrected_rows(
node,
n.limit().unwrap_or(UNKNOWN_SCAN_ROWS as i64).max(1) as u64,
stats.space(),
cardinality,
),
Start(_) | Argument(_) => 1,
Filter(n) => {
let input_rows =
estimate_node_output_rows_impl(n.input(), stats, selectivity, cardinality);
let condition = n.condition();
let tag_name = first_tag_of_input(n.input());
let expr_selectivity = condition
.expression()
.map(|meta| {
selectivity.estimate_from_expression(
stats.space(),
meta.inner(),
tag_name.as_deref(),
)
})
.unwrap_or(DEFAULT_FILTER_SELECTIVITY);
(input_rows as f64 * expr_selectivity).max(1.0) as u64
}
Project(_) | Sort(_) | Sample(_) | Window(_) => {
child_rows_of_impl(node, stats, selectivity, cardinality)
}
TopN(n) => {
let input_rows =
estimate_node_output_rows_impl(n.input(), stats, selectivity, cardinality);
input_rows.min(n.limit() as u64)
}
Limit(n) => {
let input_rows =
estimate_node_output_rows_impl(n.input(), stats, selectivity, cardinality);
input_rows.min((n.offset() + n.count()) as u64)
}
Dedup(n) => {
let input_rows =
estimate_node_output_rows_impl(n.input(), stats, selectivity, cardinality);
(input_rows as f64 * DEDUP_SELECTIVITY).max(1.0) as u64
}
Aggregate(n) => {
let input_rows =
estimate_node_output_rows_impl(n.input(), stats, selectivity, cardinality);
let raw = if n.group_keys().is_empty() {
1
} else {
let tag_for_ndv = first_tag_of_input(n.input());
let ndv =
factor_cost::ndv_for_group_keys(stats, tag_for_ndv.as_deref(), n.group_keys());
if let Some(distinct) = ndv {
distinct.min(input_rows).max(1)
} else {
factor_cost::estimate_group_count(input_rows, n.group_keys().len())
}
};
corrected_rows(node, raw, stats.space(), cardinality)
}
InnerJoin(n) => {
let children = node.children();
let raw = if children.len() >= 2 {
let left =
estimate_node_output_rows_impl(children[0], stats, selectivity, cardinality);
let right =
estimate_node_output_rows_impl(children[1], stats, selectivity, cardinality);
let sel = factor_cost::join_selectivity(stats, n.hash_keys(), n.probe_keys())
.unwrap_or(DEFAULT_JOIN_SELECTIVITY);
factor_cost::join_output_rows(left, right, sel)
} else {
child_rows_of_impl(node, stats, selectivity, cardinality)
};
corrected_rows(node, raw, stats.space(), cardinality)
}
LeftJoin(_) => {
let children = node.children();
let raw = if children.len() >= 2 {
estimate_node_output_rows_impl(children[0], stats, selectivity, cardinality)
} else {
child_rows_of_impl(node, stats, selectivity, cardinality)
};
corrected_rows(node, raw, stats.space(), cardinality)
}
RightJoin(_) => {
let children = node.children();
let raw = if children.len() >= 2 {
estimate_node_output_rows_impl(children[1], stats, selectivity, cardinality)
} else {
child_rows_of_impl(node, stats, selectivity, cardinality)
};
corrected_rows(node, raw, stats.space(), cardinality)
}
CrossJoin(_) => {
let children = node.children();
let raw = if children.len() >= 2 {
let left =
estimate_node_output_rows_impl(children[0], stats, selectivity, cardinality);
let right =
estimate_node_output_rows_impl(children[1], stats, selectivity, cardinality);
left.saturating_mul(right)
} else {
child_rows_of_impl(node, stats, selectivity, cardinality)
};
corrected_rows(node, raw, stats.space(), cardinality)
}
FullOuterJoin(_) => {
let children = node.children();
let raw = if children.len() >= 2 {
let left =
estimate_node_output_rows_impl(children[0], stats, selectivity, cardinality);
let right =
estimate_node_output_rows_impl(children[1], stats, selectivity, cardinality);
left.saturating_add(right)
} else {
child_rows_of_impl(node, stats, selectivity, cardinality)
};
corrected_rows(node, raw, stats.space(), cardinality)
}
SemiJoin(_) => child_rows_of_impl(node, stats, selectivity, cardinality),
Union(_) => {
let mut total = 0u64;
for child in node.children() {
total = total.saturating_add(estimate_node_output_rows_impl(
child,
stats,
selectivity,
cardinality,
));
}
corrected_rows(node, total, stats.space(), cardinality)
}
Minus(_) | Intersect(_) => {
let mut smallest = u64::MAX;
for child in node.children() {
smallest = smallest.min(estimate_node_output_rows_impl(
child,
stats,
selectivity,
cardinality,
));
}
let raw = if smallest == u64::MAX { 0 } else { smallest };
corrected_rows(node, raw, stats.space(), cardinality)
}
Expand(n) => {
let fanout = stats_fanout_skewed(stats, n.edge_types());
let raw =
child_rows_of_impl(node, stats, selectivity, cardinality).saturating_mul(fanout);
corrected_rows(node, raw, stats.space(), cardinality)
}
ExpandAll(n) => {
let raw = if n.count_only() {
child_rows_of_impl(node, stats, selectivity, cardinality)
.div_ceil(VECTORIZED_BATCH_SIZE as u64)
} else {
let fanout = stats_fanout_skewed(stats, n.edge_types());
child_rows_of_impl(node, stats, selectivity, cardinality).saturating_mul(fanout)
};
corrected_rows(node, raw, stats.space(), cardinality)
}
Traverse(n) => {
let fanout = stats_fanout_skewed(stats, n.edge_types());
let raw =
child_rows_of_impl(node, stats, selectivity, cardinality).saturating_mul(fanout);
corrected_rows(node, raw, stats.space(), cardinality)
}
BiExpand(n) => {
let fanout = stats_fanout_skewed(stats, n.edge_types());
let raw =
child_rows_of_impl(node, stats, selectivity, cardinality).saturating_mul(fanout);
corrected_rows(node, raw, stats.space(), cardinality)
}
BiTraverse(n) => {
let fanout = stats_fanout_skewed(stats, n.edge_types());
let raw =
child_rows_of_impl(node, stats, selectivity, cardinality).saturating_mul(fanout);
corrected_rows(node, raw, stats.space(), cardinality)
}
AppendVertices(_) => {
let raw = child_rows_of_impl(node, stats, selectivity, cardinality)
.saturating_mul(DEFAULT_NEIGHBORHOOD_FANOUT);
corrected_rows(node, raw, stats.space(), cardinality)
}
PatternApply(_) | Apply(_) | RollUpApply(_) => {
let children = node.children();
let mut total = 1u64;
for child in children {
total = total.saturating_mul(estimate_node_output_rows_impl(
child,
stats,
selectivity,
cardinality,
));
}
corrected_rows(node, total, stats.space(), cardinality)
}
PassThrough(_) | Materialize(_) | Unwind(_) | DataCollect(_) | Assign(_) => {
child_rows_of_impl(node, stats, selectivity, cardinality)
}
node => child_rows_of_impl(node, stats, selectivity, cardinality),
}
}
fn child_rows_of_impl(
node: &PlanNodeEnum,
stats: &StatsView,
selectivity: &SelectivityEstimator,
cardinality: Option<&CardinalityFeedbackManager>,
) -> u64 {
node.children()
.first()
.map(|child| estimate_node_output_rows_impl(child, stats, selectivity, cardinality))
.unwrap_or(1)
}
fn first_tag_of_input(node: &PlanNodeEnum) -> Option<String> {
match node {
PlanNodeEnum::ScanVertices(n) => n.tag().cloned(),
_ => None,
}
}