Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SPARQL Engine

Implementation status (2026-07-23, kelly): ✅ Implemented (code is AHEAD of this doc). SELECT/ASK/CONSTRUCT/DESCRIBE, BGP/JOIN/UNION/OPTIONAL/FILTER/BIND, DISTINCT/ORDER/LIMIT/GROUP/HAVING, aggregates (COUNT/SUM/AVG/MIN/MAX/SAMPLE/ GROUP_CONCAT), FILTER builtins, RDFS subclass inference, and bitemporal current-state filtering are all live (src/sparql/). Note the doc UNDER-claims: it marks “Property paths: Planned,” but they are fully implemented in src/sparql/property_path.rs (Reverse/Sequence/Alternative/ZeroOrMore/OneOrMore) — the table row below is now corrected.

Quipu includes a custom SPARQL evaluator that compiles queries directly against the SQLite fact log. No separate triple store or graph database is needed.

How It Works

  1. Parse: SPARQL string -> AST via spargebra
  2. Evaluate: Walk the AST, executing each graph pattern against SQLite
  3. Return: Variable bindings as HashMap<String, Value> rows
#![allow(unused)]
fn main() {
use quipu::store::Store;
use quipu::sparql;

let result = sparql::query(&store,
    "SELECT ?name WHERE { ?s <http://example.org/name> ?name }"
).unwrap();

for row in result.rows() {
    println!("{:?}", row.get("name"));
}
}

Query Forms

FormDescriptionExample
SELECTReturn variable bindingsSELECT ?s ?p ?o WHERE { ... }
ASKBoolean existence checkASK { ?s a ex:Person }
CONSTRUCTBuild new triplesCONSTRUCT { ?s a ex:Result } WHERE { ... }
DESCRIBEReturn all facts about an entityDESCRIBE <http://example.org/alice>

Supported Features

Graph Patterns

PatternStatusExample
Basic Graph Pattern (BGP)Supported?s ?p ?o
JOINSupportedMultiple BGP patterns
UNIONSupported{ ... } UNION { ... }
FILTERSupportedFILTER(?age > 30)
OPTIONAL (LeftJoin)SupportedOPTIONAL { ?s ex:email ?e }
PROJECTSupportedSELECT ?name
DISTINCT / REDUCEDSupportedSELECT DISTINCT ?type
LIMIT / OFFSETSupportedLIMIT 10 OFFSET 5
ORDER BYSupportedORDER BY DESC(?age)
GROUP BYSupportedGROUP BY ?type
HAVINGSupportedHAVING(COUNT(?s) > 2)
EXTEND (BIND)SupportedComputed variables
VALUESSupportedVALUES ?x { "a" "b" }, multi-column, UNDEF (src/sparql/values.rs)
Property pathsSupportedReverse ^, Sequence /, Alternative |, *, + (src/sparql/property_path.rs)

Aggregates

FunctionExample
COUNTSELECT (COUNT(?s) AS ?n) WHERE { ... }
SUMSELECT (SUM(?age) AS ?total) ...
AVGSELECT (AVG(?age) AS ?mean) ...
MIN / MAXSELECT (MIN(?age) AS ?youngest) ...

An ungrouped, non-distinct COUNT(*) or COUNT of a bound variable over one triple pattern can use a scalar SQL count. This avoids retaining a binding for every matching triple. Composed stores canonicalize subject, predicate and reference-object aliases before counting, so duplicate assertions across attached dictionaries still count once. The query’s graph and temporal scope remain in force.

During the scalar statement, file mappings are disabled and an oversized main page-cache budget is reduced to 8 MiB. Each provider’s mapping policy and the main cache policy are restored afterward, including on errors. Smaller cache budgets are preserved. Query deadlines still apply; unsupported aggregate forms and joins retain their existing complexity limits.

FILTER Expressions

ExpressionExample
Equality?name = "Alice"
Comparison?age > 30, ?age <= 50
AND / OR / NOT?age > 20 && ?age < 40
BOUNDBOUND(?name)
Regexregex(?name, "Ali")
CONTAINSCONTAINS(STR(?s), "traefik")
LCASE / STRLCASE(STR(?name))
isIRIFILTER(isIRI(?o))
IN / NOT INFILTER(?name IN ("Alice", "Bob"))

RDFS Inference

Quipu supports RDFS subclass inference for rdf:type queries. If you define:

ex:Engineer rdfs:subClassOf ex:Person .
ex:alice a ex:Engineer .

Then SELECT ?s WHERE { ?s a ex:Person } will return ex:alice through transitive subclass reasoning.

Temporal Awareness

The SPARQL engine automatically filters to current state (op = 1 AND valid_to IS NULL). Time-travel is supported via the unravel command:

# See the world as it was at a specific transaction
quipu unravel --tx 5 --db my.db

# See the world as it was at a specific time
quipu unravel --valid-at "2026-03-15T00:00:00Z" --db my.db

Via the MCP tool:

{
  "tool": "quipu_query",
  "input": {
    "query": "SELECT ?s ?p ?o WHERE { ?s ?p ?o }",
    "valid_at": "2026-03-15T00:00:00Z"
  }
}