use crate::algebra::{Algebra, Expression, TriplePattern};
use crate::statistics_collector::StatisticsCollector;
use anyhow::Result;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CostModelConfig {
pub cpu_cost_per_op: f64,
pub io_cost_per_page: f64,
pub memory_cost_per_byte: f64,
pub network_cost_per_byte: f64,
pub page_size: usize,
pub available_memory: usize,
pub calibration: CostCalibration,
}
impl Default for CostModelConfig {
fn default() -> Self {
Self {
cpu_cost_per_op: 1.0,
io_cost_per_page: 10.0,
memory_cost_per_byte: 0.001,
network_cost_per_byte: 0.1,
page_size: 4096,
available_memory: 1024 * 1024 * 1024, calibration: CostCalibration::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct CostCalibration {
pub cpu_scale: f64,
pub io_scale: f64,
pub memory_scale: f64,
pub network_scale: f64,
pub join_factors: JoinCostFactors,
}
impl Default for CostCalibration {
fn default() -> Self {
Self {
cpu_scale: 1.0,
io_scale: 1.0,
memory_scale: 1.0,
network_scale: 1.0,
join_factors: JoinCostFactors::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct JoinCostFactors {
pub hash_join_factor: f64,
pub sort_merge_join_factor: f64,
pub nested_loop_join_factor: f64,
pub index_join_factor: f64,
}
impl Default for JoinCostFactors {
fn default() -> Self {
Self {
hash_join_factor: 1.0,
sort_merge_join_factor: 1.2,
nested_loop_join_factor: 2.0,
index_join_factor: 0.8,
}
}
}
#[derive(Debug, Clone)]
pub struct CostEstimate {
pub cpu_cost: f64,
pub io_cost: f64,
pub memory_cost: f64,
pub network_cost: f64,
pub total_cost: f64,
pub cardinality: usize,
pub selectivity: f64,
pub operation_costs: HashMap<String, f64>,
}
impl CostEstimate {
pub fn new(cpu: f64, io: f64, memory: f64, network: f64, cardinality: usize) -> Self {
let total = cpu + io + memory + network;
Self {
cpu_cost: cpu,
io_cost: io,
memory_cost: memory,
network_cost: network,
total_cost: total,
cardinality,
selectivity: 1.0,
operation_costs: HashMap::new(),
}
}
pub fn with_selectivity(mut self, selectivity: f64) -> Self {
self.selectivity = selectivity;
self
}
pub fn add_operation_cost(&mut self, operation: &str, cost: f64) {
self.operation_costs.insert(operation.to_string(), cost);
self.total_cost += cost;
}
pub fn zero() -> Self {
Self::new(0.0, 0.0, 0.0, 0.0, 0)
}
pub fn infinite() -> Self {
Self::new(
f64::INFINITY,
f64::INFINITY,
f64::INFINITY,
f64::INFINITY,
usize::MAX,
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IOPattern {
Sequential,
Random,
IndexScan,
FullScan,
}
#[derive(Debug, Clone)]
pub struct MemoryUsage {
pub peak_usage: usize,
pub average_usage: usize,
pub access_pattern: MemoryAccessPattern,
pub duration_estimate: f64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MemoryAccessPattern {
Sequential,
Random,
Locality,
CacheFriendly,
}
#[derive(Debug, Clone)]
pub struct CostModel {
config: CostModelConfig,
#[allow(dead_code)]
statistics: Option<StatisticsCollector>,
cached_estimates: HashMap<String, CostEstimate>,
}
impl CostModel {
pub fn new(config: CostModelConfig) -> Self {
Self {
config,
statistics: None,
cached_estimates: HashMap::new(),
}
}
pub fn with_statistics(config: CostModelConfig, statistics: StatisticsCollector) -> Self {
Self {
config,
statistics: Some(statistics),
cached_estimates: HashMap::new(),
}
}
pub fn estimate_cost(&mut self, algebra: &Algebra) -> Result<CostEstimate> {
let algebra_key = self.algebra_to_key(algebra);
if let Some(cached) = self.cached_estimates.get(&algebra_key) {
return Ok(cached.clone());
}
let estimate = self.estimate_cost_recursive(algebra)?;
self.cached_estimates.insert(algebra_key, estimate.clone());
Ok(estimate)
}
fn estimate_cost_recursive(&self, algebra: &Algebra) -> Result<CostEstimate> {
match algebra {
Algebra::Bgp(patterns) if patterns.len() == 1 => {
self.estimate_triple_pattern_cost(&patterns[0])
}
Algebra::Bgp(patterns) => self.estimate_bgp_cost(patterns),
Algebra::Join { left, right } => self.estimate_join_cost(left, right),
Algebra::LeftJoin { left, right, .. } => self.estimate_left_join_cost(left, right),
Algebra::Union { left, right } => self.estimate_union_cost(left, right),
Algebra::Filter { condition, pattern } => self.estimate_filter_cost(condition, pattern),
Algebra::Project { pattern, .. } => self.estimate_project_cost(pattern),
Algebra::Extend { expr, pattern, .. } => self.estimate_extend_cost(expr, pattern),
Algebra::Distinct { pattern } => self.estimate_distinct_cost(pattern),
Algebra::Reduced { pattern } => self.estimate_reduced_cost(pattern),
Algebra::OrderBy { pattern, .. } => self.estimate_order_by_cost(pattern),
Algebra::Slice {
pattern,
offset,
limit,
} => self.estimate_slice_cost(pattern, *offset, *limit),
Algebra::Group { pattern, .. } => self.estimate_group_cost(pattern),
Algebra::PropertyPath { .. } => {
Ok(CostEstimate::new(5000.0, 1000.0, 500.0, 0.0, 2000))
}
Algebra::Minus { left, right } => {
let left_cost = self.estimate_cost_recursive(left)?;
let right_cost = self.estimate_cost_recursive(right)?;
Ok(CostEstimate::new(
left_cost.total_cost + right_cost.total_cost * 2.0,
0.0,
0.0,
0.0,
left_cost.cardinality,
))
}
Algebra::Service { pattern, .. } => {
let pattern_cost = self.estimate_cost_recursive(pattern)?;
Ok(CostEstimate::new(
pattern_cost.total_cost,
0.0,
0.0,
pattern_cost.total_cost * 10.0 + 1000.0, pattern_cost.cardinality,
))
}
Algebra::Graph { pattern, .. } => {
self.estimate_cost_recursive(pattern)
}
Algebra::Having { pattern, condition } => {
let pattern_cost = self.estimate_cost_recursive(pattern)?;
let filter_selectivity = self.estimate_expression_selectivity(condition);
Ok(CostEstimate::new(
pattern_cost.total_cost * 1.1, 0.0,
0.0,
0.0,
(pattern_cost.cardinality as f64 * filter_selectivity) as usize,
))
}
Algebra::Values { bindings, .. } => {
Ok(CostEstimate::new(
bindings.len() as f64 * 0.1,
0.0,
0.0,
0.0,
bindings.len(),
))
}
Algebra::Table => {
Ok(CostEstimate::new(0.1, 0.0, 0.0, 0.0, 1))
}
Algebra::Zero => {
Ok(CostEstimate::new(0.1, 0.0, 0.0, 0.0, 0))
}
Algebra::Empty => {
Ok(CostEstimate::new(0.1, 0.0, 0.0, 0.0, 0))
}
}
}
fn estimate_triple_pattern_cost(&self, pattern: &TriplePattern) -> Result<CostEstimate> {
let selectivity = self.estimate_pattern_selectivity(pattern);
let base_cardinality = 100000; let cardinality = (base_cardinality as f64 * selectivity) as usize;
let io_pattern = self.determine_io_pattern(pattern);
let pages_accessed = self.estimate_pages_accessed(cardinality, io_pattern);
let io_cost =
pages_accessed as f64 * self.config.io_cost_per_page * self.config.calibration.io_scale;
let cpu_cost =
cardinality as f64 * self.config.cpu_cost_per_op * self.config.calibration.cpu_scale;
let memory_usage = cardinality * 100; let memory_cost = memory_usage as f64
* self.config.memory_cost_per_byte
* self.config.calibration.memory_scale;
let mut estimate = CostEstimate::new(cpu_cost, io_cost, memory_cost, 0.0, cardinality)
.with_selectivity(selectivity);
estimate.add_operation_cost("pattern_scan", cpu_cost + io_cost);
Ok(estimate)
}
fn estimate_bgp_cost(&self, patterns: &[TriplePattern]) -> Result<CostEstimate> {
if patterns.is_empty() {
return Ok(CostEstimate::zero());
}
if patterns.len() == 1 {
return self.estimate_triple_pattern_cost(&patterns[0]);
}
let mut total_cost = CostEstimate::zero();
let mut current_cardinality = 1;
for pattern in patterns {
let pattern_cost = self.estimate_triple_pattern_cost(pattern)?;
let join_cost = self.estimate_join_cost_detailed(
current_cardinality,
pattern_cost.cardinality,
0.1, JoinAlgorithm::HashJoin,
);
total_cost.cpu_cost += pattern_cost.cpu_cost + join_cost.cpu_cost;
total_cost.io_cost += pattern_cost.io_cost + join_cost.io_cost;
total_cost.memory_cost += pattern_cost.memory_cost + join_cost.memory_cost;
current_cardinality = join_cost.cardinality;
}
total_cost.cardinality = current_cardinality;
total_cost.total_cost = total_cost.cpu_cost + total_cost.io_cost + total_cost.memory_cost;
Ok(total_cost)
}
fn estimate_join_cost(&self, left: &Algebra, right: &Algebra) -> Result<CostEstimate> {
let left_cost = self.estimate_cost_recursive(left)?;
let right_cost = self.estimate_cost_recursive(right)?;
let algorithm = self.choose_join_algorithm(left_cost.cardinality, right_cost.cardinality);
let join_selectivity = 0.1;
let join_cost = self.estimate_join_cost_detailed(
left_cost.cardinality,
right_cost.cardinality,
join_selectivity,
algorithm,
);
let total_cpu = left_cost.cpu_cost + right_cost.cpu_cost + join_cost.cpu_cost;
let total_io = left_cost.io_cost + right_cost.io_cost + join_cost.io_cost;
let total_memory =
left_cost.memory_cost.max(right_cost.memory_cost) + join_cost.memory_cost;
let mut estimate = CostEstimate::new(
total_cpu,
total_io,
total_memory,
0.0,
join_cost.cardinality,
);
estimate.add_operation_cost("join", join_cost.total_cost);
Ok(estimate)
}
fn estimate_left_join_cost(&self, left: &Algebra, right: &Algebra) -> Result<CostEstimate> {
let left_cost = self.estimate_cost_recursive(left)?;
let right_cost = self.estimate_cost_recursive(right)?;
let result_cardinality =
left_cost.cardinality + (right_cost.cardinality as f64 * 0.5) as usize;
let base_join_cost = self.estimate_join_cost_detailed(
left_cost.cardinality,
right_cost.cardinality,
0.5, self.choose_join_algorithm(left_cost.cardinality, right_cost.cardinality),
);
let total_cpu = left_cost.cpu_cost + right_cost.cpu_cost + base_join_cost.cpu_cost * 1.2;
let total_io = left_cost.io_cost + right_cost.io_cost + base_join_cost.io_cost;
let total_memory =
left_cost.memory_cost.max(right_cost.memory_cost) + base_join_cost.memory_cost;
Ok(CostEstimate::new(
total_cpu,
total_io,
total_memory,
0.0,
result_cardinality,
))
}
fn estimate_union_cost(&self, left: &Algebra, right: &Algebra) -> Result<CostEstimate> {
let left_cost = self.estimate_cost_recursive(left)?;
let right_cost = self.estimate_cost_recursive(right)?;
let result_cardinality = left_cost.cardinality + right_cost.cardinality;
let union_overhead = (result_cardinality as f64 * 0.1) * self.config.cpu_cost_per_op;
let total_cpu = left_cost.cpu_cost + right_cost.cpu_cost + union_overhead;
let total_io = left_cost.io_cost + right_cost.io_cost;
let total_memory = left_cost.memory_cost + right_cost.memory_cost;
Ok(CostEstimate::new(
total_cpu,
total_io,
total_memory,
0.0,
result_cardinality,
))
}
fn estimate_filter_cost(
&self,
expression: &Expression,
input: &Algebra,
) -> Result<CostEstimate> {
let input_cost = self.estimate_cost_recursive(input)?;
let filter_selectivity = self.estimate_expression_selectivity(expression);
let result_cardinality = (input_cost.cardinality as f64 * filter_selectivity) as usize;
let filter_cpu_cost = input_cost.cardinality as f64
* self.config.cpu_cost_per_op
* self.estimate_expression_complexity(expression);
let total_cpu = input_cost.cpu_cost + filter_cpu_cost;
let total_io = input_cost.io_cost;
let total_memory = input_cost.memory_cost;
Ok(
CostEstimate::new(total_cpu, total_io, total_memory, 0.0, result_cardinality)
.with_selectivity(filter_selectivity),
)
}
fn estimate_project_cost(&self, input: &Algebra) -> Result<CostEstimate> {
let input_cost = self.estimate_cost_recursive(input)?;
let memory_reduction = 0.8; let projection_cpu = input_cost.cardinality as f64 * self.config.cpu_cost_per_op * 0.1;
let total_cpu = input_cost.cpu_cost + projection_cpu;
let total_memory = input_cost.memory_cost * memory_reduction;
Ok(CostEstimate::new(
total_cpu,
input_cost.io_cost,
total_memory,
0.0,
input_cost.cardinality,
))
}
fn estimate_extend_cost(
&self,
expression: &Expression,
input: &Algebra,
) -> Result<CostEstimate> {
let input_cost = self.estimate_cost_recursive(input)?;
let expression_cost = input_cost.cardinality as f64
* self.config.cpu_cost_per_op
* self.estimate_expression_complexity(expression);
let total_cpu = input_cost.cpu_cost + expression_cost;
Ok(CostEstimate::new(
total_cpu,
input_cost.io_cost,
input_cost.memory_cost,
0.0,
input_cost.cardinality,
))
}
fn estimate_distinct_cost(&self, input: &Algebra) -> Result<CostEstimate> {
let input_cost = self.estimate_cost_recursive(input)?;
let distinct_cardinality = (input_cost.cardinality as f64 * 0.8) as usize; let sort_cost = input_cost.cardinality as f64
* (input_cost.cardinality as f64).log2()
* self.config.cpu_cost_per_op;
let total_cpu = input_cost.cpu_cost + sort_cost;
let total_memory = input_cost.memory_cost * 1.5;
Ok(CostEstimate::new(
total_cpu,
input_cost.io_cost,
total_memory,
0.0,
distinct_cardinality,
))
}
fn estimate_reduced_cost(&self, input: &Algebra) -> Result<CostEstimate> {
let input_cost = self.estimate_cost_recursive(input)?;
let reduced_cpu = input_cost.cardinality as f64 * self.config.cpu_cost_per_op * 0.1;
let total_cpu = input_cost.cpu_cost + reduced_cpu;
Ok(CostEstimate::new(
total_cpu,
input_cost.io_cost,
input_cost.memory_cost,
0.0,
input_cost.cardinality,
))
}
fn estimate_order_by_cost(&self, input: &Algebra) -> Result<CostEstimate> {
let input_cost = self.estimate_cost_recursive(input)?;
let sort_cost = input_cost.cardinality as f64
* (input_cost.cardinality as f64).log2()
* self.config.cpu_cost_per_op;
let total_cpu = input_cost.cpu_cost + sort_cost;
let total_memory = input_cost.memory_cost * 2.0;
Ok(CostEstimate::new(
total_cpu,
input_cost.io_cost,
total_memory,
0.0,
input_cost.cardinality,
))
}
fn estimate_slice_cost(
&self,
input: &Algebra,
offset: Option<usize>,
limit: Option<usize>,
) -> Result<CostEstimate> {
let input_cost = self.estimate_cost_recursive(input)?;
let offset_val = offset.unwrap_or(0);
let limit_val = limit.unwrap_or(input_cost.cardinality);
let result_cardinality = limit_val.min(input_cost.cardinality.saturating_sub(offset_val));
let slice_cpu = result_cardinality as f64 * self.config.cpu_cost_per_op * 0.01;
Ok(CostEstimate::new(
input_cost.cpu_cost + slice_cpu,
input_cost.io_cost,
input_cost.memory_cost,
0.0,
result_cardinality,
))
}
fn estimate_group_cost(&self, input: &Algebra) -> Result<CostEstimate> {
let input_cost = self.estimate_cost_recursive(input)?;
let group_cardinality = (input_cost.cardinality as f64 * 0.1) as usize; let group_cost = input_cost.cardinality as f64
* (input_cost.cardinality as f64).log2()
* self.config.cpu_cost_per_op;
let total_cpu = input_cost.cpu_cost + group_cost;
let total_memory = input_cost.memory_cost * 1.5;
Ok(CostEstimate::new(
total_cpu,
input_cost.io_cost,
total_memory,
0.0,
group_cardinality,
))
}
fn estimate_join_cost_detailed(
&self,
left_cardinality: usize,
right_cardinality: usize,
selectivity: f64,
algorithm: JoinAlgorithm,
) -> CostEstimate {
let result_cardinality =
(left_cardinality as f64 * right_cardinality as f64 * selectivity) as usize;
match algorithm {
JoinAlgorithm::HashJoin => {
let build_cost =
left_cardinality.min(right_cardinality) as f64 * self.config.cpu_cost_per_op;
let probe_cost =
left_cardinality.max(right_cardinality) as f64 * self.config.cpu_cost_per_op;
let cpu_cost = (build_cost + probe_cost)
* self.config.calibration.join_factors.hash_join_factor;
let memory_cost = (left_cardinality.min(right_cardinality) * 50) as f64
* self.config.memory_cost_per_byte;
let io_cost = 0.0;
CostEstimate::new(cpu_cost, io_cost, memory_cost, 0.0, result_cardinality)
}
JoinAlgorithm::SortMergeJoin => {
let sort_cost_left = left_cardinality as f64
* (left_cardinality as f64).log2()
* self.config.cpu_cost_per_op;
let sort_cost_right = right_cardinality as f64
* (right_cardinality as f64).log2()
* self.config.cpu_cost_per_op;
let merge_cost =
(left_cardinality + right_cardinality) as f64 * self.config.cpu_cost_per_op;
let cpu_cost = (sort_cost_left + sort_cost_right + merge_cost)
* self.config.calibration.join_factors.sort_merge_join_factor;
let memory_cost = (left_cardinality + right_cardinality) as f64
* 50.0
* self.config.memory_cost_per_byte;
let io_cost = 0.0;
CostEstimate::new(cpu_cost, io_cost, memory_cost, 0.0, result_cardinality)
}
JoinAlgorithm::NestedLoopJoin => {
let cpu_cost = (left_cardinality as f64 * right_cardinality as f64)
* self.config.cpu_cost_per_op
* self.config.calibration.join_factors.nested_loop_join_factor;
let memory_cost = 1000.0 * self.config.memory_cost_per_byte; let io_cost = 0.0;
CostEstimate::new(cpu_cost, io_cost, memory_cost, 0.0, result_cardinality)
}
JoinAlgorithm::IndexJoin => {
let cpu_cost = (left_cardinality as f64 * (right_cardinality as f64).log2())
* self.config.cpu_cost_per_op
* self.config.calibration.join_factors.index_join_factor;
let io_cost = left_cardinality as f64 * self.config.io_cost_per_page * 0.1; let memory_cost = 5000.0 * self.config.memory_cost_per_byte;
CostEstimate::new(cpu_cost, io_cost, memory_cost, 0.0, result_cardinality)
}
}
}
fn choose_join_algorithm(&self, left_size: usize, right_size: usize) -> JoinAlgorithm {
let smaller = left_size.min(right_size);
let larger = left_size.max(right_size);
if smaller < 1000 {
JoinAlgorithm::NestedLoopJoin
} else if smaller < 10000 && larger > smaller * 10 {
JoinAlgorithm::IndexJoin
} else if smaller * 8 < self.config.available_memory / 100 {
JoinAlgorithm::HashJoin
} else {
JoinAlgorithm::SortMergeJoin
}
}
fn estimate_pattern_selectivity(&self, pattern: &TriplePattern) -> f64 {
let mut specificity = 0;
if !matches!(pattern.subject, crate::algebra::Term::Variable(_)) {
specificity += 1;
}
if !matches!(pattern.predicate, crate::algebra::Term::Variable(_)) {
specificity += 1;
}
if !matches!(pattern.object, crate::algebra::Term::Variable(_)) {
specificity += 1;
}
match specificity {
0 => 1.0, 1 => 0.1, 2 => 0.01, 3 => 0.001, _ => 0.0001,
}
}
fn determine_io_pattern(&self, pattern: &TriplePattern) -> IOPattern {
if matches!(pattern.subject, crate::algebra::Term::Variable(_))
&& matches!(pattern.predicate, crate::algebra::Term::Variable(_))
&& matches!(pattern.object, crate::algebra::Term::Variable(_))
{
IOPattern::FullScan
} else if !matches!(pattern.predicate, crate::algebra::Term::Variable(_)) {
IOPattern::IndexScan
} else {
IOPattern::Random
}
}
fn estimate_pages_accessed(&self, cardinality: usize, pattern: IOPattern) -> usize {
let bytes_per_triple = 100; let total_bytes = cardinality * bytes_per_triple;
let pages = (total_bytes + self.config.page_size - 1) / self.config.page_size;
match pattern {
IOPattern::Sequential => pages,
IOPattern::Random => pages * 2, IOPattern::IndexScan => pages / 2, IOPattern::FullScan => pages,
}
}
fn estimate_expression_selectivity(&self, expression: &Expression) -> f64 {
match expression {
Expression::Binary { op: operator, .. } => match operator {
crate::algebra::BinaryOperator::Equal => 0.1,
crate::algebra::BinaryOperator::NotEqual => 0.9,
crate::algebra::BinaryOperator::Less => 0.3,
crate::algebra::BinaryOperator::LessEqual => 0.4,
crate::algebra::BinaryOperator::Greater => 0.3,
crate::algebra::BinaryOperator::GreaterEqual => 0.4,
_ => 0.5,
},
Expression::Function { name, .. } => match name.as_str() {
"contains" => 0.2,
"startsWith" => 0.1,
"endsWith" => 0.1,
"regex" => 0.05,
_ => 0.5,
},
_ => 0.5, }
}
fn estimate_expression_complexity(&self, expression: &Expression) -> f64 {
match expression {
Expression::Variable(_) | Expression::Literal(_) => 1.0,
Expression::Binary { .. } => 2.0,
Expression::Unary { .. } => 1.5,
Expression::Function { name, args } => {
let base_cost = match name.as_str() {
"regex" => 10.0,
"contains" => 3.0,
"startsWith" | "endsWith" => 2.0,
_ => 5.0,
};
base_cost + args.len() as f64
}
Expression::Conditional { .. } => 3.0,
_ => 2.0,
}
}
fn algebra_to_key(&self, algebra: &Algebra) -> String {
format!("{algebra:?}")
}
pub fn clear_cache(&mut self) {
self.cached_estimates.clear();
}
pub fn update_with_feedback(
&mut self,
algebra: &Algebra,
actual_cost: f64,
actual_cardinality: usize,
) {
let predicted = self.estimate_cost(algebra).unwrap_or(CostEstimate::zero());
if predicted.total_cost > 0.0 {
let cost_ratio = actual_cost / predicted.total_cost;
let _cardinality_ratio = actual_cardinality as f64 / predicted.cardinality as f64;
self.config.calibration.cpu_scale =
(self.config.calibration.cpu_scale + cost_ratio) / 2.0;
self.clear_cache();
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JoinAlgorithm {
HashJoin,
SortMergeJoin,
NestedLoopJoin,
IndexJoin,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algebra::{Term, Variable};
use oxirs_core::model::NamedNode;
#[test]
fn test_triple_pattern_cost_estimation() {
let cost_model = CostModel::new(CostModelConfig::default());
let pattern = TriplePattern {
subject: Term::Variable(Variable::new("s").unwrap()),
predicate: Term::Iri(NamedNode::new_unchecked("http://example.org/predicate")),
object: Term::Variable(Variable::new("o").unwrap()),
};
let cost = cost_model.estimate_triple_pattern_cost(&pattern).unwrap();
assert!(cost.total_cost > 0.0);
assert!(cost.cardinality > 0);
assert!(cost.selectivity > 0.0 && cost.selectivity <= 1.0);
}
#[test]
fn test_join_algorithm_selection() {
let cost_model = CostModel::new(CostModelConfig::default());
assert_eq!(
cost_model.choose_join_algorithm(100, 1000),
JoinAlgorithm::NestedLoopJoin
);
assert_eq!(
cost_model.choose_join_algorithm(10000, 100000),
JoinAlgorithm::HashJoin
);
}
#[test]
fn test_cost_estimate_operations() {
let mut estimate = CostEstimate::new(10.0, 5.0, 2.0, 1.0, 1000);
estimate.add_operation_cost("test_op", 3.0);
assert_eq!(estimate.total_cost, 21.0); assert!(estimate.operation_costs.contains_key("test_op"));
}
#[test]
fn test_pattern_selectivity_estimation() {
let cost_model = CostModel::new(CostModelConfig::default());
let pattern1 = TriplePattern {
subject: Term::Variable(Variable::new("s").unwrap()),
predicate: Term::Variable(Variable::new("p").unwrap()),
object: Term::Variable(Variable::new("o").unwrap()),
};
let selectivity1 = cost_model.estimate_pattern_selectivity(&pattern1);
assert_eq!(selectivity1, 1.0);
let pattern2 = TriplePattern {
subject: Term::Variable(Variable::new("s").unwrap()),
predicate: Term::Iri(NamedNode::new_unchecked("http://example.org/predicate")),
object: Term::Variable(Variable::new("o").unwrap()),
};
let selectivity2 = cost_model.estimate_pattern_selectivity(&pattern2);
assert_eq!(selectivity2, 0.1);
}
}