use std::time::Instant;
#[derive(Debug, Clone, PartialEq)]
pub struct OptimizationDecision {
pub traversal_start: TraversalStartDecision,
pub index_selection: IndexSelectionDecision,
pub join_order: JoinOrderDecision,
pub rewrite_rules: Vec<RewriteRuleId>,
pub stats_version: u64,
pub index_version: u64,
pub created_at: Instant,
}
impl OptimizationDecision {
pub fn new(
traversal_start: TraversalStartDecision,
index_selection: IndexSelectionDecision,
join_order: JoinOrderDecision,
stats_version: u64,
index_version: u64,
) -> Self {
Self {
traversal_start,
index_selection,
join_order,
rewrite_rules: Vec::new(),
stats_version,
index_version,
created_at: Instant::now(),
}
}
pub fn is_valid(&self, current_stats_version: u64, current_index_version: u64) -> bool {
self.stats_version == current_stats_version && self.index_version == current_index_version
}
pub fn age_secs(&self) -> u64 {
self.created_at.elapsed().as_secs()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TraversalStartDecision {
pub start_variable: String,
pub access_path: AccessPath,
pub estimated_selectivity_scaled: u64, pub estimated_cost_scaled: u64, }
impl TraversalStartDecision {
pub fn new(
start_variable: String,
access_path: AccessPath,
estimated_selectivity: f64,
estimated_cost: f64,
) -> Self {
Self {
start_variable,
access_path,
estimated_selectivity_scaled: (estimated_selectivity * 1_000_000.0) as u64,
estimated_cost_scaled: (estimated_cost * 1_000_000.0) as u64,
}
}
pub fn estimated_selectivity(&self) -> f64 {
self.estimated_selectivity_scaled as f64 / 1_000_000.0
}
pub fn estimated_cost(&self) -> f64 {
self.estimated_cost_scaled as f64 / 1_000_000.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AccessPath {
ExplicitVid {
vid_description: String,
},
IndexScan {
index_name: String,
property_name: String,
predicate_description: String,
},
TagIndex {
tag_name: String,
},
FullScan {
entity_type: EntityType,
},
VariableBinding {
source_variable: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum EntityType {
Vertex {
tag_name: Option<String>,
},
Edge {
edge_type: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IndexSelectionDecision {
pub entity_indexes: Vec<EntityIndexChoice>,
}
impl IndexSelectionDecision {
pub fn empty() -> Self {
Self {
entity_indexes: Vec::new(),
}
}
pub fn add_choice(&mut self, choice: EntityIndexChoice) {
self.entity_indexes.push(choice);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EntityIndexChoice {
pub entity_name: String,
pub selected_index: IndexChoice,
pub selectivity_scaled: u64,
}
impl EntityIndexChoice {
pub fn new(entity_name: String, selected_index: IndexChoice, selectivity: f64) -> Self {
Self {
entity_name,
selected_index,
selectivity_scaled: (selectivity * 1_000_000.0) as u64,
}
}
pub fn selectivity(&self) -> f64 {
self.selectivity_scaled as f64 / 1_000_000.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IndexChoice {
PrimaryKey,
PropertyIndex {
property_name: String,
index_name: String,
},
CompositeIndex {
property_names: Vec<String>,
index_name: String,
},
None,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct JoinOrderDecision {
pub join_order: Vec<String>,
pub join_algorithms: Vec<JoinAlgorithm>,
}
impl JoinOrderDecision {
pub fn empty() -> Self {
Self {
join_order: Vec::new(),
join_algorithms: Vec::new(),
}
}
pub fn add_join_step(&mut self, variable: String, algorithm: JoinAlgorithm) {
self.join_order.push(variable);
self.join_algorithms.push(algorithm);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum JoinAlgorithm {
HashJoin {
build_side: String,
probe_side: String,
},
NestedLoopJoin {
outer: String,
inner: String,
},
IndexJoin {
indexed_side: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RewriteRuleId {
PushFilterDown,
PushProjectDown,
PushLimitDown,
MergeOperations,
EliminateRedundancy,
AggregateOptimization,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_traversal_start_decision() {
let decision = TraversalStartDecision::new(
"n".to_string(),
AccessPath::TagIndex {
tag_name: "Person".to_string(),
},
0.1,
100.0,
);
assert_eq!(decision.start_variable, "n");
assert!((decision.estimated_selectivity() - 0.1).abs() < 0.0001);
assert!((decision.estimated_cost() - 100.0).abs() < 0.0001);
}
#[test]
fn test_optimization_decision_validity() {
let decision = OptimizationDecision::new(
TraversalStartDecision::new(
"n".to_string(),
AccessPath::FullScan {
entity_type: EntityType::Vertex { tag_name: None },
},
1.0,
1000.0,
),
IndexSelectionDecision::empty(),
JoinOrderDecision::empty(),
1,
1,
);
assert!(decision.is_valid(1, 1));
assert!(!decision.is_valid(2, 1));
assert!(!decision.is_valid(1, 2));
}
}