pub(crate) mod path_analysis;
pub(crate) mod plan;
pub(crate) mod rule;
pub(crate) mod rules;
use std::cell::RefCell;
use compact_str::CompactString;
use crate::graph::GraphStorage;
use crate::traversal::context::ExecutionOptions;
use crate::traversal::step::GraphStep;
pub(crate) use plan::{LevelPlan, Plan};
pub(crate) use rule::OptimizationRule;
pub(crate) use rules::add_property_fold::AddPropertyFoldRule;
pub(crate) use rules::barrier_pushdown::BarrierPushdownRule;
pub(crate) use rules::count_pushdown::CountPushdownRule;
pub(crate) use rules::filter_pushdown::SourceFilterPushdownRule;
pub(crate) use rules::filter_reorder::FilterReorderRule;
pub(crate) use rules::group_count_pushdown::GroupCountPushdownRule;
pub(crate) use rules::has_id_pushdown::HasIdPushdownRule;
pub(crate) use rules::lazy_barrier::LazyBarrierRule;
pub(crate) use rules::where_unnest::WhereUnnestRule;
pub(crate) const KNOWN_RULES: &[(&str, bool)] = &[
("where_unnest", true),
("add_property_fold", true),
("filter_reorder", true),
("has_id_pushdown", true),
("source_filter_pushdown", true),
("barrier_pushdown", true),
("group_count_pushdown", true),
("count_pushdown", true),
("lazy_barrier", true),
];
fn new_where_unnest<'g, S: GraphStorage + 'g>() -> Box<dyn OptimizationRule<'g, S> + 'g> {
Box::new(WhereUnnestRule::new())
}
fn new_add_property_fold<'g, S: GraphStorage + 'g>() -> Box<dyn OptimizationRule<'g, S> + 'g> {
Box::new(AddPropertyFoldRule::new())
}
fn new_filter_reorder<'g, S: GraphStorage + 'g>() -> Box<dyn OptimizationRule<'g, S> + 'g> {
Box::new(FilterReorderRule::new())
}
fn new_has_id_pushdown<'g, S: GraphStorage + 'g>() -> Box<dyn OptimizationRule<'g, S> + 'g> {
Box::new(HasIdPushdownRule::new())
}
fn new_source_filter_pushdown<'g, S: GraphStorage + 'g>() -> Box<dyn OptimizationRule<'g, S> + 'g> {
Box::new(SourceFilterPushdownRule::new())
}
fn new_barrier_pushdown<'g, S: GraphStorage + 'g>() -> Box<dyn OptimizationRule<'g, S> + 'g> {
Box::new(BarrierPushdownRule::new())
}
fn new_group_count_pushdown<'g, S: GraphStorage + 'g>() -> Box<dyn OptimizationRule<'g, S> + 'g> {
Box::new(GroupCountPushdownRule::new())
}
fn new_lazy_barrier<'g, S: GraphStorage + 'g>() -> Box<dyn OptimizationRule<'g, S> + 'g> {
Box::new(LazyBarrierRule::new())
}
fn new_count_pushdown<'g, S: GraphStorage + 'g>() -> Box<dyn OptimizationRule<'g, S> + 'g> {
Box::new(CountPushdownRule::new())
}
type RegistryEntry<'g, S> = (
&'static str,
bool,
fn() -> Box<dyn OptimizationRule<'g, S> + 'g>,
);
pub(crate) struct TraversalOptimizer<'g, S: GraphStorage + 'g> {
rules: Vec<Box<dyn OptimizationRule<'g, S> + 'g>>,
diagnostics: bool,
fired: RefCell<Vec<&'static str>>,
}
impl<'g, S: GraphStorage + 'g> Default for TraversalOptimizer<'g, S> {
fn default() -> Self {
Self {
rules: Vec::new(),
diagnostics: true,
fired: RefCell::new(Vec::new()),
}
}
}
impl<'g, S: GraphStorage + 'g> TraversalOptimizer<'g, S> {
pub fn new() -> Self {
Self::default()
}
pub fn with_diagnostics(mut self, diagnostics: bool) -> Self {
self.diagnostics = diagnostics;
self
}
pub(crate) fn registry() -> Vec<RegistryEntry<'g, S>> {
vec![
("where_unnest", true, new_where_unnest::<'g, S>),
("add_property_fold", true, new_add_property_fold::<'g, S>),
("filter_reorder", true, new_filter_reorder::<'g, S>),
("has_id_pushdown", true, new_has_id_pushdown::<'g, S>),
(
"source_filter_pushdown",
true,
new_source_filter_pushdown::<'g, S>,
),
("barrier_pushdown", true, new_barrier_pushdown::<'g, S>),
(
"group_count_pushdown",
true,
new_group_count_pushdown::<'g, S>,
),
("count_pushdown", true, new_count_pushdown::<'g, S>),
("lazy_barrier", true, new_lazy_barrier::<'g, S>),
]
}
#[allow(dead_code)]
pub fn default_pipeline() -> Self {
Self::default_pipeline_with(&ExecutionOptions::default())
}
pub fn default_pipeline_with(options: &ExecutionOptions) -> Self {
let mut optimizer = Self::new();
for (name, default_on, ctor) in Self::registry() {
let disabled = options.optimizer_disabled.contains(name);
let enabled = options.optimizer_enabled.contains(name);
if (default_on && !disabled) || (!default_on && enabled) {
optimizer.add_rule(ctor());
}
}
optimizer
}
#[allow(dead_code)]
pub fn add_rule(&mut self, rule: Box<dyn OptimizationRule<'g, S> + 'g>) {
self.rules.push(rule);
}
pub fn apply(&self, steps: &mut Vec<GraphStep<'g, S>>) -> Option<LevelPlan<'g, S>> {
let mut plan = Plan::from_steps(std::mem::take(steps), self.diagnostics);
for step in plan.steps_mut() {
step.optimize_nested(self);
}
for rule in &self.rules {
if rule.apply(&mut plan) {
self.fired.borrow_mut().push(rule.name());
}
}
let (optimized, level) = plan.into_parts();
*steps = optimized;
level
}
pub fn fired_rules(&self) -> Vec<&'static str> {
let fired = self.fired.borrow();
let mut names: Vec<&'static str> = Vec::with_capacity(fired.len());
for name in fired.iter() {
if !names.contains(name) {
names.push(*name);
}
}
names
}
pub fn analyze_paths(
&self,
steps: &mut [GraphStep<'g, S>],
enabled: bool,
predeclared_side_effects: &std::collections::HashMap<
CompactString,
crate::traversal::side_effect::SideEffectValue<S>,
>,
) -> crate::error::TraverserResult<(Vec<path_analysis::PathMode>, path_analysis::PlanEffects)>
{
path_analysis::PathAnalysis::analyze_query(enabled, steps, predeclared_side_effects)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::TraversalGraph;
struct DummyRule {
applied: std::sync::atomic::AtomicBool,
}
impl DummyRule {
fn new() -> Self {
Self {
applied: std::sync::atomic::AtomicBool::new(false),
}
}
}
impl<'g> OptimizationRule<'g, TraversalGraph> for DummyRule {
fn name(&self) -> &'static str {
"dummy_rule"
}
fn apply(&self, _plan: &mut Plan<'g, TraversalGraph>) -> bool {
self.applied
.store(true, std::sync::atomic::Ordering::SeqCst);
true
}
}
#[test]
fn test_empty_optimizer_leaves_steps_untouched() {
let optimizer = TraversalOptimizer::<TraversalGraph>::default_pipeline();
let mut steps: Vec<GraphStep<TraversalGraph>> = Vec::new();
optimizer.apply(&mut steps);
assert!(steps.is_empty());
}
#[test]
fn test_optimizer_executes_rules() {
let mut optimizer = TraversalOptimizer::<TraversalGraph>::new();
let rule = Box::new(DummyRule::new());
assert_eq!(rule.name(), "dummy_rule");
optimizer.add_rule(rule);
let mut steps: Vec<GraphStep<TraversalGraph>> = Vec::new();
optimizer.apply(&mut steps);
}
#[test]
fn test_optimizer_executes_rules_records_the_fired_rule() {
let mut optimizer = TraversalOptimizer::<TraversalGraph>::new();
optimizer.add_rule(Box::new(DummyRule::new()));
let mut steps: Vec<GraphStep<TraversalGraph>> = Vec::new();
optimizer.apply(&mut steps);
assert_eq!(optimizer.fired_rules(), vec!["dummy_rule"]);
}
#[test]
fn test_registry_matches_known_rules() {
let registry = TraversalOptimizer::<TraversalGraph>::registry();
assert_eq!(registry.len(), KNOWN_RULES.len());
for ((reg_name, reg_default_on, ctor), (known_name, known_default_on)) in
registry.iter().zip(KNOWN_RULES.iter())
{
assert_eq!(reg_name, known_name);
assert_eq!(reg_default_on, known_default_on);
let rule = ctor();
assert_eq!(rule.name(), *known_name);
}
}
#[test]
fn test_default_pipeline_with_honors_disabled_set() {
let mut options = ExecutionOptions::default();
options
.optimizer_disabled
.insert("filter_reorder".to_string());
let optimizer = TraversalOptimizer::<TraversalGraph>::default_pipeline_with(&options);
assert!(!optimizer.rules.iter().any(|r| r.name() == "filter_reorder"));
assert!(optimizer.rules.iter().any(|r| r.name() == "count_pushdown"));
}
}