use crate::{
IndexTarget,
plan::{
ExecutionPlan, JoinTree, ScanAccess, ScanKind,
optimize::{OptimizeContext, Rule, Transformed, cost, walk},
},
};
use super::index_helpers::single_label;
pub struct LabelScan;
impl Rule for LabelScan {
fn name(&self) -> &'static str {
"label_scan"
}
fn rewrite(
&self,
mut plan: ExecutionPlan,
ctx: &OptimizeContext<'_>,
) -> Transformed<ExecutionPlan> {
let Some(catalog) = ctx.index_catalog else {
return Transformed::unchanged(plan);
};
let mut changed = false;
if let Some(pattern) = &mut plan.pattern_plan {
changed |= rewrite_tree(&mut pattern.join_tree, catalog);
}
let nested = walk::recurse_rule_subplans(plan, self, ctx);
changed |= nested.changed;
Transformed {
plan: nested.plan,
changed,
}
}
}
fn rewrite_tree(tree: &mut JoinTree, catalog: &dyn crate::IndexCatalog) -> bool {
match tree {
JoinTree::Unit => false,
JoinTree::Scan(scan) => rewrite_scan(scan, catalog),
JoinTree::Expand { child, .. }
| JoinTree::Questioned { child, .. }
| JoinTree::Repeat { child, .. } => rewrite_tree(child, catalog),
JoinTree::HashJoin { left, right, .. } | JoinTree::Outer { left, right, .. } => {
rewrite_tree(left, catalog) | rewrite_tree(right, catalog)
}
JoinTree::PathSearch { child, .. }
| JoinTree::PathModeFilter { child, .. }
| JoinTree::MatchModeFilter { child, .. } => rewrite_tree(child, catalog),
JoinTree::WorstCaseOptimal { .. } | JoinTree::Subplan(_) => false,
JoinTree::DisjunctiveScan { branches, .. } => {
branches.iter_mut().fold(false, |changed, branch| {
rewrite_scan(branch, catalog) | changed
})
}
}
}
fn rewrite_scan(scan: &mut crate::NodeOrEdgeScan, catalog: &dyn crate::IndexCatalog) -> bool {
if !matches!(scan.access, ScanAccess::Linear) {
return false;
}
let Some(label) = single_label(&scan.label_predicate) else {
return false;
};
let target = match scan.kind {
ScanKind::Node => IndexTarget::Node,
ScanKind::Edge => IndexTarget::Edge,
};
let Some(handle) = catalog.label_index(target, label.clone()) else {
return false;
};
if let (Some(index_cost), Some(baseline)) = (
cost::label_scan_cost(catalog, target, label),
catalog.total_rows(target),
) && cost::should_decline_index(index_cost, baseline)
{
return false;
}
scan.access = ScanAccess::LabelIndex { handle };
true
}