use super::ast::*;
use crate::datatypes::values::Value;
use crate::graph::schema::DirGraph;
use std::collections::{HashMap, HashSet};
mod annotations;
mod invariants;
#[cfg(debug_assertions)]
use invariants::debug_check_invariants;
pub mod cost_model;
pub mod fusion;
pub mod index_selection;
pub mod join_order;
mod nested;
mod node_anchor;
pub mod rel_predicate_pushdown;
pub mod schema_check;
pub mod simplification;
mod var_length_lowering;
mod with_boundary;
use annotations::{
pass_mark_disjoint_fixed_trails, pass_mark_fast_var_length_paths,
pass_mark_skip_target_type_check,
};
use cost_model::reorder_predicates_by_cost;
use fusion::{
fuse_anchored_edge_count, fuse_count_short_circuits, fuse_match_return_aggregate,
fuse_match_with_aggregate, fuse_match_with_aggregate_top_k, fuse_node_scan_aggregate,
fuse_node_scan_top_k, fuse_optional_match_aggregate, fuse_order_by_top_k, fuse_spatial_join,
fuse_text_bm25_order_limit, fuse_vector_score_order_limit, mark_return_lazy_eligible,
};
use index_selection::push_where_into_match;
use join_order::{
optimize_pattern_start_node, reorder_cyclic_pattern_edges, reorder_match_clauses,
reorder_match_patterns,
};
pub(crate) use nested::import_pattern_anchors_in_arm;
use node_anchor::anchor_element_id;
use rel_predicate_pushdown::extract_pushable_rel_predicates_with_params;
use var_length_lowering::lower_fixed_var_length_hops;
use with_boundary::{
pass_fold_aliasing_with, pass_hoist_terminal_return_over_with_top_k, pass_hoist_with_where,
};
use simplification::{
desugar_multi_match_return_aggregate, fold_or_to_in, fold_pass_through_with,
narrow_unwind_source, push_distinct_into_match, push_limit_into_aggregate,
push_limit_into_match, rewrite_count_bound_var_to_star,
};
pub struct PassCtx<'a> {
pub graph: &'a DirGraph,
pub params: &'a HashMap<String, Value>,
pub disabled: &'a HashSet<String>,
initial_scope: &'a HashSet<String>,
global_scope: &'a HashSet<String>,
}
type PassFn = fn(&mut CypherQuery, &PassCtx);
pub const PASSES: &[(&str, PassFn)] = &[
(
"optimize_nested_queries",
nested::pass_optimize_nested_queries,
),
(
"lower_fixed_var_length_hops",
pass_lower_fixed_var_length_hops,
),
(
"rewrite_count_bound_var_to_star",
pass_rewrite_count_bound_var_to_star,
),
("hoist_with_where", pass_hoist_with_where),
("push_where_into_match.1", pass_push_where_into_match),
("fold_or_to_in", pass_fold_or_to_in),
("push_where_into_match.2", pass_push_where_into_match),
("anchor_element_id", pass_anchor_element_id),
(
"extract_pushable_rel_predicates",
pass_extract_pushable_rel_predicates,
),
("fold_pass_through_with", pass_fold_pass_through_with),
("fold_aliasing_with", pass_fold_aliasing_with),
(
"hoist_terminal_return_over_with_top_k",
pass_hoist_terminal_return_over_with_top_k,
),
("narrow_unwind_source", pass_narrow_unwind_source),
(
"desugar_multi_match_return_aggregate",
pass_desugar_multi_match_return_aggregate,
),
("fuse_spatial_join", pass_fuse_spatial_join),
("reorder_match_clauses", pass_reorder_match_clauses),
(
"reorder_cyclic_pattern_edges",
pass_reorder_cyclic_pattern_edges,
),
(
"optimize_pattern_start_node",
pass_optimize_pattern_start_node,
),
("reorder_match_patterns", pass_reorder_match_patterns),
("push_limit_into_match", pass_push_limit_into_match),
("push_limit_into_aggregate", pass_push_limit_into_aggregate),
("push_distinct_into_match", pass_push_distinct_into_match),
("fuse_anchored_edge_count", pass_fuse_anchored_edge_count),
("fuse_count_short_circuits", pass_fuse_count_short_circuits),
(
"fuse_optional_match_aggregate",
pass_fuse_optional_match_aggregate,
),
(
"fuse_match_return_aggregate",
pass_fuse_match_return_aggregate,
),
("fuse_match_with_aggregate", pass_fuse_match_with_aggregate),
(
"fuse_match_with_aggregate_top_k",
pass_fuse_match_with_aggregate_top_k,
),
("fuse_node_scan_aggregate", pass_fuse_node_scan_aggregate),
("fuse_node_scan_top_k", pass_fuse_node_scan_top_k),
(
"fuse_vector_score_order_limit",
pass_fuse_vector_score_order_limit,
),
(
"fuse_text_bm25_order_limit",
pass_fuse_text_bm25_order_limit,
),
("fuse_order_by_top_k", pass_fuse_order_by_top_k),
(
"reorder_predicates_by_cost",
pass_reorder_predicates_by_cost,
),
(
"mark_fast_var_length_paths",
pass_mark_fast_var_length_paths,
),
(
"mark_disjoint_fixed_trails",
pass_mark_disjoint_fixed_trails,
),
(
"mark_skip_target_type_check",
pass_mark_skip_target_type_check,
),
];
pub fn is_known_pass(name: &str) -> bool {
PASSES.iter().any(|(n, _)| *n == name)
}
pub fn all_pass_names() -> Vec<String> {
PASSES.iter().map(|(n, _)| n.to_string()).collect()
}
pub fn mark_lazy_eligibility(query: &mut CypherQuery) {
if query.clauses.iter().any(|c| matches!(c, Clause::Union(_))) {
return;
}
if query.clauses.iter().any(|c| {
matches!(
c,
Clause::Create(_)
| Clause::Set(_)
| Clause::Delete(_)
| Clause::Remove(_)
| Clause::Merge(_)
)
}) {
return;
}
mark_return_lazy_eligible(query);
}
pub fn optimize(query: &mut CypherQuery, graph: &DirGraph, params: &HashMap<String, Value>) {
optimize_with_disabled(query, graph, params, empty_disabled_set());
}
pub fn empty_disabled_set() -> &'static HashSet<String> {
static EMPTY: std::sync::OnceLock<HashSet<String>> = std::sync::OnceLock::new();
EMPTY.get_or_init(HashSet::new)
}
pub fn optimize_with_disabled(
query: &mut CypherQuery,
graph: &DirGraph,
params: &HashMap<String, Value>,
disabled: &HashSet<String>,
) {
let empty_scope = empty_disabled_set();
optimize_with_disabled_scoped(query, graph, params, disabled, empty_scope, empty_scope);
}
fn optimize_with_disabled_scoped(
query: &mut CypherQuery,
graph: &DirGraph,
params: &HashMap<String, Value>,
disabled: &HashSet<String>,
initial_scope: &HashSet<String>,
global_scope: &HashSet<String>,
) {
query.optimizer_tags.clear();
let ctx = PassCtx {
graph,
params,
disabled,
initial_scope,
global_scope,
};
for (name, pass_fn) in PASSES {
if disabled.contains(*name) {
continue;
}
let before = query.explain.then(|| format!("{:?}", query.clauses));
pass_fn(query, &ctx);
if before.is_some_and(|snapshot| snapshot != format!("{:?}", query.clauses)) {
query.optimizer_tags.push((*name).to_string());
}
#[cfg(debug_assertions)]
debug_check_invariants(query, name);
}
}
fn pass_lower_fixed_var_length_hops(query: &mut CypherQuery, _ctx: &PassCtx) {
lower_fixed_var_length_hops(query)
}
fn pass_push_where_into_match(query: &mut CypherQuery, ctx: &PassCtx) {
push_where_into_match(query, ctx.params)
}
fn pass_anchor_element_id(query: &mut CypherQuery, ctx: &PassCtx) {
anchor_element_id(query, ctx.params)
}
fn pass_fold_or_to_in(query: &mut CypherQuery, _ctx: &PassCtx) {
fold_or_to_in(query)
}
fn pass_rewrite_count_bound_var_to_star(query: &mut CypherQuery, _ctx: &PassCtx) {
rewrite_count_bound_var_to_star(query)
}
fn pass_extract_pushable_rel_predicates(query: &mut CypherQuery, ctx: &PassCtx) {
extract_pushable_rel_predicates_with_params(query, ctx.params)
}
fn pass_fold_pass_through_with(query: &mut CypherQuery, _ctx: &PassCtx) {
fold_pass_through_with(query)
}
fn pass_narrow_unwind_source(query: &mut CypherQuery, _ctx: &PassCtx) {
narrow_unwind_source(query)
}
fn pass_desugar_multi_match_return_aggregate(query: &mut CypherQuery, _ctx: &PassCtx) {
desugar_multi_match_return_aggregate(query)
}
fn pass_fuse_spatial_join(query: &mut CypherQuery, ctx: &PassCtx) {
fuse_spatial_join(query, ctx.graph)
}
fn pass_reorder_match_clauses(query: &mut CypherQuery, ctx: &PassCtx) {
reorder_match_clauses(query, ctx.graph)
}
fn pass_reorder_cyclic_pattern_edges(query: &mut CypherQuery, ctx: &PassCtx) {
reorder_cyclic_pattern_edges(query, ctx.graph)
}
fn pass_optimize_pattern_start_node(query: &mut CypherQuery, ctx: &PassCtx) {
optimize_pattern_start_node(query, ctx.graph)
}
fn pass_reorder_match_patterns(query: &mut CypherQuery, ctx: &PassCtx) {
reorder_match_patterns(query, ctx.graph)
}
fn pass_push_limit_into_match(query: &mut CypherQuery, ctx: &PassCtx) {
push_limit_into_match(query, ctx.graph)
}
fn pass_push_limit_into_aggregate(query: &mut CypherQuery, ctx: &PassCtx) {
push_limit_into_aggregate(query, ctx.graph)
}
fn pass_push_distinct_into_match(query: &mut CypherQuery, _ctx: &PassCtx) {
push_distinct_into_match(query)
}
fn pass_fuse_anchored_edge_count(query: &mut CypherQuery, ctx: &PassCtx) {
fuse_anchored_edge_count(query, ctx.graph)
}
fn pass_fuse_count_short_circuits(query: &mut CypherQuery, ctx: &PassCtx) {
fuse_count_short_circuits(query, ctx.graph.has_secondary_labels, ctx.graph)
}
fn pass_fuse_optional_match_aggregate(query: &mut CypherQuery, _ctx: &PassCtx) {
fuse_optional_match_aggregate(query)
}
fn pass_fuse_match_return_aggregate(query: &mut CypherQuery, ctx: &PassCtx) {
fuse_match_return_aggregate(query, ctx.graph)
}
fn pass_fuse_match_with_aggregate(query: &mut CypherQuery, ctx: &PassCtx) {
fuse_match_with_aggregate(query, ctx.graph)
}
fn pass_fuse_match_with_aggregate_top_k(query: &mut CypherQuery, _ctx: &PassCtx) {
fuse_match_with_aggregate_top_k(query)
}
fn pass_fuse_node_scan_aggregate(query: &mut CypherQuery, ctx: &PassCtx) {
fuse_node_scan_aggregate(query, ctx.params)
}
fn pass_fuse_node_scan_top_k(query: &mut CypherQuery, ctx: &PassCtx) {
fuse_node_scan_top_k(query, ctx.params)
}
fn pass_fuse_vector_score_order_limit(query: &mut CypherQuery, _ctx: &PassCtx) {
fuse_vector_score_order_limit(query)
}
fn pass_fuse_text_bm25_order_limit(query: &mut CypherQuery, _ctx: &PassCtx) {
fuse_text_bm25_order_limit(query)
}
fn pass_fuse_order_by_top_k(query: &mut CypherQuery, _ctx: &PassCtx) {
fuse_order_by_top_k(query)
}
fn pass_reorder_predicates_by_cost(query: &mut CypherQuery, _ctx: &PassCtx) {
reorder_predicates_by_cost(query)
}
#[cfg(test)]
#[path = "planner_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "planner_fusion_tests.rs"]
mod fusion_tests;
#[cfg(test)]
mod nested_tests;
#[cfg(test)]
#[path = "with_boundary_tests.rs"]
mod with_boundary_tests;