use petgraph::Graph;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::ast::ast::{Edge, PathPattern};
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct PatternConnectivity {
pub patterns: Vec<PathPattern>,
pub shared_variables: HashMap<String, Vec<usize>>,
pub connectivity_graph: Graph<usize, String>,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinearPath {
pub start_pattern: PathPattern,
pub steps: Vec<TraversalStep>,
pub variables: Vec<String>,
pub estimated_selectivity: f64,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraversalStep {
pub from_var: String,
pub relationship: Edge,
pub to_var: String,
pub selectivity: f64,
pub pattern_index: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum PatternPlanStrategy {
PathTraversal(LinearPath),
HashJoin {
patterns: Vec<PathPattern>,
join_order: Vec<JoinStep>,
estimated_cost: f64,
},
NestedLoopJoin {
patterns: Vec<PathPattern>,
estimated_cost: f64,
},
CartesianProduct {
patterns: Vec<PathPattern>,
estimated_cost: f64,
},
}
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JoinStep {
pub left_pattern_idx: usize,
pub right_pattern_idx: usize,
pub join_variables: Vec<String>,
pub join_type: JoinType,
pub estimated_cost: f64,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum JoinType {
Hash,
NestedLoop,
IndexLookup,
}
impl PatternConnectivity {
#[allow(dead_code)] pub fn new(patterns: Vec<PathPattern>) -> Self {
PatternConnectivity {
patterns,
shared_variables: HashMap::new(),
connectivity_graph: Graph::new(),
}
}
#[allow(dead_code)] pub fn has_shared_variables(&self) -> bool {
!self.shared_variables.is_empty()
}
#[allow(dead_code)] pub fn connected_components(&self) -> usize {
use petgraph::algo::connected_components;
connected_components(&self.connectivity_graph)
}
#[allow(dead_code)] pub fn is_fully_connected(&self) -> bool {
self.connected_components() <= 1
}
}
impl LinearPath {
pub fn new(start_pattern: PathPattern, steps: Vec<TraversalStep>) -> Self {
let variables = Self::extract_variables(&start_pattern, &steps);
let estimated_selectivity = steps.iter().map(|s| s.selectivity).product();
LinearPath {
start_pattern,
steps,
variables,
estimated_selectivity,
}
}
fn extract_variables(start_pattern: &PathPattern, steps: &[TraversalStep]) -> Vec<String> {
let mut variables = Vec::new();
for element in &start_pattern.elements {
match element {
crate::ast::ast::PatternElement::Node(node) => {
if let Some(ref id) = node.identifier {
if !variables.contains(id) {
variables.push(id.clone());
}
}
}
crate::ast::ast::PatternElement::Edge(edge) => {
if let Some(ref id) = edge.identifier {
if !variables.contains(id) {
variables.push(id.clone());
}
}
}
}
}
for step in steps {
if !variables.contains(&step.from_var) {
variables.push(step.from_var.clone());
}
if !variables.contains(&step.to_var) {
variables.push(step.to_var.clone());
}
}
variables
}
#[allow(dead_code)] pub fn length(&self) -> usize {
self.steps.len()
}
#[allow(dead_code)] pub fn is_simple_path(&self) -> bool {
self.steps.len() <= 2
}
#[allow(dead_code)] pub fn end_variable(&self) -> Option<&String> {
self.steps.last().map(|step| &step.to_var)
}
}
impl PatternPlanStrategy {
#[allow(dead_code)] pub fn name(&self) -> &'static str {
match self {
PatternPlanStrategy::PathTraversal(_) => "PathTraversal",
PatternPlanStrategy::HashJoin { .. } => "HashJoin",
PatternPlanStrategy::NestedLoopJoin { .. } => "NestedLoopJoin",
PatternPlanStrategy::CartesianProduct { .. } => "CartesianProduct",
}
}
#[allow(dead_code)] pub fn estimated_cost(&self) -> f64 {
match self {
PatternPlanStrategy::PathTraversal(path) => {
let base_cost = 100.0;
base_cost * (1.0 / path.estimated_selectivity.max(0.001)) * (path.length() as f64)
}
PatternPlanStrategy::HashJoin { estimated_cost, .. } => *estimated_cost,
PatternPlanStrategy::NestedLoopJoin { estimated_cost, .. } => *estimated_cost,
PatternPlanStrategy::CartesianProduct { estimated_cost, .. } => *estimated_cost,
}
}
#[allow(dead_code)] pub fn is_path_traversal(&self) -> bool {
matches!(self, PatternPlanStrategy::PathTraversal(_))
}
#[allow(dead_code)] pub fn is_join_based(&self) -> bool {
matches!(
self,
PatternPlanStrategy::HashJoin { .. } | PatternPlanStrategy::NestedLoopJoin { .. }
)
}
}
impl JoinStep {
#[allow(dead_code)] pub fn new(
left_idx: usize,
right_idx: usize,
join_vars: Vec<String>,
join_type: JoinType,
) -> Self {
JoinStep {
left_pattern_idx: left_idx,
right_pattern_idx: right_idx,
join_variables: join_vars,
join_type,
estimated_cost: 0.0, }
}
#[allow(dead_code)] pub fn is_hash_join(&self) -> bool {
matches!(self.join_type, JoinType::Hash)
}
#[allow(dead_code)] pub fn is_index_join(&self) -> bool {
matches!(self.join_type, JoinType::IndexLookup)
}
}
impl JoinType {
#[allow(dead_code)] pub fn as_str(&self) -> &'static str {
match self {
JoinType::Hash => "Hash",
JoinType::NestedLoop => "NestedLoop",
JoinType::IndexLookup => "IndexLookup",
}
}
#[allow(dead_code)] pub fn is_scalable(&self) -> bool {
matches!(self, JoinType::Hash | JoinType::IndexLookup)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::ast::{Location, Node};
#[test]
fn test_pattern_connectivity_creation() {
let patterns = vec![];
let connectivity = PatternConnectivity::new(patterns);
assert!(!connectivity.has_shared_variables());
assert_eq!(connectivity.connected_components(), 0);
}
#[test]
fn test_linear_path_variables() {
use crate::ast::ast::{Location, PatternElement};
let start_pattern = PathPattern {
assignment: None,
path_type: None,
elements: vec![
PatternElement::Node(Node {
identifier: Some("a".to_string()),
labels: vec![],
properties: None,
location: Location::default(),
}),
PatternElement::Edge(Edge {
identifier: Some("r".to_string()),
labels: vec!["R".to_string()],
properties: None,
direction: crate::ast::ast::EdgeDirection::Outgoing,
quantifier: None,
location: Location::default(),
}),
PatternElement::Node(Node {
identifier: Some("b".to_string()),
labels: vec![],
properties: None,
location: Location::default(),
}),
],
location: Location::default(),
};
let steps = vec![TraversalStep {
from_var: "b".to_string(),
relationship: Edge {
identifier: Some("r2".to_string()),
labels: vec!["R2".to_string()],
properties: None,
direction: crate::ast::ast::EdgeDirection::Outgoing,
quantifier: None,
location: Location::default(),
},
to_var: "c".to_string(),
selectivity: 0.1,
pattern_index: 1,
}];
let path = LinearPath::new(start_pattern, steps);
assert_eq!(path.length(), 1);
assert!(path.is_simple_path());
assert_eq!(path.end_variable(), Some(&"c".to_string()));
assert!(path.variables.contains(&"a".to_string()));
assert!(path.variables.contains(&"b".to_string()));
assert!(path.variables.contains(&"c".to_string()));
}
#[test]
fn test_join_step_creation() {
let join_step = JoinStep::new(0, 1, vec!["x".to_string()], JoinType::Hash);
assert!(join_step.is_hash_join());
assert!(!join_step.is_index_join());
assert_eq!(join_step.join_variables, vec!["x".to_string()]);
}
#[test]
fn test_pattern_strategy_properties() {
let path = LinearPath {
start_pattern: PathPattern {
assignment: None,
path_type: None,
elements: vec![],
location: Location::default(),
},
steps: vec![TraversalStep {
from_var: "n".to_string(),
relationship: Edge {
identifier: Some("r".to_string()),
labels: vec![],
properties: None,
direction: crate::ast::ast::EdgeDirection::Outgoing,
quantifier: None,
location: Location::default(),
},
to_var: "m".to_string(),
selectivity: 0.5,
pattern_index: 0,
}],
variables: vec!["n".to_string(), "m".to_string()],
estimated_selectivity: 0.1,
};
let strategy = PatternPlanStrategy::PathTraversal(path);
assert!(strategy.is_path_traversal());
assert!(!strategy.is_join_based());
assert_eq!(strategy.name(), "PathTraversal");
assert!(strategy.estimated_cost() > 0.0);
}
}