use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default)]
pub struct QueryHints {
pub preferred_index: Option<IndexType>,
pub limit: Option<usize>,
pub offset: Option<usize>,
pub use_bloom_filter: Option<bool>,
pub enable_caching: Option<bool>,
pub estimated_result_size: Option<usize>,
pub timeout_ms: Option<u64>,
pub collect_stats: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum IndexType {
SPO,
POS,
OSP,
}
impl QueryHints {
pub fn new() -> Self {
Self::default()
}
pub fn with_index(mut self, index: IndexType) -> Self {
self.preferred_index = Some(index);
self
}
pub fn with_limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn with_offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn with_bloom_filter(mut self, enable: bool) -> Self {
self.use_bloom_filter = Some(enable);
self
}
pub fn with_caching(mut self, enable: bool) -> Self {
self.enable_caching = Some(enable);
self
}
pub fn with_estimated_size(mut self, size: usize) -> Self {
self.estimated_result_size = Some(size);
self
}
pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
self.timeout_ms = Some(timeout_ms);
self
}
pub fn with_stats(mut self, collect: bool) -> Self {
self.collect_stats = collect;
self
}
pub fn auto_select_index(s_bound: bool, p_bound: bool, o_bound: bool) -> IndexType {
match (s_bound, p_bound, o_bound) {
(true, _, _) => IndexType::SPO, (false, true, _) => IndexType::POS, (false, false, true) => IndexType::OSP, (false, false, false) => IndexType::SPO, }
}
pub fn merge(&self, other: &QueryHints) -> QueryHints {
QueryHints {
preferred_index: other.preferred_index.or(self.preferred_index),
limit: other.limit.or(self.limit),
offset: other.offset.or(self.offset),
use_bloom_filter: other.use_bloom_filter.or(self.use_bloom_filter),
enable_caching: other.enable_caching.or(self.enable_caching),
estimated_result_size: other.estimated_result_size.or(self.estimated_result_size),
timeout_ms: other.timeout_ms.or(self.timeout_ms),
collect_stats: other.collect_stats || self.collect_stats,
}
}
pub fn apply_pagination<T>(&self, results: Vec<T>) -> Vec<T> {
let offset = self.offset.unwrap_or(0);
let limit = self.limit.unwrap_or(usize::MAX);
results.into_iter().skip(offset).take(limit).collect()
}
}
#[derive(Debug, Clone, Default)]
pub struct QueryStats {
pub pages_accessed: usize,
pub index_entries_scanned: usize,
pub results_found: usize,
pub execution_time_us: u64,
pub index_used: Option<IndexType>,
pub bloom_filter_used: bool,
pub bloom_false_positives: usize,
pub cached: bool,
}
impl QueryStats {
pub fn new() -> Self {
Self::default()
}
pub fn with_index(mut self, index: IndexType) -> Self {
self.index_used = Some(index);
self
}
pub fn with_bloom_filter(mut self, used: bool, false_positives: usize) -> Self {
self.bloom_filter_used = used;
self.bloom_false_positives = false_positives;
self
}
pub fn with_execution_time(mut self, time_us: u64) -> Self {
self.execution_time_us = time_us;
self
}
pub fn avg_time_per_result(&self) -> f64 {
if self.results_found == 0 {
0.0
} else {
self.execution_time_us as f64 / self.results_found as f64
}
}
pub fn bloom_filter_effectiveness(&self) -> f64 {
if !self.bloom_filter_used || self.index_entries_scanned == 0 {
0.0
} else {
1.0 - (self.bloom_false_positives as f64 / self.index_entries_scanned as f64)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_query_hints_builder() {
let hints = QueryHints::new()
.with_index(IndexType::SPO)
.with_limit(100)
.with_offset(10)
.with_bloom_filter(true)
.with_caching(false)
.with_timeout(5000);
assert_eq!(hints.preferred_index, Some(IndexType::SPO));
assert_eq!(hints.limit, Some(100));
assert_eq!(hints.offset, Some(10));
assert_eq!(hints.use_bloom_filter, Some(true));
assert_eq!(hints.enable_caching, Some(false));
assert_eq!(hints.timeout_ms, Some(5000));
}
#[test]
fn test_auto_select_index() {
assert_eq!(
QueryHints::auto_select_index(true, false, false),
IndexType::SPO
);
assert_eq!(
QueryHints::auto_select_index(true, true, false),
IndexType::SPO
);
assert_eq!(
QueryHints::auto_select_index(true, false, true),
IndexType::SPO
);
assert_eq!(
QueryHints::auto_select_index(false, true, false),
IndexType::POS
);
assert_eq!(
QueryHints::auto_select_index(false, true, true),
IndexType::POS
);
assert_eq!(
QueryHints::auto_select_index(false, false, true),
IndexType::OSP
);
assert_eq!(
QueryHints::auto_select_index(false, false, false),
IndexType::SPO
);
}
#[test]
fn test_hints_merge() {
let hints1 = QueryHints::new().with_limit(100).with_offset(10);
let hints2 = QueryHints::new().with_limit(50).with_bloom_filter(true);
let merged = hints1.merge(&hints2);
assert_eq!(merged.limit, Some(50)); assert_eq!(merged.offset, Some(10)); assert_eq!(merged.use_bloom_filter, Some(true)); }
#[test]
fn test_apply_pagination() {
let hints = QueryHints::new().with_limit(3).with_offset(2);
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let paginated = hints.apply_pagination(data);
assert_eq!(paginated, vec![3, 4, 5]); }
#[test]
fn test_apply_pagination_no_offset() {
let hints = QueryHints::new().with_limit(3);
let data = vec![1, 2, 3, 4, 5];
let paginated = hints.apply_pagination(data);
assert_eq!(paginated, vec![1, 2, 3]);
}
#[test]
fn test_apply_pagination_no_limit() {
let hints = QueryHints::new().with_offset(2);
let data = vec![1, 2, 3, 4, 5];
let paginated = hints.apply_pagination(data);
assert_eq!(paginated, vec![3, 4, 5]);
}
#[test]
fn test_query_stats() {
let stats = QueryStats::new()
.with_index(IndexType::POS)
.with_bloom_filter(true, 5)
.with_execution_time(1000);
assert_eq!(stats.index_used, Some(IndexType::POS));
assert!(stats.bloom_filter_used);
assert_eq!(stats.bloom_false_positives, 5);
assert_eq!(stats.execution_time_us, 1000);
}
#[test]
fn test_query_stats_avg_time() {
let mut stats = QueryStats::new().with_execution_time(1000);
stats.results_found = 10;
assert_eq!(stats.avg_time_per_result(), 100.0);
}
#[test]
fn test_bloom_filter_effectiveness() {
let mut stats = QueryStats::new().with_bloom_filter(true, 10);
stats.index_entries_scanned = 100;
assert_eq!(stats.bloom_filter_effectiveness(), 0.9);
}
}