use std::collections::{HashMap, HashSet, VecDeque};
use serde::{Deserialize, Serialize};
use super::ir::{PlanNode, Predicate, PredicateValue, QueryPlan};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FusedPlanBatch {
groups: Vec<FusionGroup>,
subquery_batch: Option<Box<FusedPlanBatch>>,
stats: FusionStats,
#[serde(default)]
shared_nodes: Vec<SharedNode>,
}
impl FusedPlanBatch {
#[inline]
#[must_use]
pub fn groups(&self) -> &[FusionGroup] {
&self.groups
}
#[inline]
#[must_use]
pub fn subquery_batch(&self) -> Option<&FusedPlanBatch> {
self.subquery_batch.as_deref()
}
#[inline]
#[must_use]
pub fn stats(&self) -> &FusionStats {
&self.stats
}
#[inline]
#[must_use]
pub fn shared_nodes(&self) -> &[SharedNode] {
&self.shared_nodes
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.groups.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.groups.is_empty()
}
pub fn iter_groups(&self) -> impl Iterator<Item = &FusionGroup> {
self.groups.iter()
}
#[must_use]
pub fn input_plans(&self) -> Vec<QueryPlan> {
let total_tails: usize = self.groups.iter().map(|g| g.tails.len()).sum();
let mut out: Vec<Option<QueryPlan>> = (0..total_tails).map(|_| None).collect();
for group in &self.groups {
for tail in &group.tails {
let plan = tail.reconstruct(&group.prefix);
let idx = tail.original_index;
debug_assert!(idx < out.len(), "original_index out of bounds");
out[idx] = Some(plan);
}
}
out.into_iter()
.map(|p| p.expect("every original index must be filled"))
.collect()
}
#[must_use]
pub fn total_plans(&self) -> usize {
self.groups.iter().map(|g| g.tails.len()).sum()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SharedNodeId(u32);
impl SharedNodeId {
#[inline]
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SharedNode {
canonical_plan: PlanNode,
consumers: Vec<usize>,
id: SharedNodeId,
}
impl SharedNode {
#[inline]
#[must_use]
pub fn canonical_plan(&self) -> &PlanNode {
&self.canonical_plan
}
#[inline]
#[must_use]
pub fn consumers(&self) -> &[usize] {
&self.consumers
}
#[inline]
#[must_use]
pub const fn id(&self) -> SharedNodeId {
self.id
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FusionGroup {
prefix: PlanNode,
tails: Vec<FusedTail>,
}
impl FusionGroup {
#[inline]
#[must_use]
pub fn prefix(&self) -> &PlanNode {
&self.prefix
}
#[inline]
#[must_use]
pub fn tails(&self) -> &[FusedTail] {
&self.tails
}
#[inline]
#[must_use]
pub fn tail_count(&self) -> usize {
self.tails.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FusedTail {
pub original_index: usize,
pub tail: FusionTail,
}
impl FusedTail {
#[must_use]
pub fn reconstruct(&self, prefix: &PlanNode) -> QueryPlan {
let root = match &self.tail {
FusionTail::Identity => prefix.clone(),
FusionTail::ChainContinuation { remaining_steps } => {
let mut steps = Vec::with_capacity(remaining_steps.len() + 1);
steps.push(prefix.clone());
steps.extend(remaining_steps.iter().cloned());
PlanNode::Chain { steps }
}
};
QueryPlan::new(root)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FusionTail {
Identity,
ChainContinuation {
remaining_steps: Vec<PlanNode>,
},
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct FusionStats {
pub total_plans: usize,
pub fusion_groups: usize,
pub scans_eliminated: usize,
pub subqueries_total: usize,
pub subqueries_unique: usize,
pub shared_nodes_promoted: usize,
#[serde(with = "f64_bits")]
pub plan_tree_reduction_ratio: f64,
}
impl Default for FusionStats {
fn default() -> Self {
Self {
total_plans: 0,
fusion_groups: 0,
scans_eliminated: 0,
subqueries_total: 0,
subqueries_unique: 0,
shared_nodes_promoted: 0,
plan_tree_reduction_ratio: 0.0,
}
}
}
impl PartialEq for FusionStats {
fn eq(&self, other: &Self) -> bool {
self.total_plans == other.total_plans
&& self.fusion_groups == other.fusion_groups
&& self.scans_eliminated == other.scans_eliminated
&& self.subqueries_total == other.subqueries_total
&& self.subqueries_unique == other.subqueries_unique
&& self.shared_nodes_promoted == other.shared_nodes_promoted
&& self.plan_tree_reduction_ratio.to_bits() == other.plan_tree_reduction_ratio.to_bits()
}
}
impl Eq for FusionStats {}
mod f64_bits {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(value: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_f64(*value)
} else {
serializer.serialize_u64(value.to_bits())
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
f64::deserialize(deserializer)
} else {
u64::deserialize(deserializer).map(f64::from_bits)
}
}
}
impl FusionStats {
#[inline]
#[must_use]
pub fn subqueries_eliminated(&self) -> usize {
self.subqueries_total.saturating_sub(self.subqueries_unique)
}
}
#[must_use]
pub fn fuse_plans(plans: Vec<QueryPlan>) -> FusedPlanBatch {
let original_plans = plans.clone();
let total_plans = plans.len();
let (subqueries_total, subquery_plans) = collect_subquery_plans(&plans);
let subquery_batch = if subquery_plans.is_empty() {
None
} else {
Some(Box::new(fuse_plans(subquery_plans)))
};
let mut groups: Vec<FusionGroup> = Vec::new();
let mut prefix_index: HashMap<PlanNode, usize> = HashMap::new();
for (original_index, plan) in plans.into_iter().enumerate() {
let (prefix, tail) = split_prefix_and_tail(plan.root);
let fused_tail = FusedTail {
original_index,
tail,
};
if let Some(&idx) = prefix_index.get(&prefix) {
groups[idx].tails.push(fused_tail);
} else {
let group = FusionGroup {
prefix: prefix.clone(),
tails: vec![fused_tail],
};
prefix_index.insert(prefix, groups.len());
groups.push(group);
}
}
let fusion_groups = groups.len();
let scans_eliminated = total_plans.saturating_sub(fusion_groups);
let promoted_shared_candidates = collect_promoted_candidates(&original_plans, &groups);
let promoted_shared_nodes = materialize_shared_nodes(&promoted_shared_candidates);
let shared_nodes_promoted = promoted_shared_nodes.len();
let subqueries_unique = subquery_batch
.as_deref()
.map_or(0, FusedPlanBatch::total_plans);
let plan_tree_reduction_ratio =
estimate_plan_tree_reduction_ratio(&original_plans, &promoted_shared_candidates);
let stats = FusionStats {
total_plans,
fusion_groups,
scans_eliminated,
subqueries_total,
subqueries_unique,
shared_nodes_promoted,
plan_tree_reduction_ratio,
};
FusedPlanBatch {
groups,
subquery_batch,
stats,
shared_nodes: promoted_shared_nodes,
}
}
#[must_use]
pub fn fuse_single(plan: QueryPlan) -> FusedPlanBatch {
fuse_plans(vec![plan])
}
fn split_prefix_and_tail(root: PlanNode) -> (PlanNode, FusionTail) {
if let PlanNode::Chain { mut steps } = root {
match steps.len() {
0 => {
(PlanNode::Chain { steps }, FusionTail::Identity)
}
1 => {
let only = steps.pop().expect("len == 1");
if only.is_context_free() {
(only, FusionTail::Identity)
} else {
(PlanNode::Chain { steps: vec![only] }, FusionTail::Identity)
}
}
_ => {
let first = steps.remove(0);
if first.is_context_free() {
(
first,
FusionTail::ChainContinuation {
remaining_steps: steps,
},
)
} else {
let mut original = Vec::with_capacity(steps.len() + 1);
original.push(first);
original.extend(steps);
(PlanNode::Chain { steps: original }, FusionTail::Identity)
}
}
}
} else {
(root, FusionTail::Identity)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum PathSegment {
ChainStep(usize),
ChainPrefix(usize),
SetLeft,
SetRight,
PredicateValueSubquery,
PredicateAnd(usize),
PredicateOr(usize),
PredicateNot,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
struct SubtreePath(Vec<PathSegment>);
impl SubtreePath {
fn push(&mut self, segment: PathSegment) {
self.0.push(segment);
}
fn pop(&mut self) {
self.0.pop();
}
}
#[derive(Debug, Default)]
struct SharedSubtreeCollector {
candidates: HashMap<PlanNode, Vec<(usize, SubtreePath)>>,
}
impl SharedSubtreeCollector {
fn register(&mut self, plan_index: usize, path: &SubtreePath, plan: PlanNode) {
self.candidates
.entry(plan)
.or_default()
.push((plan_index, path.clone()));
}
}
#[derive(Debug, Clone)]
struct PromotedCandidate {
canonical_plan: PlanNode,
positions: Vec<(usize, SubtreePath)>,
}
fn collect_promoted_candidates(
plans: &[QueryPlan],
groups: &[FusionGroup],
) -> Vec<PromotedCandidate> {
let mut collector = SharedSubtreeCollector::default();
for (plan_index, plan) in plans.iter().enumerate() {
let mut path = SubtreePath::default();
walk_plan_for_shared_subtrees(&plan.root, plan_index, &mut path, &mut collector);
}
let promoted = promote_candidates(collector.candidates, groups);
sort_promoted_candidates_by_containment(promoted)
}
fn materialize_shared_nodes(candidates: &[PromotedCandidate]) -> Vec<SharedNode> {
candidates
.iter()
.enumerate()
.map(|(index, candidate)| SharedNode {
canonical_plan: candidate.canonical_plan.clone(),
consumers: candidate_consumers(&candidate.positions),
id: SharedNodeId(index as u32),
})
.collect()
}
fn is_independently_executable_root(plan: &PlanNode) -> bool {
match plan {
PlanNode::NodeScan { .. } | PlanNode::SetOp { .. } => true,
PlanNode::Chain { steps } => steps.first().is_some_and(PlanNode::is_context_free),
PlanNode::Filter { .. } | PlanNode::EdgeTraversal { .. } => false,
}
}
fn walk_plan_for_shared_subtrees(
plan: &PlanNode,
plan_index: usize,
path: &mut SubtreePath,
collector: &mut SharedSubtreeCollector,
) {
if is_independently_executable_root(plan) {
collector.register(plan_index, path, plan.clone());
}
match plan {
PlanNode::NodeScan { .. } | PlanNode::EdgeTraversal { .. } => {}
PlanNode::Chain { steps } => {
register_executable_chain_prefixes(steps, plan_index, path, collector);
for (step_index, step) in steps.iter().enumerate() {
path.push(PathSegment::ChainStep(step_index));
walk_plan_for_shared_subtrees(step, plan_index, path, collector);
path.pop();
}
}
PlanNode::SetOp { left, right, .. } => {
path.push(PathSegment::SetLeft);
walk_plan_for_shared_subtrees(left, plan_index, path, collector);
path.pop();
path.push(PathSegment::SetRight);
walk_plan_for_shared_subtrees(right, plan_index, path, collector);
path.pop();
}
PlanNode::Filter { predicate } => {
walk_predicate_for_shared_subtrees(predicate, plan_index, path, collector);
}
}
}
fn register_executable_chain_prefixes(
steps: &[PlanNode],
plan_index: usize,
path: &mut SubtreePath,
collector: &mut SharedSubtreeCollector,
) {
if !steps.first().is_some_and(PlanNode::is_context_free) {
return;
}
for prefix_len in 2..steps.len() {
path.push(PathSegment::ChainPrefix(prefix_len));
collector.register(
plan_index,
path,
PlanNode::Chain {
steps: steps[..prefix_len].to_vec(),
},
);
path.pop();
}
}
fn walk_predicate_for_shared_subtrees(
pred: &Predicate,
plan_index: usize,
path: &mut SubtreePath,
collector: &mut SharedSubtreeCollector,
) {
match pred {
Predicate::HasCaller
| Predicate::HasCallee
| Predicate::IsUnused
| Predicate::InFile(_)
| Predicate::InScope(_)
| Predicate::MatchesName(_)
| Predicate::Returns(_) => {}
Predicate::Callers(value)
| Predicate::Callees(value)
| Predicate::Imports(value)
| Predicate::Exports(value)
| Predicate::References(value)
| Predicate::Implements(value) => {
walk_predicate_value_for_shared_subtrees(value, plan_index, path, collector);
}
Predicate::And(list) => {
for (index, inner) in list.iter().enumerate() {
path.push(PathSegment::PredicateAnd(index));
walk_predicate_for_shared_subtrees(inner, plan_index, path, collector);
path.pop();
}
}
Predicate::Or(list) => {
for (index, inner) in list.iter().enumerate() {
path.push(PathSegment::PredicateOr(index));
walk_predicate_for_shared_subtrees(inner, plan_index, path, collector);
path.pop();
}
}
Predicate::Not(inner) => {
path.push(PathSegment::PredicateNot);
walk_predicate_for_shared_subtrees(inner, plan_index, path, collector);
path.pop();
}
}
}
fn walk_predicate_value_for_shared_subtrees(
value: &PredicateValue,
plan_index: usize,
path: &mut SubtreePath,
collector: &mut SharedSubtreeCollector,
) {
if let PredicateValue::Subquery(plan) = value {
path.push(PathSegment::PredicateValueSubquery);
walk_plan_for_shared_subtrees(plan, plan_index, path, collector);
path.pop();
}
}
fn promote_candidates(
candidates: HashMap<PlanNode, Vec<(usize, SubtreePath)>>,
groups: &[FusionGroup],
) -> Vec<PromotedCandidate> {
let existing_prefixes: HashSet<PlanNode> =
groups.iter().map(|group| group.prefix.clone()).collect();
let mut promoted: Vec<PromotedCandidate> = candidates
.into_iter()
.filter_map(|(canonical_plan, mut positions)| {
if positions.len() < 2 || existing_prefixes.contains(&canonical_plan) {
return None;
}
positions
.sort_by(|left, right| left.0.cmp(&right.0).then_with(|| left.1.cmp(&right.1)));
Some(PromotedCandidate {
canonical_plan,
positions,
})
})
.collect();
promoted.sort_by(|left, right| {
left.positions[0]
.0
.cmp(&right.positions[0].0)
.then_with(|| left.positions[0].1.cmp(&right.positions[0].1))
.then_with(|| {
left.canonical_plan
.operator_count()
.cmp(&right.canonical_plan.operator_count())
})
});
promoted
}
fn candidate_consumers(positions: &[(usize, SubtreePath)]) -> Vec<usize> {
let mut consumers = Vec::new();
for (plan_index, _) in positions {
if !consumers.contains(plan_index) {
consumers.push(*plan_index);
}
}
consumers
}
fn sort_promoted_candidates_by_containment(
candidates: Vec<PromotedCandidate>,
) -> Vec<PromotedCandidate> {
if candidates.len() <= 1 {
return candidates;
}
let mut outgoing: HashMap<usize, Vec<usize>> = HashMap::new();
let mut indegree = vec![0_usize; candidates.len()];
for (parent_index, parent) in candidates.iter().enumerate() {
for (child_index, child) in candidates.iter().enumerate() {
if parent_index == child_index {
continue;
}
if is_proper_subtree(&child.canonical_plan, &parent.canonical_plan) {
outgoing.entry(child_index).or_default().push(parent_index);
indegree[parent_index] += 1;
}
}
}
let mut ready: VecDeque<usize> = indegree
.iter()
.enumerate()
.filter_map(|(index, degree)| (*degree == 0).then_some(index))
.collect();
let mut order = Vec::with_capacity(candidates.len());
while let Some(index) = ready.pop_front() {
order.push(index);
if let Some(edges) = outgoing.get(&index) {
for &dependent in edges {
indegree[dependent] -= 1;
if indegree[dependent] == 0 {
ready.push_back(dependent);
}
}
}
}
if order.len() != candidates.len() {
return candidates;
}
order
.into_iter()
.map(|index| candidates[index].clone())
.collect()
}
fn is_proper_subtree(needle: &PlanNode, haystack: &PlanNode) -> bool {
let mut found = false;
visit_proper_plan_subtrees(haystack, &mut |candidate| {
if !found && candidate == needle {
found = true;
}
});
found
}
fn visit_proper_plan_subtrees(plan: &PlanNode, visitor: &mut dyn FnMut(&PlanNode)) {
match plan {
PlanNode::NodeScan { .. } | PlanNode::EdgeTraversal { .. } => {}
PlanNode::Filter { predicate } => {
visit_proper_predicate_subtrees(predicate, visitor);
}
PlanNode::SetOp { left, right, .. } => {
visitor(left);
visit_proper_plan_subtrees(left, visitor);
visitor(right);
visit_proper_plan_subtrees(right, visitor);
}
PlanNode::Chain { steps } => {
for prefix_len in 2..steps.len() {
let prefix = PlanNode::Chain {
steps: steps[..prefix_len].to_vec(),
};
visitor(&prefix);
}
for step in steps {
visitor(step);
visit_proper_plan_subtrees(step, visitor);
}
}
}
}
fn visit_proper_predicate_subtrees(predicate: &Predicate, visitor: &mut dyn FnMut(&PlanNode)) {
match predicate {
Predicate::HasCaller
| Predicate::HasCallee
| Predicate::IsUnused
| Predicate::InFile(_)
| Predicate::InScope(_)
| Predicate::MatchesName(_)
| Predicate::Returns(_) => {}
Predicate::Callers(value)
| Predicate::Callees(value)
| Predicate::Imports(value)
| Predicate::Exports(value)
| Predicate::References(value)
| Predicate::Implements(value) => {
if let PredicateValue::Subquery(plan) = value {
visitor(plan);
visit_proper_plan_subtrees(plan, visitor);
}
}
Predicate::And(list) | Predicate::Or(list) => {
for inner in list {
visit_proper_predicate_subtrees(inner, visitor);
}
}
Predicate::Not(inner) => {
visit_proper_predicate_subtrees(inner, visitor);
}
}
}
fn estimate_plan_tree_reduction_ratio(
plans: &[QueryPlan],
candidates: &[PromotedCandidate],
) -> f64 {
let total_nodes_before: usize = plans.iter().map(|plan| plan.root.operator_count()).sum();
if total_nodes_before == 0 {
return 0.0;
}
let total_saved_nodes: usize = candidates
.iter()
.map(|candidate| {
candidate
.canonical_plan
.operator_count()
.saturating_mul(candidate.positions.len().saturating_sub(1))
})
.sum();
let bounded_saved_nodes = total_saved_nodes.min(total_nodes_before);
bounded_saved_nodes as f64 / total_nodes_before as f64
}
fn collect_subquery_plans(plans: &[QueryPlan]) -> (usize, Vec<QueryPlan>) {
let mut total = 0_usize;
let mut seen: HashMap<PlanNode, ()> = HashMap::new();
let mut ordered: Vec<PlanNode> = Vec::new();
for plan in plans {
walk_plan_for_subqueries(&plan.root, &mut total, &mut seen, &mut ordered);
}
let dedup_plans = ordered.into_iter().map(QueryPlan::new).collect();
(total, dedup_plans)
}
fn walk_plan_for_subqueries(
node: &PlanNode,
total: &mut usize,
seen: &mut HashMap<PlanNode, ()>,
ordered: &mut Vec<PlanNode>,
) {
match node {
PlanNode::NodeScan { .. } | PlanNode::EdgeTraversal { .. } => {}
PlanNode::Filter { predicate } => {
walk_predicate_for_subqueries(predicate, total, seen, ordered);
}
PlanNode::SetOp { left, right, .. } => {
walk_plan_for_subqueries(left, total, seen, ordered);
walk_plan_for_subqueries(right, total, seen, ordered);
}
PlanNode::Chain { steps } => {
for step in steps {
walk_plan_for_subqueries(step, total, seen, ordered);
}
}
}
}
fn walk_predicate_for_subqueries(
pred: &Predicate,
total: &mut usize,
seen: &mut HashMap<PlanNode, ()>,
ordered: &mut Vec<PlanNode>,
) {
match pred {
Predicate::HasCaller
| Predicate::HasCallee
| Predicate::IsUnused
| Predicate::InFile(_)
| Predicate::InScope(_)
| Predicate::MatchesName(_)
| Predicate::Returns(_) => {}
Predicate::Callers(v)
| Predicate::Callees(v)
| Predicate::Imports(v)
| Predicate::Exports(v)
| Predicate::References(v)
| Predicate::Implements(v) => {
walk_predicate_value_for_subqueries(v, total, seen, ordered);
}
Predicate::And(list) | Predicate::Or(list) => {
for inner in list {
walk_predicate_for_subqueries(inner, total, seen, ordered);
}
}
Predicate::Not(inner) => {
walk_predicate_for_subqueries(inner, total, seen, ordered);
}
}
}
fn walk_predicate_value_for_subqueries(
value: &PredicateValue,
total: &mut usize,
seen: &mut HashMap<PlanNode, ()>,
ordered: &mut Vec<PlanNode>,
) {
if let PredicateValue::Subquery(inner) = value {
*total += 1;
let key: PlanNode = (**inner).clone();
if !seen.contains_key(&key) {
seen.insert(key.clone(), ());
ordered.push(key);
}
walk_plan_for_subqueries(inner, total, seen, ordered);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::planner::ir::{Direction, MatchMode, PathPattern, SetOperation, StringPattern};
use sqry_core::graph::unified::node::kind::NodeKind;
fn make_scan(kind: NodeKind) -> PlanNode {
PlanNode::NodeScan {
kind: Some(kind),
visibility: None,
name_pattern: None,
}
}
fn make_filter_has_caller() -> PlanNode {
PlanNode::Filter {
predicate: Predicate::HasCaller,
}
}
#[test]
fn split_chain_with_multiple_steps() {
let chain = PlanNode::Chain {
steps: vec![
make_scan(NodeKind::Function),
make_filter_has_caller(),
PlanNode::EdgeTraversal {
direction: Direction::Forward,
edge_kind: None,
max_depth: 1,
},
],
};
let (prefix, tail) = split_prefix_and_tail(chain);
assert_eq!(prefix, make_scan(NodeKind::Function));
match tail {
FusionTail::ChainContinuation { remaining_steps } => {
assert_eq!(remaining_steps.len(), 2);
}
FusionTail::Identity => panic!("expected ChainContinuation"),
}
}
#[test]
fn split_chain_with_one_step_collapses_to_identity() {
let chain = PlanNode::Chain {
steps: vec![make_scan(NodeKind::Class)],
};
let (prefix, tail) = split_prefix_and_tail(chain);
assert_eq!(prefix, make_scan(NodeKind::Class));
assert_eq!(tail, FusionTail::Identity);
}
#[test]
fn split_standalone_scan_is_identity() {
let scan = make_scan(NodeKind::Method);
let (prefix, tail) = split_prefix_and_tail(scan.clone());
assert_eq!(prefix, scan);
assert_eq!(tail, FusionTail::Identity);
}
#[test]
fn split_standalone_setop_is_identity() {
let set = PlanNode::SetOp {
op: SetOperation::Union,
left: Box::new(make_scan(NodeKind::Function)),
right: Box::new(make_scan(NodeKind::Method)),
};
let (prefix, tail) = split_prefix_and_tail(set.clone());
assert_eq!(prefix, set);
assert_eq!(tail, FusionTail::Identity);
}
#[test]
fn split_malformed_chain_with_filter_first_passes_through() {
let chain = PlanNode::Chain {
steps: vec![make_filter_has_caller(), make_scan(NodeKind::Function)],
};
let original = chain.clone();
let (prefix, tail) = split_prefix_and_tail(chain);
assert_eq!(prefix, original);
assert_eq!(tail, FusionTail::Identity);
}
#[test]
fn split_empty_chain_passes_through() {
let chain = PlanNode::Chain { steps: vec![] };
let (prefix, tail) = split_prefix_and_tail(chain.clone());
assert_eq!(prefix, chain);
assert_eq!(tail, FusionTail::Identity);
}
#[test]
fn collect_subquery_plans_empty_when_no_filters() {
let plans = vec![
QueryPlan::new(make_scan(NodeKind::Function)),
QueryPlan::new(make_scan(NodeKind::Method)),
];
let (total, dedup) = collect_subquery_plans(&plans);
assert_eq!(total, 0);
assert!(dedup.is_empty());
}
#[test]
fn collect_subquery_plans_dedupes_identical_subqueries() {
let inner = make_scan(NodeKind::Method);
let pred = |v: PlanNode| Predicate::Callers(PredicateValue::Subquery(Box::new(v)));
let plan_a = QueryPlan::new(PlanNode::Chain {
steps: vec![
make_scan(NodeKind::Function),
PlanNode::Filter {
predicate: pred(inner.clone()),
},
],
});
let plan_b = QueryPlan::new(PlanNode::Chain {
steps: vec![
make_scan(NodeKind::Class),
PlanNode::Filter {
predicate: pred(inner.clone()),
},
],
});
let (total, dedup) = collect_subquery_plans(&[plan_a, plan_b]);
assert_eq!(total, 2);
assert_eq!(dedup.len(), 1);
assert_eq!(dedup[0].root(), &inner);
}
#[test]
fn collect_subquery_plans_walks_all_predicate_arms() {
let inner_a = make_scan(NodeKind::Function);
let inner_b = make_scan(NodeKind::Method);
let sub_a = || PredicateValue::Subquery(Box::new(inner_a.clone()));
let sub_b = || PredicateValue::Subquery(Box::new(inner_b.clone()));
let predicate = Predicate::And(vec![
Predicate::Or(vec![
Predicate::Callers(sub_a()),
Predicate::Callees(sub_a()),
Predicate::Imports(sub_b()),
Predicate::Exports(sub_b()),
Predicate::References(sub_a()),
Predicate::Implements(sub_b()),
]),
Predicate::Not(Box::new(Predicate::Callers(sub_a()))),
]);
let plan = QueryPlan::new(PlanNode::Chain {
steps: vec![make_scan(NodeKind::Class), PlanNode::Filter { predicate }],
});
let (total, dedup) = collect_subquery_plans(&[plan]);
assert_eq!(total, 7);
assert_eq!(dedup.len(), 2);
}
#[test]
fn collect_subquery_plans_recurses_into_nested_subqueries() {
let leaf = make_scan(NodeKind::Function);
let nested_pred = Predicate::Callers(PredicateValue::Subquery(Box::new(leaf.clone())));
let mid_plan = PlanNode::Chain {
steps: vec![
make_scan(NodeKind::Method),
PlanNode::Filter {
predicate: nested_pred,
},
],
};
let outer = QueryPlan::new(PlanNode::Chain {
steps: vec![
make_scan(NodeKind::Class),
PlanNode::Filter {
predicate: Predicate::Callees(PredicateValue::Subquery(Box::new(mid_plan))),
},
],
});
let (total, dedup) = collect_subquery_plans(&[outer]);
assert_eq!(total, 2);
assert_eq!(dedup.len(), 2);
}
#[test]
fn fused_tail_reconstruct_identity() {
let scan = make_scan(NodeKind::Function);
let tail = FusedTail {
original_index: 0,
tail: FusionTail::Identity,
};
let plan = tail.reconstruct(&scan);
assert_eq!(plan.root(), &scan);
}
#[test]
fn fused_tail_reconstruct_chain_continuation() {
let scan = make_scan(NodeKind::Function);
let f = make_filter_has_caller();
let tail = FusedTail {
original_index: 7,
tail: FusionTail::ChainContinuation {
remaining_steps: vec![f.clone()],
},
};
let plan = tail.reconstruct(&scan);
match plan.root() {
PlanNode::Chain { steps } => {
assert_eq!(steps.len(), 2);
assert_eq!(&steps[0], &scan);
assert_eq!(&steps[1], &f);
}
other => panic!("expected Chain, got {other:?}"),
}
}
#[test]
fn fusion_stats_subqueries_eliminated() {
let stats = FusionStats {
total_plans: 5,
fusion_groups: 3,
scans_eliminated: 2,
subqueries_total: 7,
subqueries_unique: 3,
shared_nodes_promoted: 0,
plan_tree_reduction_ratio: 0.0,
};
assert_eq!(stats.subqueries_eliminated(), 4);
}
#[test]
fn fusion_stats_subqueries_eliminated_saturates() {
let stats = FusionStats {
total_plans: 0,
fusion_groups: 0,
scans_eliminated: 0,
subqueries_total: 1,
subqueries_unique: 5,
shared_nodes_promoted: 0,
plan_tree_reduction_ratio: 0.0,
};
assert_eq!(stats.subqueries_eliminated(), 0);
}
#[test]
fn fuse_single_round_trip() {
let scan = make_scan(NodeKind::Function);
let plan = QueryPlan::new(scan.clone());
let batch = fuse_single(plan.clone());
assert_eq!(batch.len(), 1);
assert_eq!(batch.stats().total_plans, 1);
assert_eq!(batch.stats().scans_eliminated, 0);
let recovered = batch.input_plans();
assert_eq!(recovered, vec![plan]);
}
#[test]
fn helpers_do_not_double_count_distinct_pattern_arms() {
let predicate = Predicate::And(vec![
Predicate::Callers(PredicateValue::Pattern(StringPattern::glob("foo*"))),
Predicate::References(PredicateValue::Pattern(StringPattern {
raw: "bar".into(),
mode: MatchMode::Exact,
case_insensitive: true,
})),
Predicate::InFile(PathPattern::new("src/**")),
]);
let plan = QueryPlan::new(PlanNode::Chain {
steps: vec![
make_scan(NodeKind::Function),
PlanNode::Filter { predicate },
],
});
let (total, dedup) = collect_subquery_plans(&[plan]);
assert_eq!(total, 0);
assert!(dedup.is_empty());
}
}