oxiland 0.13.0

Embedded RDF datasets, SPARQL, persistence, and streaming I/O for Rust
Documentation
# 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

```rust
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 with `serialize_graph_results_to_writer` and an
`io::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 triggers `cancel()` 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::io::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 BY` when 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.

See [Streams and iterators](streams.md) and
[Rust production operations](rust-production.md).