#![allow(dead_code)]
use crate::indexing::IndexStats as BaseIndexStats;
use crate::model::pattern::{
ObjectPattern, PredicatePattern, SubjectPattern, TriplePattern as ModelTriplePattern,
};
use crate::model::*;
use crate::query::algebra::{AlgebraTriplePattern, TermPattern as AlgebraTermPattern};
use crate::store::IndexedGraph;
use crate::OxirsError;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[derive(Debug, Default)]
pub struct IndexStats {
base_stats: Arc<BaseIndexStats>,
pub predicate_counts: std::sync::RwLock<HashMap<String, usize>>,
pub total_triples: std::sync::atomic::AtomicUsize,
pub subject_cardinality: std::sync::RwLock<HashMap<String, usize>>,
pub object_cardinality: std::sync::RwLock<HashMap<String, usize>>,
pub join_selectivity_cache: std::sync::RwLock<HashMap<String, f64>>,
}
impl IndexStats {
pub fn new() -> Self {
Self {
base_stats: Arc::new(BaseIndexStats::new()),
predicate_counts: std::sync::RwLock::new(HashMap::new()),
total_triples: std::sync::atomic::AtomicUsize::new(0),
subject_cardinality: std::sync::RwLock::new(HashMap::new()),
object_cardinality: std::sync::RwLock::new(HashMap::new()),
join_selectivity_cache: std::sync::RwLock::new(HashMap::new()),
}
}
pub fn update_predicate_count(&self, predicate: &str, count: usize) {
if let Ok(mut counts) = self.predicate_counts.write() {
counts.insert(predicate.to_string(), count);
}
}
pub fn set_total_triples(&self, count: usize) {
self.total_triples
.store(count, std::sync::atomic::Ordering::Relaxed);
}
pub fn update_subject_cardinality(&self, predicate: &str, cardinality: usize) {
if let Ok(mut card) = self.subject_cardinality.write() {
card.insert(predicate.to_string(), cardinality);
}
}
pub fn update_object_cardinality(&self, predicate: &str, cardinality: usize) {
if let Ok(mut card) = self.object_cardinality.write() {
card.insert(predicate.to_string(), cardinality);
}
}
pub fn cache_join_selectivity(&self, pattern_pair: &str, selectivity: f64) {
if let Ok(mut cache) = self.join_selectivity_cache.write() {
cache.insert(pattern_pair.to_string(), selectivity);
}
}
pub fn get_join_selectivity(&self, pattern_pair: &str) -> Option<f64> {
match self.join_selectivity_cache.read() {
Ok(cache) => cache.get(pattern_pair).copied(),
_ => None,
}
}
}
#[derive(Debug)]
pub struct PatternOptimizer {
index_stats: Arc<IndexStats>,
available_indexes: Vec<IndexType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IndexType {
SPO,
POS,
OSP,
SOP,
PSO,
OPS,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum VarPosition {
Subject,
Predicate,
Object,
}
#[derive(Debug, Clone)]
pub enum FilterExpression {
Equals(Variable, Term),
LessThan(Variable, Term),
GreaterThan(Variable, Term),
Regex(Variable, String),
In(Variable, Vec<Term>),
And(Box<FilterExpression>, Box<FilterExpression>),
Or(Box<FilterExpression>, Box<FilterExpression>),
Not(Box<FilterExpression>),
}
#[derive(Debug, Clone)]
pub struct PatternStrategy {
pub index_type: IndexType,
pub estimated_cost: f64,
pub selectivity: f64,
pub bound_vars: HashSet<Variable>,
pub pushdown_filters: Vec<FilterExpression>,
}
#[derive(Debug, Clone)]
pub struct OptimizedPatternPlan {
pub patterns: Vec<(AlgebraTriplePattern, PatternStrategy)>,
pub total_cost: f64,
pub binding_order: Vec<HashSet<Variable>>,
}
impl PatternOptimizer {
pub fn new(index_stats: Arc<IndexStats>) -> Self {
Self {
index_stats,
available_indexes: vec![IndexType::SPO, IndexType::POS, IndexType::OSP],
}
}
pub fn optimize_patterns(
&self,
patterns: &[AlgebraTriplePattern],
) -> Result<OptimizedPatternPlan, OxirsError> {
if patterns.is_empty() {
return Ok(OptimizedPatternPlan {
patterns: Vec::new(),
total_cost: 0.0,
binding_order: Vec::new(),
});
}
let mut pattern_strategies: Vec<(usize, Vec<PatternStrategy>)> = patterns
.iter()
.enumerate()
.map(|(idx, pattern)| {
let strategies = self.analyze_pattern(pattern);
(idx, strategies)
})
.collect();
pattern_strategies.sort_by(|a, b| {
let best_a =
a.1.iter()
.map(|s| s.selectivity)
.min_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(1.0);
let best_b =
b.1.iter()
.map(|s| s.selectivity)
.min_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(1.0);
best_a
.partial_cmp(&best_b)
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut ordered_patterns = Vec::new();
let mut bound_vars = HashSet::new();
let mut binding_order = Vec::new();
let mut total_cost = 0.0;
let (first_idx, first_strategies) = &pattern_strategies[0];
let first_pattern = &patterns[*first_idx];
let first_strategy = self.select_best_strategy(first_strategies, &bound_vars)?;
ordered_patterns.push((first_pattern.clone(), first_strategy.clone()));
bound_vars.extend(first_strategy.bound_vars.clone());
binding_order.push(bound_vars.clone());
total_cost += first_strategy.estimated_cost;
let mut remaining: Vec<_> = pattern_strategies[1..].to_vec();
while !remaining.is_empty() {
let (best_pos, best_idx, best_strategy) = remaining
.iter()
.enumerate()
.map(|(pos, (idx, strategies))| {
let pattern = &patterns[*idx];
let strategy =
self.select_strategy_with_bindings(pattern, strategies, &bound_vars);
(pos, idx, strategy)
})
.min_by(|(_, _, a), (_, _, b)| {
a.estimated_cost
.partial_cmp(&b.estimated_cost)
.unwrap_or(std::cmp::Ordering::Equal)
})
.ok_or_else(|| OxirsError::Query("Failed to find next pattern".to_string()))?;
let pattern = &patterns[*best_idx];
ordered_patterns.push((pattern.clone(), best_strategy.clone()));
bound_vars.extend(best_strategy.bound_vars.clone());
binding_order.push(bound_vars.clone());
total_cost += best_strategy.estimated_cost;
remaining.remove(best_pos);
}
Ok(OptimizedPatternPlan {
patterns: ordered_patterns,
total_cost,
binding_order,
})
}
pub fn analyze_pattern(&self, pattern: &AlgebraTriplePattern) -> Vec<PatternStrategy> {
let mut strategies = Vec::new();
let s_bound = !matches!(pattern.subject, AlgebraTermPattern::Variable(_));
let p_bound = !matches!(pattern.predicate, AlgebraTermPattern::Variable(_));
let o_bound = !matches!(pattern.object, AlgebraTermPattern::Variable(_));
for &index_type in &self.available_indexes {
let (cost, selectivity) =
self.estimate_index_cost(index_type, s_bound, p_bound, o_bound, pattern);
let bound_vars = self.extract_bound_vars(pattern);
strategies.push(PatternStrategy {
index_type,
estimated_cost: cost,
selectivity,
bound_vars,
pushdown_filters: Vec::new(), });
}
strategies
}
fn estimate_index_cost(
&self,
index_type: IndexType,
s_bound: bool,
p_bound: bool,
o_bound: bool,
pattern: &AlgebraTriplePattern,
) -> (f64, f64) {
let base_cost = match index_type {
IndexType::SPO => {
if s_bound && p_bound && o_bound {
1.0 } else if s_bound && p_bound {
10.0 } else if s_bound {
100.0 } else {
10000.0 }
}
IndexType::POS => {
if p_bound && o_bound && s_bound {
1.0
} else if p_bound && o_bound {
10.0
} else if p_bound {
100.0
} else {
10000.0
}
}
IndexType::OSP => {
if o_bound && s_bound && p_bound {
1.0
} else if o_bound && s_bound {
10.0
} else if o_bound {
100.0
} else {
10000.0
}
}
_ => 10000.0, };
let selectivity = self.estimate_selectivity(pattern);
let adjusted_cost = base_cost * selectivity;
(adjusted_cost, selectivity)
}
fn estimate_selectivity(&self, pattern: &AlgebraTriplePattern) -> f64 {
let mut selectivity: f64 = 1.0;
let total_triples = self
.index_stats
.total_triples
.load(std::sync::atomic::Ordering::Relaxed) as f64;
if total_triples == 0.0 {
return 0.001; }
match &pattern.subject {
AlgebraTermPattern::NamedNode(_) | AlgebraTermPattern::BlankNode(_) => {
selectivity *= 0.001;
}
AlgebraTermPattern::Variable(_) => {
if let AlgebraTermPattern::NamedNode(pred) = &pattern.predicate {
if let Ok(card) = self.index_stats.subject_cardinality.read() {
if let Some(subj_card) = card.get(pred.as_str()) {
selectivity *= (*subj_card as f64) / total_triples;
}
}
}
}
_ => {}
}
match &pattern.predicate {
AlgebraTermPattern::NamedNode(pred) => {
if let Ok(counts) = self.index_stats.predicate_counts.read() {
if let Some(pred_count) = counts.get(pred.as_str()) {
selectivity *= (*pred_count as f64) / total_triples;
} else {
selectivity *= 0.001;
}
}
}
AlgebraTermPattern::Variable(_) => {
selectivity *= 0.5;
}
_ => {}
}
match &pattern.object {
AlgebraTermPattern::Literal(_) => {
selectivity *= 0.01;
}
AlgebraTermPattern::NamedNode(_) => {
selectivity *= 0.1;
}
AlgebraTermPattern::BlankNode(_) => {
selectivity *= 0.1;
}
AlgebraTermPattern::Variable(_) => {
if let AlgebraTermPattern::NamedNode(pred) = &pattern.predicate {
if let Ok(card) = self.index_stats.object_cardinality.read() {
if let Some(obj_card) = card.get(pred.as_str()) {
selectivity *= (*obj_card as f64) / total_triples;
}
}
}
}
AlgebraTermPattern::QuotedTriple(_) => {
selectivity *= 0.1;
}
}
selectivity.clamp(0.00001, 1.0)
}
fn extract_bound_vars(&self, pattern: &AlgebraTriplePattern) -> HashSet<Variable> {
let mut vars = HashSet::new();
if let AlgebraTermPattern::Variable(v) = &pattern.subject {
vars.insert(v.clone());
}
if let AlgebraTermPattern::Variable(v) = &pattern.predicate {
vars.insert(v.clone());
}
if let AlgebraTermPattern::Variable(v) = &pattern.object {
vars.insert(v.clone());
}
vars
}
fn select_best_strategy(
&self,
strategies: &[PatternStrategy],
_bound_vars: &HashSet<Variable>,
) -> Result<PatternStrategy, OxirsError> {
strategies
.iter()
.min_by(|a, b| {
a.estimated_cost
.partial_cmp(&b.estimated_cost)
.unwrap_or(std::cmp::Ordering::Equal)
})
.cloned()
.ok_or_else(|| OxirsError::Query("No valid strategy found".to_string()))
}
fn select_strategy_with_bindings(
&self,
pattern: &AlgebraTriplePattern,
strategies: &[PatternStrategy],
bound_vars: &HashSet<Variable>,
) -> PatternStrategy {
let s_bound = match &pattern.subject {
AlgebraTermPattern::Variable(v) => bound_vars.contains(v),
_ => true,
};
let p_bound = match &pattern.predicate {
AlgebraTermPattern::Variable(v) => bound_vars.contains(v),
_ => true,
};
let o_bound = match &pattern.object {
AlgebraTermPattern::Variable(v) => bound_vars.contains(v),
_ => true,
};
let mut best_strategy = strategies[0].clone();
let mut best_cost = f64::MAX;
for strategy in strategies {
let (adjusted_cost, _) =
self.estimate_index_cost(strategy.index_type, s_bound, p_bound, o_bound, pattern);
if adjusted_cost < best_cost {
best_cost = adjusted_cost;
best_strategy = strategy.clone();
best_strategy.estimated_cost = adjusted_cost;
}
}
best_strategy
}
pub fn estimate_join_selectivity(
&self,
left: &AlgebraTriplePattern,
right: &AlgebraTriplePattern,
) -> f64 {
let cache_key = format!("{left:?}|{right:?}");
if let Some(cached) = self.index_stats.get_join_selectivity(&cache_key) {
return cached;
}
let left_vars = self.extract_bound_vars(left);
let right_vars = self.extract_bound_vars(right);
let common_vars: HashSet<_> = left_vars.intersection(&right_vars).cloned().collect();
let selectivity = if common_vars.is_empty() {
1.0
} else {
let mut join_selectivity = 1.0;
for var in common_vars.iter() {
let var_selectivity = self.estimate_variable_join_selectivity(var, left, right);
join_selectivity *= var_selectivity;
}
if common_vars.len() > 1 {
join_selectivity *= 0.1_f64.powf(common_vars.len() as f64 - 1.0);
}
join_selectivity
};
self.index_stats
.cache_join_selectivity(&cache_key, selectivity);
selectivity.clamp(0.00001, 1.0)
}
fn estimate_variable_join_selectivity(
&self,
var: &Variable,
left: &AlgebraTriplePattern,
right: &AlgebraTriplePattern,
) -> f64 {
let left_pos = self.find_variable_position(var, left);
let right_pos = self.find_variable_position(var, right);
match (left_pos, right_pos) {
(Some(pos1), Some(pos2)) => {
match (pos1, pos2) {
(VarPosition::Subject, VarPosition::Subject) => 0.01, (VarPosition::Subject, VarPosition::Object) => 0.1, (VarPosition::Object, VarPosition::Subject) => 0.1, (VarPosition::Object, VarPosition::Object) => 0.2, (VarPosition::Predicate, _) | (_, VarPosition::Predicate) => 0.05, }
}
_ => 1.0, }
}
fn find_variable_position(
&self,
var: &Variable,
pattern: &AlgebraTriplePattern,
) -> Option<VarPosition> {
if let AlgebraTermPattern::Variable(v) = &pattern.subject {
if v == var {
return Some(VarPosition::Subject);
}
}
if let AlgebraTermPattern::Variable(v) = &pattern.predicate {
if v == var {
return Some(VarPosition::Predicate);
}
}
if let AlgebraTermPattern::Variable(v) = &pattern.object {
if v == var {
return Some(VarPosition::Object);
}
}
None
}
pub fn optimize_filters(
&self,
patterns: &[AlgebraTriplePattern],
filters: &[FilterExpression],
) -> Vec<(usize, Vec<FilterExpression>)> {
let mut pushdown_map = Vec::new();
for (pattern_idx, pattern) in patterns.iter().enumerate() {
let mut pattern_filters = Vec::new();
for filter in filters {
if self.can_pushdown_filter(filter, pattern) {
pattern_filters.push(filter.clone());
}
}
if !pattern_filters.is_empty() {
pushdown_map.push((pattern_idx, pattern_filters));
}
}
pushdown_map
}
fn can_pushdown_filter(
&self,
filter: &FilterExpression,
pattern: &AlgebraTriplePattern,
) -> bool {
match filter {
FilterExpression::Equals(var, _)
| FilterExpression::LessThan(var, _)
| FilterExpression::GreaterThan(var, _)
| FilterExpression::Regex(var, _)
| FilterExpression::In(var, _) => {
self.pattern_binds_variable(var, pattern)
}
FilterExpression::And(left, right) => {
self.can_pushdown_filter(left, pattern) || self.can_pushdown_filter(right, pattern)
}
FilterExpression::Or(left, right) => {
self.can_pushdown_filter(left, pattern) && self.can_pushdown_filter(right, pattern)
}
FilterExpression::Not(inner) => self.can_pushdown_filter(inner, pattern),
}
}
fn pattern_binds_variable(&self, var: &Variable, pattern: &AlgebraTriplePattern) -> bool {
matches!(&pattern.subject, AlgebraTermPattern::Variable(v) if v == var)
|| matches!(&pattern.predicate, AlgebraTermPattern::Variable(v) if v == var)
|| matches!(&pattern.object, AlgebraTermPattern::Variable(v) if v == var)
}
#[allow(clippy::only_used_in_recursion)]
pub fn estimate_filter_selectivity(&self, filter: &FilterExpression) -> f64 {
match filter {
FilterExpression::Equals(_, _) => 0.1, FilterExpression::LessThan(_, _) => 0.3, FilterExpression::GreaterThan(_, _) => 0.3,
FilterExpression::Regex(_, _) => 0.2, FilterExpression::In(_, values) => {
(values.len() as f64 * 0.1).min(0.9)
}
FilterExpression::And(left, right) => {
self.estimate_filter_selectivity(left) * self.estimate_filter_selectivity(right)
}
FilterExpression::Or(left, right) => {
let left_sel = self.estimate_filter_selectivity(left);
let right_sel = self.estimate_filter_selectivity(right);
left_sel + right_sel - (left_sel * right_sel)
}
FilterExpression::Not(inner) => {
1.0 - self.estimate_filter_selectivity(inner)
}
}
}
pub fn get_optimal_index(
&self,
pattern: &ModelTriplePattern,
bound_vars: &HashSet<Variable>,
) -> IndexType {
let s_bound = pattern.subject.as_ref().is_some_and(|s| match s {
SubjectPattern::Variable(v) => bound_vars.contains(v),
_ => true,
});
let p_bound = pattern.predicate.as_ref().is_some_and(|p| match p {
PredicatePattern::Variable(v) => bound_vars.contains(v),
_ => true,
});
let o_bound = pattern.object.as_ref().is_some_and(|o| match o {
ObjectPattern::Variable(v) => bound_vars.contains(v),
_ => true,
});
match (s_bound, p_bound, o_bound) {
(true, true, _) => IndexType::SPO,
(true, false, true) => IndexType::SPO, (false, true, true) => IndexType::POS,
(true, false, false) => IndexType::SPO,
(false, true, false) => IndexType::POS,
(false, false, true) => IndexType::OSP,
(false, false, false) => IndexType::SPO, }
}
}
pub struct PatternExecutor {
graph: Arc<IndexedGraph>,
optimizer: PatternOptimizer,
}
impl PatternExecutor {
pub fn new(graph: Arc<IndexedGraph>, index_stats: Arc<IndexStats>) -> Self {
Self {
graph,
optimizer: PatternOptimizer::new(index_stats),
}
}
pub fn execute_plan(
&self,
plan: &OptimizedPatternPlan,
) -> Result<Vec<HashMap<Variable, Term>>, OxirsError> {
let mut results = vec![HashMap::new()];
for (pattern, strategy) in &plan.patterns {
results = self.execute_pattern_with_strategy(pattern, strategy, results)?;
}
Ok(results)
}
fn execute_pattern_with_strategy(
&self,
pattern: &AlgebraTriplePattern,
_strategy: &PatternStrategy,
bindings: Vec<HashMap<Variable, Term>>,
) -> Result<Vec<HashMap<Variable, Term>>, OxirsError> {
let mut new_results = Vec::new();
for binding in bindings {
let bound_pattern = self.bind_pattern(pattern, &binding)?;
let subject = bound_pattern
.subject
.as_ref()
.and_then(|s| self.subject_pattern_to_subject(s));
let predicate = bound_pattern
.predicate
.as_ref()
.and_then(|p| self.predicate_pattern_to_predicate(p));
let object = bound_pattern
.object
.as_ref()
.and_then(|o| self.object_pattern_to_object(o));
let matches = self
.graph
.query(subject.as_ref(), predicate.as_ref(), object.as_ref());
for triple in matches {
let mut new_binding = binding.clone();
if let AlgebraTermPattern::Variable(v) = &pattern.subject {
new_binding.insert(v.clone(), Term::from(triple.subject().clone()));
}
if let AlgebraTermPattern::Variable(v) = &pattern.predicate {
new_binding.insert(v.clone(), Term::from(triple.predicate().clone()));
}
if let AlgebraTermPattern::Variable(v) = &pattern.object {
new_binding.insert(v.clone(), Term::from(triple.object().clone()));
}
new_results.push(new_binding);
}
}
Ok(new_results)
}
fn bind_pattern(
&self,
pattern: &AlgebraTriplePattern,
bindings: &HashMap<Variable, Term>,
) -> Result<ModelTriplePattern, OxirsError> {
let subject = match &pattern.subject {
AlgebraTermPattern::Variable(v) => {
if let Some(term) = bindings.get(v) {
Some(self.term_to_subject_pattern(term)?)
} else {
None
}
}
AlgebraTermPattern::NamedNode(n) => Some(SubjectPattern::NamedNode(n.clone())),
AlgebraTermPattern::BlankNode(b) => Some(SubjectPattern::BlankNode(b.clone())),
AlgebraTermPattern::Literal(_) => {
return Err(OxirsError::Query("Literal cannot be subject".to_string()))
}
AlgebraTermPattern::QuotedTriple(_) => {
return Err(OxirsError::Query(
"RDF-star quoted triples not yet fully supported".to_string(),
))
}
};
let predicate = match &pattern.predicate {
AlgebraTermPattern::Variable(v) => {
if let Some(term) = bindings.get(v) {
Some(self.term_to_predicate_pattern(term)?)
} else {
None
}
}
AlgebraTermPattern::NamedNode(n) => Some(PredicatePattern::NamedNode(n.clone())),
_ => return Err(OxirsError::Query("Invalid predicate pattern".to_string())),
};
let object = match &pattern.object {
AlgebraTermPattern::Variable(v) => {
if let Some(term) = bindings.get(v) {
Some(self.term_to_object_pattern(term)?)
} else {
None
}
}
AlgebraTermPattern::NamedNode(n) => Some(ObjectPattern::NamedNode(n.clone())),
AlgebraTermPattern::BlankNode(b) => Some(ObjectPattern::BlankNode(b.clone())),
AlgebraTermPattern::Literal(l) => Some(ObjectPattern::Literal(l.clone())),
AlgebraTermPattern::QuotedTriple(_) => {
return Err(OxirsError::Query(
"RDF-star quoted triples not yet fully supported".to_string(),
))
}
};
Ok(ModelTriplePattern::new(subject, predicate, object))
}
fn term_to_subject_pattern(&self, term: &Term) -> Result<SubjectPattern, OxirsError> {
match term {
Term::NamedNode(n) => Ok(SubjectPattern::NamedNode(n.clone())),
Term::BlankNode(b) => Ok(SubjectPattern::BlankNode(b.clone())),
_ => Err(OxirsError::Query("Invalid term for subject".to_string())),
}
}
fn term_to_predicate_pattern(&self, term: &Term) -> Result<PredicatePattern, OxirsError> {
match term {
Term::NamedNode(n) => Ok(PredicatePattern::NamedNode(n.clone())),
_ => Err(OxirsError::Query("Invalid term for predicate".to_string())),
}
}
fn term_to_object_pattern(&self, term: &Term) -> Result<ObjectPattern, OxirsError> {
match term {
Term::NamedNode(n) => Ok(ObjectPattern::NamedNode(n.clone())),
Term::BlankNode(b) => Ok(ObjectPattern::BlankNode(b.clone())),
Term::Literal(l) => Ok(ObjectPattern::Literal(l.clone())),
_ => Err(OxirsError::Query(
"Invalid term for object pattern".to_string(),
)),
}
}
fn subject_pattern_to_subject(&self, pattern: &SubjectPattern) -> Option<Subject> {
match pattern {
SubjectPattern::NamedNode(n) => Some(Subject::NamedNode(n.clone())),
SubjectPattern::BlankNode(b) => Some(Subject::BlankNode(b.clone())),
SubjectPattern::Variable(_) => None,
}
}
fn predicate_pattern_to_predicate(&self, pattern: &PredicatePattern) -> Option<Predicate> {
match pattern {
PredicatePattern::NamedNode(n) => Some(Predicate::NamedNode(n.clone())),
PredicatePattern::Variable(_) => None,
}
}
fn object_pattern_to_object(&self, pattern: &ObjectPattern) -> Option<Object> {
match pattern {
ObjectPattern::NamedNode(n) => Some(Object::NamedNode(n.clone())),
ObjectPattern::BlankNode(b) => Some(Object::BlankNode(b.clone())),
ObjectPattern::Literal(l) => Some(Object::Literal(l.clone())),
ObjectPattern::Variable(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pattern_optimizer_creation() {
let stats = Arc::new(IndexStats::new());
let optimizer = PatternOptimizer::new(stats);
assert_eq!(optimizer.available_indexes.len(), 3);
}
#[test]
fn test_index_selection() {
let stats = Arc::new(IndexStats::new());
let optimizer = PatternOptimizer::new(stats);
let pattern = ModelTriplePattern::new(
Some(SubjectPattern::NamedNode(
NamedNode::new("http://example.org/s").expect("valid IRI"),
)),
None,
None,
);
let bound_vars = HashSet::new();
let index = optimizer.get_optimal_index(&pattern, &bound_vars);
assert_eq!(index, IndexType::SPO);
}
#[test]
fn test_selectivity_estimation() {
let stats = Arc::new(IndexStats::new());
let optimizer = PatternOptimizer::new(stats);
let pattern = AlgebraTriplePattern::new(
AlgebraTermPattern::Variable(Variable::new("s").expect("valid variable name")),
AlgebraTermPattern::NamedNode(
NamedNode::new("http://example.org/type").expect("valid IRI"),
),
AlgebraTermPattern::Literal(Literal::new("test")),
);
let selectivity = optimizer.estimate_selectivity(&pattern);
assert!(selectivity < 0.2);
}
#[test]
fn test_pattern_optimization() {
let stats = Arc::new(IndexStats::new());
let optimizer = PatternOptimizer::new(stats);
let patterns = vec![
AlgebraTriplePattern::new(
AlgebraTermPattern::Variable(Variable::new("s").expect("valid variable name")),
AlgebraTermPattern::NamedNode(
NamedNode::new("http://example.org/type").expect("valid IRI"),
),
AlgebraTermPattern::NamedNode(
NamedNode::new("http://example.org/Person").expect("valid IRI"),
),
),
AlgebraTriplePattern::new(
AlgebraTermPattern::Variable(Variable::new("s").expect("valid variable name")),
AlgebraTermPattern::NamedNode(
NamedNode::new("http://example.org/name").expect("valid IRI"),
),
AlgebraTermPattern::Variable(Variable::new("name").expect("valid variable name")),
),
];
let plan = optimizer
.optimize_patterns(&patterns)
.expect("operation should succeed");
assert_eq!(plan.patterns.len(), 2);
assert!(plan.total_cost > 0.0);
assert_eq!(plan.binding_order.len(), 2);
}
}