Rust SPARQL queries and updates¶
Oxiland provides configured Query and Update builders over its Model.
Queries return a result enum that distinguishes boolean, solution, and graph
results without materializing the complete response.
ASK and SELECT¶
use oxiland::terms::{Literal, Triple, named_node};
use oxiland::{Model, Query, QueryResults};
# fn main() -> oxiland::Result<()> {
let model = Model::new()?;
model.add(Triple::new(
named_node("https://example.com/alice")?,
named_node("https://schema.org/name")?,
Literal::new_simple_literal("Alice"),
))?;
let ask = Query::new("ASK { ?s ?p ?o }").execute(&model)?;
assert!(matches!(ask, QueryResults::Boolean(true)));
let rows = Query::new(
"SELECT ?name WHERE { <https://example.com/alice> <https://schema.org/name> ?name }",
)
.limit(100)?
.execute(&model)?;
if let QueryResults::Solutions(solutions) = rows {
for item in solutions {
let solution = item
.map_err(|error| oxiland::Error::SparqlEvaluation(error.to_string()))?;
if let Some(name) = solution.get("name") {
println!("{name}");
}
}
}
# Ok(())
# }
Unbound SELECT variables are absent from the solution and return None through
get. Evaluation errors may occur while advancing the lazy iterator.
CONSTRUCT and DESCRIBE¶
Graph-producing queries return QueryResults::Graph, a lazy stream of triples:
```rust,no_run use oxiland::{Model, Query, QueryResults};
fn run(model: &Model) -> oxiland::Result<()> { let results = Query::new( "CONSTRUCT { ?s https://example.com/seen ?o } WHERE { ?s ?p ?o }", ) .limit(1_000)? .execute(model)?;
if let QueryResults::Graph(triples) = results {
for item in triples {
println!("{}", item.map_err(|error|
oxiland::Error::SparqlEvaluation(error.to_string()))?);
}
}
Ok(())
}
``
Serialize graph results as RDF withserialize_graph_results_to_writerand anio::Serializer`. SPARQL Results JSON/XML/CSV/TSV formats apply only to ASK and
SELECT.
Query configuration¶
| Builder | Behavior |
|---|---|
base_iri |
Resolve relative IRIs while parsing the query |
prefix |
Add a parser prefix without modifying query text |
limit / offset |
Apply an algebra slice to SELECT/CONSTRUCT/DESCRIBE |
default_graph |
Select one or more graphs as the query default graph |
default_graph_as_union |
Use the union of named graphs as the default graph |
available_named_graphs |
Restrict graphs addressable through GRAPH |
cancellation_token |
Attach cooperative cancellation |
API-level limit and offset replace an in-query slice and reject ASK queries. |
|
An empty default_graph list selects an empty default dataset rather than the |
|
| store's normal default graph. | |
| ## Updates | |
| ```rust | |
| use oxiland::{Model, Update}; | |
| # fn main() -> oxiland::Result<()> { | |
| let model = Model::new()?; | |
| Update::new( | |
| "INSERT DATA { https://example.com/a https://example.com/p \"value\" }", | |
| ) | |
| .execute(&model)?; | |
| assert_eq!(model.len()?, 1); | |
| # Ok(()) | |
| # } | |
| ``` |
Persistent updates resynchronize durable state after successful execution. If durable sync fails, Oxiland restores the pre-update disk key set and rolls the in-memory model back to that snapshot.
Dataset configuration for updates requires operations whose SPARQL algebra
supports USING datasets. INSERT DATA, DELETE DATA, and similar forms return
Error::Unsupported when incompatible dataset builders are applied.
Cooperative cancellation¶
```rust,no_run use std::thread; use std::time::Duration; use oxiland::sparql::CancellationToken; use oxiland::{Model, Query};
fn run(model: &Model) -> oxiland::Result<()> { let token = CancellationToken::new(); let deadline = token.clone(); thread::spawn(move || { thread::sleep(Duration::from_secs(2)); deadline.cancel(); });
let _ = Query::new("SELECT * WHERE { ?s ?p ?o }")
.limit(10_000)?
.cancellation_token(token)
.execute(model)?;
Ok(())
}
``
Cancellation is cooperative. The future that triggerscancel()` defines the
wall-clock deadline; completion is not a hard real-time guarantee. Combine it
with input limits, result limits, and worker isolation for untrusted workloads.
Result serialization¶
```rust,no_run use oxiland::{Query, ResultsFormat, serialize_query_results_to_writer};
use oxiland::Model;¶
let model = Model::new()?;¶
let results = Query::new("SELECT ?s WHERE { ?s ?p ?o }")
.limit(100)?
.execute(&model)?;
serialize_query_results_to_writer(
results,
ResultsFormat::Json,
std::stdout().lock(),
)?;
Ok::<(), oxiland::Error>(())¶
```
ResultsFormat supports XML, JSON, CSV, and TSV. Prefer the writer API for
large result sets; the string helper buffers the serialized response.
Error handling¶
| Error | Meaning |
|---|---|
Error::InvalidRdf |
Invalid configured base or prefix IRI |
Error::SparqlParse |
Query or update text could not be parsed |
Error::SparqlEvaluation |
Execution or result iteration failed |
Error::Storage |
Model read, update, rollback, or durable sync failed |
Error::Unsupported |
Builder/query combination is outside the public contract |
Do not match diagnostic strings. Use error variants for control flow and retain the full message for logs or user diagnostics.
Production guidance¶
- Use
ORDER BYwhen output order is part of the application contract. - Apply explicit result limits to caller-facing queries.
- Do not run unrestricted SPARQL from untrusted clients in a shared worker.
- Stream results to their destination and stop early when possible.
- Avoid logging raw query text and bound literals unless the data policy allows it.
- Instrument parse, first-result, total-result, cancellation, and error timing.
Advanced Oxigraph primitives are re-exported under oxiland::sparql, but they
are an escape hatch rather than the verified Oxiland compatibility surface.