Skip to main content

tensorlogic_oxirs_bridge/
oxirs_executor.rs

1//! OxiRS SPARQL execution integration.
2//!
3//! This module provides a bridge to OxiRS's SPARQL execution capabilities,
4//! enabling execution of SPARQL queries against RDF data stores.
5//!
6//! # Overview
7//!
8//! The `OxirsSparqlExecutor` provides:
9//! - Loading RDF data from Turtle, N-Triples, and other formats
10//! - Executing SPARQL queries (SELECT, ASK, CONSTRUCT, DESCRIBE)
11//! - Converting query results to TensorLogic expressions
12//!
13//! # Example
14//!
15//! ```no_run
16//! use tensorlogic_oxirs_bridge::oxirs_executor::OxirsSparqlExecutor;
17//!
18//! let mut executor = OxirsSparqlExecutor::new().expect("Failed to create executor");
19//!
20//! // Load RDF data
21//! executor.load_turtle(r#"
22//!     @prefix ex: <http://example.org/> .
23//!     ex:Alice ex:knows ex:Bob .
24//!     ex:Bob ex:knows ex:Carol .
25//! "#).unwrap();
26//!
27//! // Execute a query
28//! let results = executor.execute("SELECT ?s ?o WHERE { ?s <http://example.org/knows> ?o }").unwrap();
29//! ```
30
31use crate::sparql::{SparqlCompiler, SparqlQuery};
32use anyhow::{anyhow, Context, Result};
33use oxirs_core::model::{BlankNode, Literal, NamedNode, Object, Predicate, Subject};
34use oxirs_core::{RdfStore, Triple};
35use oxrdf::Term as OxrdfTerm;
36use oxttl::TurtleParser as OxTtlParser;
37use serde::{Deserialize, Serialize};
38use std::collections::HashMap;
39use tensorlogic_ir::TLExpr;
40
41/// Query results from SPARQL execution.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub enum QueryResults {
44    /// SELECT query results: variable bindings
45    Select {
46        /// Variable names in the result set
47        variables: Vec<String>,
48        /// Result rows, each containing bindings for each variable
49        bindings: Vec<HashMap<String, QueryValue>>,
50    },
51    /// ASK query result: boolean
52    Ask(bool),
53    /// CONSTRUCT query result: generated triples
54    Construct { triples: Vec<TripleResult> },
55    /// DESCRIBE query result: description triples
56    Describe { triples: Vec<TripleResult> },
57}
58
59/// A value in query results.
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub enum QueryValue {
62    /// IRI value
63    Iri(String),
64    /// Literal value with optional datatype and language
65    Literal {
66        value: String,
67        datatype: Option<String>,
68        language: Option<String>,
69    },
70    /// Blank node
71    BlankNode(String),
72}
73
74impl QueryValue {
75    /// Get the string value.
76    pub fn as_str(&self) -> &str {
77        match self {
78            QueryValue::Iri(s) => s,
79            QueryValue::Literal { value, .. } => value,
80            QueryValue::BlankNode(s) => s,
81        }
82    }
83
84    /// Check if this is an IRI.
85    pub fn is_iri(&self) -> bool {
86        matches!(self, QueryValue::Iri(_))
87    }
88
89    /// Check if this is a literal.
90    pub fn is_literal(&self) -> bool {
91        matches!(self, QueryValue::Literal { .. })
92    }
93}
94
95/// A triple in query results.
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct TripleResult {
98    pub subject: String,
99    pub predicate: String,
100    pub object: String,
101}
102
103/// OxiRS SPARQL executor.
104///
105/// This executor uses OxiRS's storage backend and query execution
106/// capabilities to process SPARQL queries against RDF data.
107pub struct OxirsSparqlExecutor {
108    /// Internal RDF store
109    store: RdfStore,
110    /// SPARQL compiler for parsing queries
111    compiler: SparqlCompiler,
112    /// Predicate mappings for TensorLogic conversion
113    predicate_mappings: HashMap<String, String>,
114    /// Base IRI for relative references
115    base_iri: Option<String>,
116}
117
118impl OxirsSparqlExecutor {
119    /// Create a new SPARQL executor.
120    pub fn new() -> Result<Self> {
121        Ok(Self {
122            store: RdfStore::new()?,
123            compiler: SparqlCompiler::new(),
124            predicate_mappings: HashMap::new(),
125            base_iri: None,
126        })
127    }
128
129    /// Set the base IRI for relative references.
130    pub fn set_base_iri(&mut self, base: &str) {
131        self.base_iri = Some(base.to_string());
132    }
133
134    /// Add a predicate mapping for TensorLogic conversion.
135    pub fn add_predicate_mapping(&mut self, iri: &str, name: &str) {
136        self.predicate_mappings
137            .insert(iri.to_string(), name.to_string());
138        self.compiler
139            .add_predicate_mapping(iri.to_string(), name.to_string());
140    }
141
142    /// Load RDF data from Turtle format.
143    pub fn load_turtle(&mut self, turtle: &str) -> Result<usize> {
144        let parser = OxTtlParser::new().for_slice(turtle.as_bytes());
145        let mut count = 0;
146
147        for result in parser {
148            let triple = result.context("Failed to parse Turtle")?;
149
150            // Convert to OxiRS triple and store
151            let triple = self.oxttl_to_oxirs_triple(&triple)?;
152            self.store.insert_triple(triple)?;
153            count += 1;
154        }
155
156        Ok(count)
157    }
158
159    /// Convert an oxttl Triple to an OxiRS Triple.
160    #[allow(deprecated)]
161    fn oxttl_to_oxirs_triple(&self, triple: &oxrdf::Triple) -> Result<Triple> {
162        let subject: Subject = match &triple.subject {
163            oxrdf::NamedOrBlankNode::NamedNode(n) => {
164                Subject::NamedNode(NamedNode::new(n.as_str())?)
165            }
166            oxrdf::NamedOrBlankNode::BlankNode(b) => {
167                Subject::BlankNode(BlankNode::new(b.as_str())?)
168            }
169        };
170
171        let predicate: Predicate = Predicate::NamedNode(NamedNode::new(triple.predicate.as_str())?);
172
173        let object: Object = match &triple.object {
174            OxrdfTerm::NamedNode(n) => Object::NamedNode(NamedNode::new(n.as_str())?),
175            OxrdfTerm::BlankNode(b) => Object::BlankNode(BlankNode::new(b.as_str())?),
176            OxrdfTerm::Literal(l) => Object::Literal(Literal::new(l.value())),
177            // RDF 1.2 quoted triples - convert to empty literal as fallback
178            #[allow(unreachable_patterns)]
179            _ => Object::Literal(Literal::new("")),
180        };
181
182        Ok(Triple::new(subject, predicate, object))
183    }
184
185    /// Load RDF data from N-Triples format.
186    pub fn load_ntriples(&mut self, ntriples: &str) -> Result<usize> {
187        use oxttl::NTriplesParser;
188        let parser = NTriplesParser::new().for_slice(ntriples.as_bytes());
189        let mut count = 0;
190
191        for result in parser {
192            let triple = result.context("Failed to parse N-Triples")?;
193            let triple = self.oxttl_to_oxirs_triple(&triple)?;
194            self.store.insert_triple(triple)?;
195            count += 1;
196        }
197
198        Ok(count)
199    }
200
201    /// Get the number of triples in the store.
202    pub fn num_triples(&self) -> usize {
203        self.store.len().unwrap_or(0)
204    }
205
206    /// Convert Subject to string representation.
207    fn subject_to_string(subject: &Subject) -> String {
208        match subject {
209            Subject::NamedNode(n) => n.as_str().to_string(),
210            Subject::BlankNode(b) => format!("_:{}", b.as_str()),
211            Subject::Variable(v) => format!("?{}", v.as_str()),
212            Subject::QuotedTriple(_) => "[QuotedTriple]".to_string(),
213        }
214    }
215
216    /// Convert Predicate to string representation.
217    fn predicate_to_string(predicate: &Predicate) -> String {
218        match predicate {
219            Predicate::NamedNode(n) => n.as_str().to_string(),
220            Predicate::Variable(v) => format!("?{}", v.as_str()),
221        }
222    }
223
224    /// Convert Object to string representation.
225    fn object_to_string(object: &Object) -> String {
226        match object {
227            Object::NamedNode(n) => n.as_str().to_string(),
228            Object::BlankNode(b) => format!("_:{}", b.as_str()),
229            Object::Literal(l) => l.value().to_string(),
230            Object::Variable(v) => format!("?{}", v.as_str()),
231            Object::QuotedTriple(_) => "[QuotedTriple]".to_string(),
232        }
233    }
234
235    /// Get all triples from the store.
236    fn get_all_triples(&self) -> Vec<Triple> {
237        self.store.triples().unwrap_or_default()
238    }
239
240    /// Query triples by subject.
241    fn query_by_subject(&self, subject_str: &str) -> Vec<Triple> {
242        if let Ok(subject_node) = NamedNode::new(subject_str) {
243            let subject = Subject::NamedNode(subject_node);
244            self.store
245                .query_triples(Some(&subject), None, None)
246                .unwrap_or_default()
247        } else {
248            Vec::new()
249        }
250    }
251
252    /// Query triples by predicate.
253    fn query_by_predicate(&self, predicate_str: &str) -> Vec<Triple> {
254        if let Ok(predicate_node) = NamedNode::new(predicate_str) {
255            let predicate = Predicate::NamedNode(predicate_node);
256            self.store
257                .query_triples(None, Some(&predicate), None)
258                .unwrap_or_default()
259        } else {
260            Vec::new()
261        }
262    }
263
264    /// Query triples by object (IRI only).
265    fn query_by_object(&self, object_str: &str) -> Vec<Triple> {
266        if let Ok(object_node) = NamedNode::new(object_str) {
267            let object = Object::NamedNode(object_node);
268            self.store
269                .query_triples(None, None, Some(&object))
270                .unwrap_or_default()
271        } else {
272            // Try as literal
273            let object = Object::Literal(Literal::new(object_str));
274            self.store
275                .query_triples(None, None, Some(&object))
276                .unwrap_or_default()
277        }
278    }
279
280    /// Execute a SPARQL query.
281    pub fn execute(&self, sparql: &str) -> Result<QueryResults> {
282        // Parse the query
283        let query = self.compiler.parse_query(sparql)?;
284
285        // Execute based on query type
286        self.execute_query(&query)
287    }
288
289    /// Execute a parsed query.
290    fn execute_query(&self, query: &SparqlQuery) -> Result<QueryResults> {
291        use crate::sparql::QueryType;
292
293        match &query.query_type {
294            QueryType::Select {
295                select_vars,
296                distinct,
297                ..
298            } => {
299                let bindings = self.execute_pattern(&query.where_pattern)?;
300
301                // Project to selected variables
302                let projected: Vec<HashMap<String, QueryValue>> =
303                    if select_vars.contains(&"*".to_string()) {
304                        bindings
305                    } else {
306                        bindings
307                            .into_iter()
308                            .map(|b| {
309                                b.into_iter()
310                                    .filter(|(k, _)| select_vars.contains(k))
311                                    .collect()
312                            })
313                            .collect()
314                    };
315
316                // Handle DISTINCT
317                let results = if *distinct {
318                    self.deduplicate_bindings(projected)
319                } else {
320                    projected
321                };
322
323                // Apply LIMIT and OFFSET
324                let results = self.apply_modifiers(results, query.limit, query.offset);
325
326                Ok(QueryResults::Select {
327                    variables: select_vars.clone(),
328                    bindings: results,
329                })
330            }
331            QueryType::Ask => {
332                let bindings = self.execute_pattern(&query.where_pattern)?;
333                Ok(QueryResults::Ask(!bindings.is_empty()))
334            }
335            QueryType::Construct { template } => {
336                use crate::sparql::PatternElement;
337                let bindings = self.execute_pattern(&query.where_pattern)?;
338                let mut triples = Vec::new();
339
340                for binding in bindings {
341                    for pattern in template {
342                        let subject = match &pattern.subject {
343                            PatternElement::Variable(v) => binding
344                                .get(v)
345                                .map(|q| q.as_str().to_string())
346                                .unwrap_or_default(),
347                            PatternElement::Constant(c) => c.clone(),
348                        };
349                        let predicate = match &pattern.predicate {
350                            PatternElement::Variable(v) => binding
351                                .get(v)
352                                .map(|q| q.as_str().to_string())
353                                .unwrap_or_default(),
354                            PatternElement::Constant(c) => c.clone(),
355                        };
356                        let object = match &pattern.object {
357                            PatternElement::Variable(v) => binding
358                                .get(v)
359                                .map(|q| q.as_str().to_string())
360                                .unwrap_or_default(),
361                            PatternElement::Constant(c) => c.clone(),
362                        };
363
364                        if !subject.is_empty() && !predicate.is_empty() && !object.is_empty() {
365                            triples.push(TripleResult {
366                                subject,
367                                predicate,
368                                object,
369                            });
370                        }
371                    }
372                }
373
374                Ok(QueryResults::Construct { triples })
375            }
376            QueryType::Describe { resources } => {
377                let mut triples = Vec::new();
378
379                for resource in resources {
380                    // Get all triples where resource is subject
381                    let outgoing = self.query_by_subject(resource);
382                    for triple in outgoing {
383                        triples.push(TripleResult {
384                            subject: Self::subject_to_string(triple.subject()),
385                            predicate: Self::predicate_to_string(triple.predicate()),
386                            object: Self::object_to_string(triple.object()),
387                        });
388                    }
389
390                    // Get all triples where resource is object
391                    let incoming = self.query_by_object(resource);
392                    for triple in incoming {
393                        triples.push(TripleResult {
394                            subject: Self::subject_to_string(triple.subject()),
395                            predicate: Self::predicate_to_string(triple.predicate()),
396                            object: Self::object_to_string(triple.object()),
397                        });
398                    }
399                }
400
401                Ok(QueryResults::Describe { triples })
402            }
403        }
404    }
405
406    /// Execute a graph pattern and return bindings.
407    fn execute_pattern(
408        &self,
409        pattern: &crate::sparql::GraphPattern,
410    ) -> Result<Vec<HashMap<String, QueryValue>>> {
411        use crate::sparql::{GraphPattern, PatternElement};
412
413        match pattern {
414            GraphPattern::Triple(triple) => {
415                let mut results = Vec::new();
416
417                // Query the store based on bound elements
418                let triples = match (&triple.subject, &triple.predicate, &triple.object) {
419                    (PatternElement::Constant(s), _, _) => self.query_by_subject(s),
420                    (_, PatternElement::Constant(p), _) => self.query_by_predicate(p),
421                    (_, _, PatternElement::Constant(o)) => self.query_by_object(o),
422                    _ => self.get_all_triples(),
423                };
424
425                for t in triples {
426                    let mut binding = HashMap::new();
427                    let t_subject = Self::subject_to_string(t.subject());
428                    let t_predicate = Self::predicate_to_string(t.predicate());
429                    let t_object = Self::object_to_string(t.object());
430
431                    // Bind subject
432                    if let PatternElement::Variable(v) = &triple.subject {
433                        binding.insert(v.clone(), QueryValue::Iri(t_subject.clone()));
434                    } else if let PatternElement::Constant(c) = &triple.subject {
435                        if t_subject != *c {
436                            continue;
437                        }
438                    }
439
440                    // Bind predicate
441                    if let PatternElement::Variable(v) = &triple.predicate {
442                        binding.insert(v.clone(), QueryValue::Iri(t_predicate.clone()));
443                    } else if let PatternElement::Constant(c) = &triple.predicate {
444                        if t_predicate != *c {
445                            continue;
446                        }
447                    }
448
449                    // Bind object
450                    if let PatternElement::Variable(v) = &triple.object {
451                        let value = if t_object.starts_with("http") {
452                            QueryValue::Iri(t_object.clone())
453                        } else {
454                            QueryValue::Literal {
455                                value: t_object.clone(),
456                                datatype: None,
457                                language: None,
458                            }
459                        };
460                        binding.insert(v.clone(), value);
461                    } else if let PatternElement::Constant(c) = &triple.object {
462                        if t_object != *c {
463                            continue;
464                        }
465                    }
466
467                    results.push(binding);
468                }
469
470                Ok(results)
471            }
472            GraphPattern::Group(patterns) => {
473                if patterns.is_empty() {
474                    return Ok(vec![HashMap::new()]);
475                }
476
477                let mut current_results = self.execute_pattern(&patterns[0])?;
478
479                for pattern in patterns.iter().skip(1) {
480                    let new_results = self.execute_pattern(pattern)?;
481                    current_results = self.join_bindings(current_results, new_results);
482                }
483
484                Ok(current_results)
485            }
486            GraphPattern::Optional(inner) => {
487                // OPTIONAL: left outer join semantics
488                let inner_results = self.execute_pattern(inner)?;
489                if inner_results.is_empty() {
490                    Ok(vec![HashMap::new()])
491                } else {
492                    Ok(inner_results)
493                }
494            }
495            GraphPattern::Union(left, right) => {
496                let mut left_results = self.execute_pattern(left)?;
497                let right_results = self.execute_pattern(right)?;
498                left_results.extend(right_results);
499                Ok(left_results)
500            }
501            GraphPattern::Filter(_condition) => {
502                // Filters are applied during pattern matching
503                // For now, return empty (would need context from parent pattern)
504                Ok(Vec::new())
505            }
506        }
507    }
508
509    /// Join two sets of bindings.
510    fn join_bindings(
511        &self,
512        left: Vec<HashMap<String, QueryValue>>,
513        right: Vec<HashMap<String, QueryValue>>,
514    ) -> Vec<HashMap<String, QueryValue>> {
515        let mut results = Vec::new();
516
517        for l in &left {
518            for r in &right {
519                // Check for compatible bindings
520                let mut compatible = true;
521                for (key, lval) in l {
522                    if let Some(rval) = r.get(key) {
523                        if lval.as_str() != rval.as_str() {
524                            compatible = false;
525                            break;
526                        }
527                    }
528                }
529
530                if compatible {
531                    // Merge bindings
532                    let mut merged = l.clone();
533                    for (key, rval) in r {
534                        merged.entry(key.clone()).or_insert_with(|| rval.clone());
535                    }
536                    results.push(merged);
537                }
538            }
539        }
540
541        results
542    }
543
544    /// Remove duplicate bindings.
545    fn deduplicate_bindings(
546        &self,
547        bindings: Vec<HashMap<String, QueryValue>>,
548    ) -> Vec<HashMap<String, QueryValue>> {
549        let mut seen = std::collections::HashSet::new();
550        let mut results = Vec::new();
551
552        for binding in bindings {
553            let key: Vec<_> = binding
554                .iter()
555                .map(|(k, v)| format!("{}={}", k, v.as_str()))
556                .collect();
557            let key = key.join(",");
558
559            if !seen.contains(&key) {
560                seen.insert(key);
561                results.push(binding);
562            }
563        }
564
565        results
566    }
567
568    /// Apply LIMIT and OFFSET modifiers.
569    fn apply_modifiers(
570        &self,
571        bindings: Vec<HashMap<String, QueryValue>>,
572        limit: Option<usize>,
573        offset: Option<usize>,
574    ) -> Vec<HashMap<String, QueryValue>> {
575        let offset = offset.unwrap_or(0);
576        let mut results: Vec<_> = bindings.into_iter().skip(offset).collect();
577
578        if let Some(limit) = limit {
579            results.truncate(limit);
580        }
581
582        results
583    }
584
585    /// Execute a SPARQL query and convert results to TensorLogic expressions.
586    pub fn execute_to_tlexpr(&self, sparql: &str) -> Result<TLExpr> {
587        let query = self.compiler.parse_query(sparql)?;
588        self.compiler.compile_to_tensorlogic(&query)
589    }
590
591    /// Convert query results to TensorLogic expression.
592    pub fn results_to_tlexpr(&self, results: &QueryResults) -> Result<TLExpr> {
593        match results {
594            QueryResults::Ask(value) => {
595                if *value {
596                    Ok(TLExpr::pred("true", vec![]))
597                } else {
598                    Ok(TLExpr::pred("false", vec![]))
599                }
600            }
601            QueryResults::Select { bindings, .. } => {
602                if bindings.is_empty() {
603                    return Ok(TLExpr::pred("empty", vec![]));
604                }
605
606                // Convert bindings to conjunctions of predicates
607                let mut exprs = Vec::new();
608                for binding in bindings {
609                    let mut terms = Vec::new();
610                    for (var, value) in binding {
611                        let pred_name = self
612                            .predicate_mappings
613                            .get(value.as_str())
614                            .cloned()
615                            .unwrap_or_else(|| var.clone());
616                        terms.push(TLExpr::pred(
617                            &pred_name,
618                            vec![tensorlogic_ir::Term::constant(value.as_str())],
619                        ));
620                    }
621
622                    if !terms.is_empty() {
623                        let conjunction = terms.into_iter().reduce(TLExpr::and);
624                        if let Some(expr) = conjunction {
625                            exprs.push(expr);
626                        }
627                    }
628                }
629
630                // Return disjunction of all bindings
631                exprs
632                    .into_iter()
633                    .reduce(TLExpr::or)
634                    .ok_or_else(|| anyhow!("Empty results"))
635            }
636            QueryResults::Construct { triples } | QueryResults::Describe { triples } => {
637                // Convert triples to predicates
638                let mut exprs = Vec::new();
639                for triple in triples {
640                    let pred_name = self
641                        .predicate_mappings
642                        .get(&triple.predicate)
643                        .cloned()
644                        .unwrap_or_else(|| Self::iri_to_name(&triple.predicate));
645                    let expr = TLExpr::pred(
646                        &pred_name,
647                        vec![
648                            tensorlogic_ir::Term::constant(&triple.subject),
649                            tensorlogic_ir::Term::constant(&triple.object),
650                        ],
651                    );
652                    exprs.push(expr);
653                }
654
655                exprs
656                    .into_iter()
657                    .reduce(TLExpr::and)
658                    .ok_or_else(|| anyhow!("Empty results"))
659            }
660        }
661    }
662
663    /// Extract local name from an IRI.
664    fn iri_to_name(iri: &str) -> String {
665        iri.split(['/', '#']).next_back().unwrap_or(iri).to_string()
666    }
667}
668
669#[cfg(test)]
670mod tests {
671    use super::*;
672
673    #[test]
674    fn test_executor_creation() {
675        let executor = OxirsSparqlExecutor::new().expect("Failed to create executor");
676        assert_eq!(executor.num_triples(), 0);
677    }
678
679    #[test]
680    fn test_load_turtle() {
681        let mut executor = OxirsSparqlExecutor::new().expect("Failed to create executor");
682        let turtle = r#"
683            @prefix ex: <http://example.org/> .
684            ex:Alice ex:knows ex:Bob .
685            ex:Bob ex:knows ex:Carol .
686        "#;
687
688        let result = executor.load_turtle(turtle);
689        assert!(result.is_ok());
690        assert_eq!(executor.num_triples(), 2);
691    }
692
693    #[test]
694    fn test_execute_select() {
695        let mut executor = OxirsSparqlExecutor::new().expect("Failed to create executor");
696        executor
697            .load_turtle(
698                r#"
699            @prefix ex: <http://example.org/> .
700            ex:Alice ex:knows ex:Bob .
701        "#,
702            )
703            .expect("Load failed");
704
705        let query = "SELECT ?s ?o WHERE { ?s <http://example.org/knows> ?o }";
706        let result = executor.execute(query);
707
708        assert!(result.is_ok());
709        match result.expect("Query failed") {
710            QueryResults::Select { bindings, .. } => {
711                assert!(!bindings.is_empty());
712            }
713            _ => panic!("Expected SELECT results"),
714        }
715    }
716
717    #[test]
718    fn test_execute_ask() {
719        let mut executor = OxirsSparqlExecutor::new().expect("Failed to create executor");
720        executor
721            .load_turtle(
722                r#"
723            @prefix ex: <http://example.org/> .
724            ex:Alice ex:knows ex:Bob .
725        "#,
726            )
727            .expect("Load failed");
728
729        let query = "ASK WHERE { ?s <http://example.org/knows> ?o }";
730        let result = executor.execute(query);
731
732        assert!(result.is_ok());
733        match result.expect("Query failed") {
734            QueryResults::Ask(value) => {
735                assert!(value);
736            }
737            _ => panic!("Expected ASK results"),
738        }
739    }
740
741    #[test]
742    fn test_predicate_mapping() {
743        let mut executor = OxirsSparqlExecutor::new().expect("Failed to create executor");
744        executor.add_predicate_mapping("http://example.org/knows", "knows");
745
746        assert!(executor
747            .predicate_mappings
748            .contains_key("http://example.org/knows"));
749    }
750
751    #[test]
752    fn test_query_value_methods() {
753        let iri = QueryValue::Iri("http://example.org/Alice".to_string());
754        assert!(iri.is_iri());
755        assert!(!iri.is_literal());
756        assert_eq!(iri.as_str(), "http://example.org/Alice");
757
758        let literal = QueryValue::Literal {
759            value: "Hello".to_string(),
760            datatype: None,
761            language: Some("en".to_string()),
762        };
763        assert!(!literal.is_iri());
764        assert!(literal.is_literal());
765    }
766
767    #[test]
768    fn test_execute_to_tlexpr() {
769        let mut executor = OxirsSparqlExecutor::new().expect("Failed to create executor");
770        executor.add_predicate_mapping("http://example.org/knows", "knows");
771        executor
772            .load_turtle(
773                r#"
774            @prefix ex: <http://example.org/> .
775            ex:Alice ex:knows ex:Bob .
776        "#,
777            )
778            .expect("Load failed");
779
780        let query = "SELECT ?s ?o WHERE { ?s <http://example.org/knows> ?o }";
781        let result = executor.execute_to_tlexpr(query);
782
783        assert!(result.is_ok());
784    }
785
786    #[test]
787    fn test_load_ntriples() {
788        let mut executor = OxirsSparqlExecutor::new().expect("Failed to create executor");
789        let ntriples = r#"
790            <http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob> .
791        "#;
792
793        let result = executor.load_ntriples(ntriples);
794        assert!(result.is_ok());
795        assert_eq!(executor.num_triples(), 1);
796    }
797}