use std::collections::{HashMap, HashSet};
use tracing::{debug, span, Level};
use crate::functions::{Expression, ExpressionEvaluator};
use crate::model::{StarGraph, StarTerm, StarTriple};
use crate::store::StarStore;
use crate::{StarError, StarResult};
#[derive(Debug, Clone, PartialEq)]
pub enum QueryType {
Select,
Construct,
Ask,
Describe,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Binding {
bindings: HashMap<String, StarTerm>,
}
impl Binding {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
}
}
pub fn bind(&mut self, variable: &str, term: StarTerm) {
self.bindings.insert(variable.to_string(), term);
}
pub fn get(&self, variable: &str) -> Option<&StarTerm> {
self.bindings.get(variable)
}
pub fn variables(&self) -> Vec<&String> {
self.bindings.keys().collect()
}
pub fn is_bound(&self, variable: &str) -> bool {
self.bindings.contains_key(variable)
}
pub fn merge(&self, other: &Binding) -> Option<Binding> {
let mut merged = self.clone();
for (var, term) in &other.bindings {
if let Some(existing_term) = merged.bindings.get(var) {
if existing_term != term {
return None; }
} else {
merged.bindings.insert(var.clone(), term.clone());
}
}
Some(merged)
}
}
impl Default for Binding {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct BasicGraphPattern {
patterns: Vec<TriplePattern>,
filters: Vec<Expression>,
}
#[derive(Debug, Clone)]
pub struct TriplePattern {
pub subject: TermPattern,
pub predicate: TermPattern,
pub object: TermPattern,
}
#[derive(Debug, Clone)]
pub enum TermPattern {
Term(StarTerm),
Variable(String),
QuotedTriplePattern(Box<TriplePattern>),
}
impl TermPattern {
pub fn matches(&self, term: &StarTerm, binding: &Binding) -> bool {
match self {
TermPattern::Term(pattern_term) => pattern_term == term,
TermPattern::Variable(var_name) => {
if let Some(bound_term) = binding.get(var_name) {
bound_term == term
} else {
true }
}
TermPattern::QuotedTriplePattern(pattern) => {
if let StarTerm::QuotedTriple(quoted_triple) = term {
pattern.matches(quoted_triple, binding)
} else {
false
}
}
}
}
pub fn extract_variables(&self, variables: &mut HashSet<String>) {
match self {
TermPattern::Term(_) => {}
TermPattern::Variable(var) => {
variables.insert(var.clone());
}
TermPattern::QuotedTriplePattern(pattern) => {
pattern.extract_variables(variables);
}
}
}
}
impl TriplePattern {
pub fn new(subject: TermPattern, predicate: TermPattern, object: TermPattern) -> Self {
Self {
subject,
predicate,
object,
}
}
pub fn matches(&self, triple: &StarTriple, binding: &Binding) -> bool {
self.subject.matches(&triple.subject, binding)
&& self.predicate.matches(&triple.predicate, binding)
&& self.object.matches(&triple.object, binding)
}
pub fn try_bind(&self, triple: &StarTriple, existing_binding: &Binding) -> Option<Binding> {
let mut new_binding = existing_binding.clone();
if !self.bind_term(&self.subject, &triple.subject, &mut new_binding) {
return None;
}
if !self.bind_term(&self.predicate, &triple.predicate, &mut new_binding) {
return None;
}
if !self.bind_term(&self.object, &triple.object, &mut new_binding) {
return None;
}
Some(new_binding)
}
fn bind_term(&self, pattern: &TermPattern, term: &StarTerm, binding: &mut Binding) -> bool {
match pattern {
TermPattern::Term(pattern_term) => pattern_term == term,
TermPattern::Variable(var_name) => {
if let Some(existing_term) = binding.get(var_name) {
existing_term == term
} else {
binding.bind(var_name, term.clone());
true
}
}
TermPattern::QuotedTriplePattern(quoted_pattern) => {
if let StarTerm::QuotedTriple(quoted_triple) = term {
if let Some(new_binding) = quoted_pattern.try_bind(quoted_triple, binding) {
for (var, value) in new_binding.bindings.iter() {
if !binding.is_bound(var) {
binding.bind(var, value.clone());
}
}
true
} else {
false
}
} else {
false
}
}
}
}
pub fn extract_variables(&self, variables: &mut HashSet<String>) {
self.subject.extract_variables(variables);
self.predicate.extract_variables(variables);
self.object.extract_variables(variables);
}
}
impl BasicGraphPattern {
pub fn new() -> Self {
Self {
patterns: Vec::new(),
filters: Vec::new(),
}
}
pub fn add_pattern(&mut self, pattern: TriplePattern) {
self.patterns.push(pattern);
}
pub fn add_filter(&mut self, filter: Expression) {
self.filters.push(filter);
}
pub fn patterns(&self) -> &[TriplePattern] {
&self.patterns
}
pub fn filters(&self) -> &[Expression] {
&self.filters
}
pub fn extract_variables(&self) -> HashSet<String> {
let mut variables = HashSet::new();
for pattern in &self.patterns {
pattern.extract_variables(&mut variables);
}
for filter in &self.filters {
Self::extract_variables_from_expr(filter, &mut variables);
}
variables
}
fn extract_variables_from_expr(expr: &Expression, variables: &mut HashSet<String>) {
match expr {
Expression::Variable(var) => {
variables.insert(var.clone());
}
Expression::FunctionCall { args, .. } => {
for arg in args {
Self::extract_variables_from_expr(arg, variables);
}
}
Expression::BinaryOp { left, right, .. } => {
Self::extract_variables_from_expr(left, variables);
Self::extract_variables_from_expr(right, variables);
}
Expression::UnaryOp { expr, .. } => {
Self::extract_variables_from_expr(expr, variables);
}
Expression::Term(_) => {}
}
}
}
impl Default for BasicGraphPattern {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum QueryOptimization {
None,
CostBased,
Heuristic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum JoinStrategy {
NestedLoop,
Hash,
Index,
}
#[derive(Debug, Clone, Default)]
pub struct QueryStatistics {
pub pattern_evaluations: usize,
pub join_operations: usize,
pub execution_time_us: u64,
pub intermediate_results: usize,
pub memory_usage_bytes: usize,
}
pub struct QueryExecutor {
store: StarStore,
optimization: QueryOptimization,
join_strategy: JoinStrategy,
statistics: QueryStatistics,
}
impl QueryExecutor {
pub fn new(store: StarStore) -> Self {
Self {
store,
optimization: QueryOptimization::Heuristic,
join_strategy: JoinStrategy::Index,
statistics: QueryStatistics::default(),
}
}
pub fn with_optimization(
store: StarStore,
optimization: QueryOptimization,
join_strategy: JoinStrategy,
) -> Self {
Self {
store,
optimization,
join_strategy,
statistics: QueryStatistics::default(),
}
}
pub fn statistics(&self) -> &QueryStatistics {
&self.statistics
}
pub fn reset_statistics(&mut self) {
self.statistics = QueryStatistics::default();
}
pub fn execute_bgp(&mut self, bgp: &BasicGraphPattern) -> StarResult<Vec<Binding>> {
let span = span!(Level::INFO, "execute_bgp");
let _enter = span.enter();
let start_time = std::time::Instant::now();
if bgp.patterns.is_empty() {
return Ok(vec![Binding::new()]);
}
let optimized_patterns = self.optimize_pattern_order(bgp)?;
let mut current_bindings =
self.execute_pattern_optimized(&optimized_patterns[0], &Binding::new())?;
self.statistics.pattern_evaluations += 1;
for pattern in &optimized_patterns[1..] {
current_bindings = self.join_with_pattern(current_bindings, pattern)?;
self.statistics.pattern_evaluations += 1; self.statistics.join_operations += 1;
self.statistics.intermediate_results += current_bindings.len();
}
if !bgp.filters.is_empty() {
current_bindings = self.apply_filters(current_bindings, &bgp.filters)?;
}
self.statistics.execution_time_us += start_time.elapsed().as_micros() as u64;
debug!(
"BGP execution produced {} bindings using {} strategy",
current_bindings.len(),
format!("{:?}", self.optimization)
);
Ok(current_bindings)
}
fn optimize_pattern_order(&self, bgp: &BasicGraphPattern) -> StarResult<Vec<TriplePattern>> {
match self.optimization {
QueryOptimization::None => Ok(bgp.patterns().to_vec()),
QueryOptimization::Heuristic => self.heuristic_optimization(bgp),
QueryOptimization::CostBased => self.cost_based_optimization(bgp),
}
}
fn heuristic_optimization(&self, bgp: &BasicGraphPattern) -> StarResult<Vec<TriplePattern>> {
let mut patterns = bgp.patterns().to_vec();
patterns.sort_by(|a, b| {
let a_selectivity = self.estimate_pattern_selectivity(a);
let b_selectivity = self.estimate_pattern_selectivity(b);
a_selectivity
.partial_cmp(&b_selectivity)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(patterns)
}
fn cost_based_optimization(&self, bgp: &BasicGraphPattern) -> StarResult<Vec<TriplePattern>> {
let mut patterns = bgp.patterns().to_vec();
patterns.sort_by(|a, b| {
let a_cost = self.estimate_pattern_cost(a);
let b_cost = self.estimate_pattern_cost(b);
a_cost
.partial_cmp(&b_cost)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(patterns)
}
fn estimate_pattern_selectivity(&self, pattern: &TriplePattern) -> f64 {
let mut selectivity = 1.0;
if matches!(pattern.subject, TermPattern::QuotedTriplePattern(_)) {
selectivity *= 0.1;
}
if matches!(pattern.object, TermPattern::QuotedTriplePattern(_)) {
selectivity *= 0.1;
}
if matches!(pattern.subject, TermPattern::Term(_)) {
selectivity *= 0.3;
}
if matches!(pattern.predicate, TermPattern::Term(_)) {
selectivity *= 0.2;
}
if matches!(pattern.object, TermPattern::Term(_)) {
selectivity *= 0.3;
}
selectivity
}
fn estimate_pattern_cost(&self, pattern: &TriplePattern) -> f64 {
let mut cost = 100.0;
if matches!(pattern.subject, TermPattern::QuotedTriplePattern(_)) {
cost += 500.0;
}
if matches!(pattern.object, TermPattern::QuotedTriplePattern(_)) {
cost += 500.0;
}
if matches!(pattern.subject, TermPattern::Term(_)) {
cost *= 0.5;
}
if matches!(pattern.predicate, TermPattern::Term(_)) {
cost *= 0.3;
}
if matches!(pattern.object, TermPattern::Term(_)) {
cost *= 0.5;
}
cost
}
fn execute_pattern_optimized(
&self,
pattern: &TriplePattern,
initial_binding: &Binding,
) -> StarResult<Vec<Binding>> {
if self.can_use_index_optimization(pattern) {
self.execute_pattern_with_index(pattern, initial_binding)
} else {
self.execute_pattern(pattern, initial_binding)
}
}
fn can_use_index_optimization(&self, pattern: &TriplePattern) -> bool {
matches!(
pattern.subject,
TermPattern::QuotedTriplePattern(_) | TermPattern::Term(_)
) || matches!(
pattern.predicate,
TermPattern::QuotedTriplePattern(_) | TermPattern::Term(_)
) || matches!(
pattern.object,
TermPattern::QuotedTriplePattern(_) | TermPattern::Term(_)
)
}
fn execute_pattern_with_index(
&self,
pattern: &TriplePattern,
initial_binding: &Binding,
) -> StarResult<Vec<Binding>> {
let mut bindings = Vec::new();
let subject_term = match &pattern.subject {
TermPattern::Term(term) => Some(term),
_ => None,
};
let predicate_term = match &pattern.predicate {
TermPattern::Term(term) => Some(term),
_ => None,
};
let object_term = match &pattern.object {
TermPattern::Term(term) => Some(term),
_ => None,
};
let matching_triples =
self.store
.query_triples(subject_term, predicate_term, object_term)?;
for triple in matching_triples.iter() {
if pattern.matches(triple, initial_binding) {
if let Some(new_binding) = pattern.try_bind(triple, initial_binding) {
bindings.push(new_binding);
}
}
}
Ok(bindings)
}
fn join_with_pattern(
&self,
current_bindings: Vec<Binding>,
pattern: &TriplePattern,
) -> StarResult<Vec<Binding>> {
match self.join_strategy {
JoinStrategy::NestedLoop => self.nested_loop_join(current_bindings, pattern),
JoinStrategy::Hash => self.hash_join(current_bindings, pattern),
JoinStrategy::Index => self.index_join(current_bindings, pattern),
}
}
fn nested_loop_join(
&self,
current_bindings: Vec<Binding>,
pattern: &TriplePattern,
) -> StarResult<Vec<Binding>> {
let mut new_bindings = Vec::new();
for binding in ¤t_bindings {
let pattern_bindings = self.execute_pattern_optimized(pattern, binding)?;
new_bindings.extend(pattern_bindings);
}
Ok(new_bindings)
}
fn hash_join(
&self,
current_bindings: Vec<Binding>,
pattern: &TriplePattern,
) -> StarResult<Vec<Binding>> {
self.nested_loop_join(current_bindings, pattern)
}
fn index_join(
&self,
current_bindings: Vec<Binding>,
pattern: &TriplePattern,
) -> StarResult<Vec<Binding>> {
let mut new_bindings = Vec::new();
let shared_vars = self.find_shared_variables(¤t_bindings, pattern);
if shared_vars.is_empty() {
return self.nested_loop_join(current_bindings, pattern);
}
for binding in ¤t_bindings {
let pattern_bindings = self.execute_pattern_with_binding_context(pattern, binding)?;
new_bindings.extend(pattern_bindings);
}
Ok(new_bindings)
}
fn find_shared_variables(&self, bindings: &[Binding], pattern: &TriplePattern) -> Vec<String> {
if bindings.is_empty() {
return Vec::new();
}
let mut pattern_vars = std::collections::HashSet::new();
pattern.extract_variables(&mut pattern_vars);
let binding_vars: std::collections::HashSet<String> =
bindings[0].variables().into_iter().cloned().collect();
pattern_vars.intersection(&binding_vars).cloned().collect()
}
fn execute_pattern_with_binding_context(
&self,
pattern: &TriplePattern,
binding: &Binding,
) -> StarResult<Vec<Binding>> {
let resolved_pattern = self.resolve_pattern_variables(pattern, binding);
self.execute_pattern_optimized(&resolved_pattern, binding)
}
fn resolve_pattern_variables(
&self,
pattern: &TriplePattern,
binding: &Binding,
) -> TriplePattern {
let subject = match &pattern.subject {
TermPattern::Variable(var) => {
if let Some(term) = binding.get(var) {
TermPattern::Term(term.clone())
} else {
pattern.subject.clone()
}
}
_ => pattern.subject.clone(),
};
let predicate = match &pattern.predicate {
TermPattern::Variable(var) => {
if let Some(term) = binding.get(var) {
TermPattern::Term(term.clone())
} else {
pattern.predicate.clone()
}
}
_ => pattern.predicate.clone(),
};
let object = match &pattern.object {
TermPattern::Variable(var) => {
if let Some(term) = binding.get(var) {
TermPattern::Term(term.clone())
} else {
pattern.object.clone()
}
}
_ => pattern.object.clone(),
};
TriplePattern::new(subject, predicate, object)
}
fn apply_filters(
&self,
bindings: Vec<Binding>,
filters: &[Expression],
) -> StarResult<Vec<Binding>> {
let mut filtered_bindings = Vec::new();
for binding in bindings {
let mut passes_all_filters = true;
for filter in filters {
let binding_map: HashMap<String, StarTerm> = binding.bindings.clone();
match ExpressionEvaluator::evaluate(filter, &binding_map) {
Ok(result) => {
if let Some(literal) = result.as_literal() {
if let Some(datatype) = &literal.datatype {
if datatype.iri == "http://www.w3.org/2001/XMLSchema#boolean" {
if literal.value != "true" {
passes_all_filters = false;
break;
}
} else {
passes_all_filters = false;
break;
}
} else {
if literal.value.is_empty()
|| literal.value == "false"
|| literal.value == "0"
{
passes_all_filters = false;
break;
}
}
} else {
passes_all_filters = false;
break;
}
}
Err(_) => {
passes_all_filters = false;
break;
}
}
}
if passes_all_filters {
filtered_bindings.push(binding);
}
}
Ok(filtered_bindings)
}
fn execute_pattern(
&self,
pattern: &TriplePattern,
initial_binding: &Binding,
) -> StarResult<Vec<Binding>> {
let mut bindings = Vec::new();
let triples = self.store.triples();
for triple in triples {
if pattern.matches(&triple, initial_binding) {
if let Some(new_binding) = pattern.try_bind(&triple, initial_binding) {
bindings.push(new_binding);
}
}
}
Ok(bindings)
}
pub fn execute_select(
&mut self,
bgp: &BasicGraphPattern,
select_vars: &[String],
) -> StarResult<Vec<HashMap<String, StarTerm>>> {
let span = span!(Level::INFO, "execute_select");
let _enter = span.enter();
let bindings = self.execute_bgp(bgp)?;
let mut results = Vec::new();
for binding in bindings {
let mut result = HashMap::new();
for var in select_vars {
if let Some(term) = binding.get(var) {
result.insert(var.clone(), term.clone());
}
}
if !result.is_empty() {
results.push(result);
}
}
debug!(
"SELECT query produced {} results with {} pattern evaluations",
results.len(),
self.statistics.pattern_evaluations
);
Ok(results)
}
pub fn execute_construct(
&mut self,
bgp: &BasicGraphPattern,
construct_patterns: &[TriplePattern],
) -> StarResult<StarGraph> {
let span = span!(Level::INFO, "execute_construct");
let _enter = span.enter();
let bindings = self.execute_bgp(bgp)?;
let mut constructed_graph = StarGraph::new();
for binding in bindings {
for pattern in construct_patterns {
if let Some(triple) = self.instantiate_pattern(pattern, &binding)? {
constructed_graph.insert(triple)?;
}
}
}
debug!(
"CONSTRUCT query produced {} triples with {} join operations",
constructed_graph.len(),
self.statistics.join_operations
);
Ok(constructed_graph)
}
pub fn execute_ask(&mut self, bgp: &BasicGraphPattern) -> StarResult<bool> {
let span = span!(Level::INFO, "execute_ask");
let _enter = span.enter();
let bindings = self.execute_bgp(bgp)?;
let result = !bindings.is_empty();
debug!(
"ASK query result: {} (execution time: {}µs)",
result, self.statistics.execution_time_us
);
Ok(result)
}
fn instantiate_pattern(
&self,
pattern: &TriplePattern,
binding: &Binding,
) -> StarResult<Option<StarTriple>> {
let subject = self.instantiate_term_pattern(&pattern.subject, binding)?;
let predicate = self.instantiate_term_pattern(&pattern.predicate, binding)?;
let object = self.instantiate_term_pattern(&pattern.object, binding)?;
if let (Some(s), Some(p), Some(o)) = (subject, predicate, object) {
Ok(Some(StarTriple::new(s, p, o)))
} else {
Ok(None)
}
}
fn instantiate_term_pattern(
&self,
pattern: &TermPattern,
binding: &Binding,
) -> StarResult<Option<StarTerm>> {
match pattern {
TermPattern::Term(term) => Ok(Some(term.clone())),
TermPattern::Variable(var) => Ok(binding.get(var).cloned()),
TermPattern::QuotedTriplePattern(quoted_pattern) => {
if let Some(triple) = self.instantiate_pattern(quoted_pattern, binding)? {
Ok(Some(StarTerm::quoted_triple(triple)))
} else {
Ok(None)
}
}
}
}
}
pub struct QueryParser;
impl QueryParser {
pub fn parse_simple_select(query: &str) -> StarResult<(Vec<String>, BasicGraphPattern)> {
let lines: Vec<&str> = query.lines().map(|l| l.trim()).collect();
let mut select_vars = Vec::new();
let mut bgp = BasicGraphPattern::new();
let mut in_where = false;
for line in lines {
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.to_uppercase().starts_with("SELECT") {
let parts: Vec<&str> = line.split_whitespace().collect();
for part in &parts[1..] {
if let Some(stripped) = part.strip_prefix('?') {
select_vars.push(stripped.to_string());
}
}
} else if line.to_uppercase().contains("WHERE") {
in_where = true;
} else if in_where && line.contains('.') {
if let Ok(pattern) = Self::parse_triple_pattern(line) {
bgp.add_pattern(pattern);
}
}
}
Ok((select_vars, bgp))
}
fn parse_triple_pattern(line: &str) -> StarResult<TriplePattern> {
let line = line.trim_end_matches('.').trim();
let parts = Self::tokenize_pattern(line)?;
if parts.len() != 3 {
return Err(StarError::query_error(format!(
"Invalid triple pattern: {line}"
)));
}
let subject = Self::parse_term_pattern(&parts[0])?;
let predicate = Self::parse_term_pattern(&parts[1])?;
let object = Self::parse_term_pattern(&parts[2])?;
Ok(TriplePattern::new(subject, predicate, object))
}
fn tokenize_pattern(pattern: &str) -> StarResult<Vec<String>> {
let mut tokens = Vec::new();
let mut current_token = String::new();
let mut chars = pattern.chars().peekable();
let mut depth = 0;
let mut in_string = false;
while let Some(ch) = chars.next() {
match ch {
'"' => {
in_string = !in_string;
current_token.push(ch);
}
'<' if !in_string && chars.peek() == Some(&'<') => {
chars.next(); depth += 1;
current_token.push_str("<<");
}
'>' if !in_string && chars.peek() == Some(&'>') => {
chars.next(); depth -= 1;
current_token.push_str(">>");
}
' ' | '\t' if !in_string && depth == 0 => {
if !current_token.trim().is_empty() {
tokens.push(current_token.trim().to_string());
current_token.clear();
}
}
_ => {
current_token.push(ch);
}
}
}
if !current_token.trim().is_empty() {
tokens.push(current_token.trim().to_string());
}
Ok(tokens)
}
fn parse_term_pattern(term_str: &str) -> StarResult<TermPattern> {
let term_str = term_str.trim();
if let Some(stripped) = term_str.strip_prefix('?') {
return Ok(TermPattern::Variable(stripped.to_string()));
}
if term_str.starts_with("<<") && term_str.ends_with(">>") {
let inner = &term_str[2..term_str.len() - 2];
let inner_pattern = Self::parse_triple_pattern(inner)?;
return Ok(TermPattern::QuotedTriplePattern(Box::new(inner_pattern)));
}
let term = Self::parse_concrete_term(term_str)?;
Ok(TermPattern::Term(term))
}
fn parse_concrete_term(term_str: &str) -> StarResult<StarTerm> {
if term_str.starts_with('<') && term_str.ends_with('>') {
let iri = &term_str[1..term_str.len() - 1];
return StarTerm::iri(iri);
}
if let Some(id) = term_str.strip_prefix("_:") {
return StarTerm::blank_node(id);
}
if term_str.starts_with('"') {
let end_quote = term_str.rfind('"').unwrap_or(term_str.len());
let value = &term_str[1..end_quote];
return StarTerm::literal(value);
}
Err(StarError::query_error(format!(
"Cannot parse term: {term_str}"
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binding_operations() {
let mut binding = Binding::new();
assert!(!binding.is_bound("x"));
binding.bind("x", StarTerm::iri("http://example.org/alice").unwrap());
assert!(binding.is_bound("x"));
assert_eq!(
binding.get("x"),
Some(&StarTerm::iri("http://example.org/alice").unwrap())
);
let mut other = Binding::new();
other.bind("y", StarTerm::literal("test").unwrap());
let merged = binding.merge(&other).unwrap();
assert!(merged.is_bound("x"));
assert!(merged.is_bound("y"));
}
#[test]
fn test_triple_pattern_matching() {
let pattern = TriplePattern::new(
TermPattern::Variable("x".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/knows").unwrap()),
TermPattern::Variable("y".to_string()),
);
let triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/knows").unwrap(),
StarTerm::iri("http://example.org/bob").unwrap(),
);
let binding = Binding::new();
assert!(pattern.matches(&triple, &binding));
let new_binding = pattern.try_bind(&triple, &binding).unwrap();
assert_eq!(
new_binding.get("x"),
Some(&StarTerm::iri("http://example.org/alice").unwrap())
);
assert_eq!(
new_binding.get("y"),
Some(&StarTerm::iri("http://example.org/bob").unwrap())
);
}
#[test]
fn test_quoted_triple_pattern() {
let inner_pattern = TriplePattern::new(
TermPattern::Variable("x".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/age").unwrap()),
TermPattern::Variable("age".to_string()),
);
let outer_pattern = TriplePattern::new(
TermPattern::QuotedTriplePattern(Box::new(inner_pattern)),
TermPattern::Term(StarTerm::iri("http://example.org/certainty").unwrap()),
TermPattern::Variable("cert".to_string()),
);
let inner_triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let outer_triple = StarTriple::new(
StarTerm::quoted_triple(inner_triple),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
);
let binding = Binding::new();
assert!(outer_pattern.matches(&outer_triple, &binding));
let new_binding = outer_pattern.try_bind(&outer_triple, &binding).unwrap();
assert!(new_binding.is_bound("x"));
assert!(new_binding.is_bound("age"));
assert!(new_binding.is_bound("cert"));
}
#[test]
fn test_bgp_execution() {
let store = StarStore::new();
let triple1 = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/knows").unwrap(),
StarTerm::iri("http://example.org/bob").unwrap(),
);
let triple2 = StarTriple::new(
StarTerm::iri("http://example.org/bob").unwrap(),
StarTerm::iri("http://example.org/knows").unwrap(),
StarTerm::iri("http://example.org/charlie").unwrap(),
);
store.insert(&triple1).unwrap();
store.insert(&triple2).unwrap();
let mut executor = QueryExecutor::new(store);
let mut bgp = BasicGraphPattern::new();
bgp.add_pattern(TriplePattern::new(
TermPattern::Variable("x".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/knows").unwrap()),
TermPattern::Variable("y".to_string()),
));
let bindings = executor.execute_bgp(&bgp).unwrap();
assert_eq!(bindings.len(), 2);
}
#[test]
fn test_query_optimization_strategies() {
let store = StarStore::new();
let inner_triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let quoted_triple = StarTriple::new(
StarTerm::quoted_triple(inner_triple),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
);
store.insert("ed_triple).unwrap();
for optimization in [
QueryOptimization::None,
QueryOptimization::Heuristic,
QueryOptimization::CostBased,
] {
for join_strategy in [
JoinStrategy::NestedLoop,
JoinStrategy::Index,
JoinStrategy::Hash,
] {
let mut executor =
QueryExecutor::with_optimization(store.clone(), optimization, join_strategy);
let mut bgp = BasicGraphPattern::new();
bgp.add_pattern(TriplePattern::new(
TermPattern::QuotedTriplePattern(Box::new(TriplePattern::new(
TermPattern::Variable("x".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/age").unwrap()),
TermPattern::Variable("age".to_string()),
))),
TermPattern::Term(StarTerm::iri("http://example.org/certainty").unwrap()),
TermPattern::Variable("cert".to_string()),
));
let bindings = executor.execute_bgp(&bgp).unwrap();
assert_eq!(bindings.len(), 1);
let stats = executor.statistics();
assert!(stats.pattern_evaluations > 0);
}
}
}
#[test]
fn test_advanced_sparql_star_queries() {
let store = StarStore::new();
let fact1 = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let fact2 = StarTriple::new(
StarTerm::iri("http://example.org/bob").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("30").unwrap(),
);
let meta1 = StarTriple::new(
StarTerm::quoted_triple(fact1.clone()),
StarTerm::iri("http://example.org/source").unwrap(),
StarTerm::literal("census").unwrap(),
);
let meta2 = StarTriple::new(
StarTerm::quoted_triple(fact2.clone()),
StarTerm::iri("http://example.org/source").unwrap(),
StarTerm::literal("survey").unwrap(),
);
store.insert(&fact1).unwrap();
store.insert(&fact2).unwrap();
store.insert(&meta1).unwrap();
store.insert(&meta2).unwrap();
let mut executor = QueryExecutor::with_optimization(
store,
QueryOptimization::CostBased,
JoinStrategy::Index,
);
let mut bgp = BasicGraphPattern::new();
bgp.add_pattern(TriplePattern::new(
TermPattern::QuotedTriplePattern(Box::new(TriplePattern::new(
TermPattern::Variable("person".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/age").unwrap()),
TermPattern::Variable("age".to_string()),
))),
TermPattern::Term(StarTerm::iri("http://example.org/source").unwrap()),
TermPattern::Term(StarTerm::literal("census").unwrap()),
));
let bindings = executor.execute_bgp(&bgp).unwrap();
assert_eq!(bindings.len(), 1);
let binding = &bindings[0];
assert!(binding.is_bound("person"));
assert!(binding.is_bound("age"));
if let Some(person_term) = binding.get("person") {
if let Some(person_node) = person_term.as_named_node() {
assert!(person_node.iri.contains("alice"));
}
}
}
#[test]
fn test_query_statistics_tracking() {
let store = StarStore::new();
for i in 0..10 {
let triple = StarTriple::new(
StarTerm::iri(&format!("http://example.org/person{i}")).unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal(&format!("{}", 20 + i)).unwrap(),
);
store.insert(&triple).unwrap();
}
let mut executor = QueryExecutor::new(store);
let mut bgp = BasicGraphPattern::new();
bgp.add_pattern(TriplePattern::new(
TermPattern::Variable("x".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/age").unwrap()),
TermPattern::Variable("age".to_string()),
));
bgp.add_pattern(TriplePattern::new(
TermPattern::Variable("x".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/age").unwrap()),
TermPattern::Variable("age2".to_string()),
));
let bindings = executor.execute_bgp(&bgp).unwrap();
assert_eq!(bindings.len(), 10);
let stats = executor.statistics();
assert!(stats.pattern_evaluations >= 2); assert!(stats.join_operations >= 1); assert!(stats.intermediate_results > 0);
}
#[test]
fn test_query_parser() {
let query = r#"
SELECT ?x ?y
WHERE {
?x <http://example.org/knows> ?y .
}
"#;
let (vars, bgp) = QueryParser::parse_simple_select(query).unwrap();
assert_eq!(vars, vec!["x", "y"]);
assert_eq!(bgp.patterns().len(), 1);
}
#[test]
fn test_quoted_triple_query_parsing() {
let query = r#"
SELECT ?cert
WHERE {
<< ?x <http://example.org/age> ?age >> <http://example.org/certainty> ?cert .
}
"#;
let (vars, bgp) = QueryParser::parse_simple_select(query).unwrap();
assert_eq!(vars, vec!["cert"]);
assert_eq!(bgp.patterns().len(), 1);
let pattern = &bgp.patterns()[0];
assert!(matches!(
pattern.subject,
TermPattern::QuotedTriplePattern(_)
));
}
#[test]
fn test_sparql_star_functions_in_filters() {
let store = StarStore::new();
let fact = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let meta = StarTriple::new(
StarTerm::quoted_triple(fact.clone()),
StarTerm::iri("http://example.org/source").unwrap(),
StarTerm::literal("census").unwrap(),
);
store.insert(&fact).unwrap();
store.insert(&meta).unwrap();
let mut executor = QueryExecutor::new(store);
let mut bgp = BasicGraphPattern::new();
bgp.add_pattern(TriplePattern::new(
TermPattern::Variable("quoted".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/source").unwrap()),
TermPattern::Variable("source".to_string()),
));
bgp.add_filter(Expression::is_triple(Expression::var("quoted")));
let bindings = executor.execute_bgp(&bgp).unwrap();
assert_eq!(bindings.len(), 1);
let binding = &bindings[0];
assert!(binding.is_bound("quoted"));
if let Some(quoted_term) = binding.get("quoted") {
assert!(quoted_term.is_quoted_triple());
}
}
#[test]
fn test_sparql_star_function_composition() {
let store = StarStore::new();
let inner = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/knows").unwrap(),
StarTerm::iri("http://example.org/bob").unwrap(),
);
let outer = StarTriple::new(
StarTerm::quoted_triple(inner.clone()),
StarTerm::iri("http://example.org/confidence").unwrap(),
StarTerm::literal("0.8").unwrap(),
);
store.insert(&inner).unwrap();
store.insert(&outer).unwrap();
let mut executor = QueryExecutor::new(store);
let mut bgp = BasicGraphPattern::new();
bgp.add_pattern(TriplePattern::new(
TermPattern::Variable("statement".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/confidence").unwrap()),
TermPattern::Variable("conf".to_string()),
));
let bindings = executor.execute_bgp(&bgp).unwrap();
assert_eq!(bindings.len(), 1);
let binding = &bindings[0];
if let Some(statement) = binding.get("statement") {
assert!(statement.is_quoted_triple());
let subject_result = crate::functions::FunctionEvaluator::evaluate(
crate::functions::StarFunction::Subject,
std::slice::from_ref(statement),
)
.unwrap();
assert_eq!(
subject_result,
StarTerm::iri("http://example.org/alice").unwrap()
);
}
}
#[test]
fn test_filter_with_triple_construction() {
let store = StarStore::new();
let triple1 = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let triple2 = StarTriple::new(
StarTerm::iri("http://example.org/bob").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("30").unwrap(),
);
store.insert(&triple1).unwrap();
store.insert(&triple2).unwrap();
let mut executor = QueryExecutor::new(store);
let mut bgp = BasicGraphPattern::new();
bgp.add_pattern(TriplePattern::new(
TermPattern::Variable("person".to_string()),
TermPattern::Term(StarTerm::iri("http://example.org/age").unwrap()),
TermPattern::Variable("age".to_string()),
));
let bindings = executor.execute_bgp(&bgp).unwrap();
assert_eq!(bindings.len(), 2);
}
}