SPARQL 1.1 conformance
Claim boundary — read this before quoting any number on this page. Quipu does not pass every approved W3C SPARQL 1.1 Query, Update, Protocol and Results test at rdf-tests
369a90d: query syntax 86/86, query evaluation 167/168, update 93/93, protocol 34/34, result format 10/10, SPARQL 1.0 query 199/242. Exceptions, each named below: federated query (SERVICE) passes 6/7, with 1 refused by policy (variable endpoints); entailment regimes are scored separately (35/70 passed, 0 failing, 35 declared non-goals); SHACL-SPARQL, OWL, RIF and D entailment are declared non-goals. What these counts are. Working Group–approved tests only. The query-evaluation manifests list 225 tests, and the 168 approved ones are scored; the 57 Proposed or unclassified are not run. The update-syntax suites are not run yet. The SPARQL 1.0 query tests are scored separately. The SPARQL 1.1 manifests hold what 1.1 added; the 1.0 tests (rdf-testssparql/sparql10) also bear on SPARQL 1.1 Query conformance. Quipu passes 199/242 of the approved ones, with 33 failing, 0 errors and 10 not comparable; see SPARQL 1.0 query tests. This score is fitted to this suite. Quipu’s failures here were found by running this suite and fixed against it, case by case, so a perfect score is partly a record of that work rather than an independent sample. Other stores measured with the same harness were not tuned to it. Every class below is scored separately and is never combined into a single compliance percentage, because a blended figure would hide exactly the classes that are not implemented at all.
These results come from the W3C RDF Tests suite at a pinned revision, run against throwaway stores by a checked-in runner. You can re-derive every number on this page yourself — the commands are below.
For what Quipu does with a graph once it is correct — handing it to another
store, and composing another store’s without trusting it — see
Sharing & Federation. That page states its own claim
boundary for SERVICE, including the configured-endpoint policy deviation scored below.
What was measured
| Field | Value |
|---|---|
| W3C RDF Tests revision | 369a90d1a60c021b746df2e411da0ff36258a758 |
| Quipu revision (evaluation) | 8fb57af0472af4bb8038247ccb54f9a4b2afe689 |
| Quipu revision (syntax) | 8fb57af0472af4bb8038247ccb54f9a4b2afe689 |
| Quipu version | quipu 0.15.0 |
| Store isolation | one temporary SQLite store per executable test |
| Test selection | Working Group–approved tests only |
Results by class
unsupported means the harness cannot execute the test at all — the capability
is missing, not merely wrong. Those cases stay in the denominator and each one
carries a named reason further down this page.
| Class | Passed | Failed | Error | Unsupported | Approved cases |
|---|---|---|---|---|---|
| query syntax | 86 | 0 | 0 | 0 | 86 |
| query evaluation | 167 | 1 | 0 | 0 | 168 |
federated query (SERVICE) | 6 | 0 | 0 | 1 | 7 |
| result format | 10 | 0 | 0 | 0 | 10 |
| protocol | 34 | 0 | 0 | 0 | 34 |
| update | 93 | 0 | 0 | 0 | 93 |
| entailment | 35 | 0 | 0 | 35 | 70 |
| all classes | 431 | 1 | 0 | 36 | 468 |
The final row is an arithmetic total, not a score. It is here so the class rows can be checked against the ledgers, not so it can be quoted as a percentage.
Other stores, same harness
The same discovery, test selection and result comparison, run against other
stores at the same rdf-tests revision (369a90d1). Scores use RDF
term equality, the rule quipu is held to. “Same value” counts failures whose answer
had the right values in a different lexical form; they stay failures and are
shown separately, so a design choice is not presented as a wrong answer.
| System | Version | Query evaluation | Of those failures, same value | Update |
|---|---|---|---|---|
| quipu | quipu 0.15.0 | 167/168 | — | 93/93 |
| RDF4J | 6.1.0 | 162/168 | 5 | 87/93 |
| Oxigraph | 0.5.11 | 159/168 | 8 | 93/93 |
| Jena Fuseki | 6.2.0 | 155/168 | 12 | 93/93 |
| rdflib | 7.6.0 | 154/168 | 9 | 69/93 |
The quipu row is this page’s own ledger. Quipu’s runner compares exact labels and has no same-value tag, so that cell is empty rather than zero.
Disclosure. Quipu parses SPARQL with spargebra and models RDF with oxrdf, both
from the Oxigraph project. Where the two agree, part of that agreement is shared code.
Quipu’s score is fitted to this suite. Its failures were found by running this suite and fixed against it, case by case. The other stores were not tuned to this harness.
Pinned versions, the fairness rules, every competitor deviation checked by hand, and
the per-case ledgers are in
benchmark/competitors.
RDF syntax
The W3C RDF 1.1 and RDF 1.2 syntax suites at the same rdf-tests revision
(369a90d1). Every manifest case is counted, including cases
the manifests have not marked approved.
| Format | RDF 1.1 | RDF 1.2 |
|---|---|---|
| Turtle | 313/313 | not supported (0/106) |
| N-Triples | 70/70 | not supported (0/70) |
| N-Quads | 0/87 (87 unsupported) | not supported (0/68) |
| TriG | 0/357 (357 unsupported) | not supported (0/61) |
RDF 1.2 is measured and not supported. Quipu is built without RDF 1.2, so it cannot parse a triple term. The RDF 1.2 cases are enumerated from the pinned manifests and not run: a loader that rejects all RDF 1.2 input would “pass” every negative-syntax case, and those passes would read as partial support. No RDF 1.2 case is scored as a pass until the support exists.
Known deviation: literal lexical identity
The current loader canonicalizes integer and double literals: for example,
"01"^^xsd:integer is stored and exported as "1"^^xsd:integer.
It also converts an ill-typed boolean such as "z"^^xsd:boolean to false,
and rejects some ill-typed numeric literals during loading. These are known
deviations, not the intended storage contract; affected cases remain failures.
The chosen contract is to preserve lexical form and datatype as RDF term
identity, including ill-typed literals, and derive numeric values separately.
Distinct terms such as "01" and "1" with the same integer datatype must
coexist even though their numeric values compare equal. This follows
RDF 1.1 literal term equality.
An expression’s effective boolean value must not replace the stored term.
The preservation fix is not implemented yet. Existing data cannot recover discarded spellings; a future compatibility plan must preserve legacy term identity, exact retraction and history without inventing missing lexical forms.
Ledgers: rdf11-syntax.json
and rdf12-syntax.json.
SPARQL 1.0 query tests
The approved W3C SPARQL 1.0 query-evaluation tests (sparql/sparql10) at the same
rdf-tests revision (369a90d1), run by the same runner: 199/242 pass.
Most SPARQL 1.0 answers are RDF result-set graphs (rs:ResultSet). They are read with
rdflib, pinned, with literal normalisation off, so "01"^^xsd:integer stays 01.
Quipu never reads its own expected answers. Where the answer numbers its solutions
(rs:index), order is compared, not just the multiset. The unsupported cases have
RDF/XML answers, which the runner does not read.
| Family | Passed | Failed | Error | Unsupported | Cases |
|---|---|---|---|---|---|
expr-builtin | 12 | 12 | 0 | 0 | 24 |
open-world | 5 | 12 | 0 | 0 | 17 |
sort | 3 | 0 | 0 | 10 | 13 |
distinct | 6 | 5 | 0 | 0 | 11 |
expr-equals | 10 | 2 | 0 | 0 | 12 |
reduced | 1 | 1 | 0 | 0 | 2 |
regex | 3 | 1 | 0 | 0 | 4 |
algebra | 14 | 0 | 0 | 0 | 14 |
ask | 4 | 0 | 0 | 0 | 4 |
basic | 27 | 0 | 0 | 0 | 27 |
bnode-coreference | 1 | 0 | 0 | 0 | 1 |
boolean-effective-value | 7 | 0 | 0 | 0 | 7 |
bound | 1 | 0 | 0 | 0 | 1 |
cast | 7 | 0 | 0 | 0 | 7 |
construct | 5 | 0 | 0 | 0 | 5 |
dataset | 12 | 0 | 0 | 0 | 12 |
expr-ops | 7 | 0 | 0 | 0 | 7 |
graph | 11 | 0 | 0 | 0 | 11 |
i18n | 5 | 0 | 0 | 0 | 5 |
optional | 7 | 0 | 0 | 0 | 7 |
optional-filter | 4 | 0 | 0 | 0 | 4 |
solution-seq | 13 | 0 | 0 | 0 | 13 |
triple-match | 4 | 0 | 0 | 0 | 4 |
type-promotion | 30 | 0 | 0 | 0 | 30 |
Some failures are the lexical-form design choice described above (a number is stored
by value, so "01" reads back as 1); others are engine defects being fixed.
Every case, with its diagnostic, is in
sparql10-evaluation.json.
Named SPARQL 1.0 deviations
These are the current ledger’s non-passing cases, including loader errors and runner limitations. Listing a case does not claim its engine defect is fixed.
| W3C test | Status | Diagnostic |
|---|---|---|
:open-eq-03 | failed | actual result differs from expected multiset |
:open-eq-04 | failed | actual result differs from expected multiset |
:open-eq-06 | failed | actual result differs from expected multiset |
:open-eq-07 | failed | actual result differs from expected multiset |
:open-eq-08 | failed | actual result differs from expected multiset |
:open-eq-10 | failed | actual result differs from expected multiset |
:open-eq-11 | failed | actual result differs from expected multiset |
:open-eq-12 | failed | actual result differs from expected multiset |
:date-2 | failed | actual result differs from expected multiset |
:date-3 | failed | actual result differs from expected multiset |
:open-cmp-01 | failed | actual result differs from expected multiset |
:open-cmp-02 | failed | actual result differs from expected multiset |
:dawg-str-2 | failed | actual result differs from expected multiset |
:dawg-isBlank-1 | failed | actual result differs from expected multiset |
:dawg-datatype-2 | failed | actual result differs from expected multiset |
:dawg-lang-1 | failed | actual result differs from expected multiset |
:dawg-lang-2 | failed | actual result differs from expected multiset |
:dawg-isURI-1 | failed | actual result differs from expected multiset |
:dawg-isIRI-1 | failed | actual result differs from expected multiset |
:dawg-langMatches-4 | failed | actual result differs from expected multiset |
:lang-case-insensitive-eq | failed | actual result differs from expected multiset |
:sameTerm-simple | failed | actual result differs from expected multiset |
:sameTerm-eq | failed | actual result differs from expected multiset |
:sameTerm-not-eq | failed | actual result differs from expected multiset |
:eq-2-1 | failed | actual result differs from expected multiset |
:eq-2-2 | failed | actual result differs from expected multiset |
:dawg-regex-003 | failed | actual result differs from expected multiset |
:no-distinct-1 | failed | actual result differs from expected multiset |
:distinct-1 | failed | actual result differs from expected multiset |
:no-distinct-2 | failed | actual result differs from expected multiset |
:no-distinct-9 | failed | actual result differs from expected multiset |
:distinct-9 | failed | actual result differs from expected multiset |
:dawg-sort-1 | unsupported | |
:dawg-sort-2 | unsupported | |
:dawg-sort-3 | unsupported | |
:dawg-sort-4 | unsupported | |
:dawg-sort-5 | unsupported | |
:dawg-sort-6 | unsupported | |
:dawg-sort-7 | unsupported | |
:dawg-sort-8 | unsupported | |
:dawg-sort-9 | unsupported | |
:dawg-sort-10 | unsupported | |
:reduced-2 | failed | actual result differs from expected multiset |
SPARQL 1.2 query tests
The W3C SPARQL 1.2 query tests (sparql/sparql12) at the same rdf-tests revision
(369a90d1). No SPARQL 1.2 case is Working Group–approved yet; every one is
counted anyway. 3 of 269 pass, and 263 are not run.
Most of SPARQL 1.2 needs grammar or terms Quipu does not have: triple terms, the VERSION
declaration, base direction, new codepoint escapes. Those cases are listed and never run,
because a parser that rejects all SPARQL 1.2 input would “pass” every negative case. The
few cases that need nothing new are run by the same runner as everything above.
| Part | Passed | Failed | Not run | Cases | Why not run |
|---|---|---|---|---|---|
codepoint-escapes | 0 | 0 | 14 | 14 | SPARQL 1.2 codepoint-escape grammar is not implemented; its negative cases would pass by rejection |
eval-triple-terms | 0 | 0 | 41 | 41 | needs RDF 1.2 triple terms: Quipu is built without rdf-12 (aegis-6l8hkk) |
expression | 0 | 1 | 4 | 5 | needs RDF 1.2 triple terms: Quipu is built without rdf-12 (aegis-6l8hkk) |
grouping | 0 | 2 | 0 | 2 | run |
lang-basedir | 0 | 0 | 11 | 11 | RDF 1.2 base direction (rdf:dirLangString) is not implemented |
rdf11 | 3 | 0 | 0 | 3 | run |
syntax | 0 | 0 | 6 | 6 | not wired: the syntax runner scores the 1.1 syntax manifest only |
syntax-triple-terms-negative | 0 | 0 | 65 | 65 | needs RDF 1.2 triple terms: Quipu is built without rdf-12 (aegis-6l8hkk) |
syntax-triple-terms-positive | 0 | 0 | 113 | 113 | needs RDF 1.2 triple terms: Quipu is built without rdf-12 (aegis-6l8hkk) |
version | 0 | 0 | 9 | 9 | the SPARQL 1.2 VERSION declaration is not implemented |
Ledger: sparql12.json.
Query evaluation, by feature family
The family is the pinned suite’s own directory for each manifest, so this grouping is re-derivable by anyone holding the same suite revision. It is the map of where the work is: the largest failing families are where conformance moves the most per fix.
| Family | Passed | Failed | Error | Unsupported | Cases |
|---|---|---|---|---|---|
functions | 57 | 0 | 0 | 0 | 57 |
property-path | 24 | 0 | 0 | 0 | 24 |
aggregates | 21 | 1 | 0 | 0 | 22 |
subquery | 14 | 0 | 0 | 0 | 14 |
negation | 11 | 0 | 0 | 0 | 11 |
bind | 10 | 0 | 0 | 0 | 10 |
bindings | 10 | 0 | 0 | 0 | 10 |
project-expression | 7 | 0 | 0 | 0 | 7 |
exists | 5 | 0 | 0 | 0 | 5 |
construct | 4 | 0 | 0 | 0 | 4 |
grouping | 4 | 0 | 0 | 0 | 4 |
The not-yet-passing tests, named
Every test that does not pass is listed here with its W3C identifier, so a claim of progress can be checked against a specific case rather than a count.
aggregates — 1 of 22 not passing
| Test | Name | Status | Reason |
|---|---|---|---|
:agg-min-02 | MIN with GROUP BY | failed | actual result differs from expected multiset |
Federated query (SERVICE)
Quipu passes 6/7 approved W3C Basic Federated Query cases.
SERVICE is a query-planned subquery path using the same declarations and labels as RemoteProvider; it is separate from GraphProvider whole-query fanout and is not open federation.
The variable-endpoint case is a deliberate policy deviation because query data cannot widen the configured remote allowlist.
| Test | Name | Status | Reason |
|---|---|---|---|
:service1 | SERVICE test 1 | passed | |
:service2 | SERVICE test 2 | passed | |
:service3 | SERVICE test 3 | passed | |
:service4a | SERVICE test 4a with VALUES clause | passed | |
:service5 | SERVICE test 5 | unsupported | variable SERVICE endpoints are deliberately refused; endpoints must be operator-configured |
:service6 | SERVICE test 6 | passed | |
:service7 | SERVICE test 7 | passed |
Why a class is unsupported
Grouped by the reason the runner recorded.
| Why it is unsupported | Cases | Classes |
|---|---|---|
| OWL-Direct entailment is a deliberate non-goal pending a design (aegis-b5moll): it needs a real DL reasoner, and this store’s OWL layer is a write gate with no axioms – no amount of RDFS closure reaches it | 18 | entailment |
| OWL-RDF-Based entailment is a deliberate non-goal pending the same design (aegis-b5moll): it needs an RL rule set or an external reasoner, not an extension of the RDFS closure | 11 | entailment |
| RIF entailment is a deliberate non-goal: RIF is a rule-interchange format, not a semantics asked of this store | 4 | entailment |
| D entailment (datatype entailment) is a deliberate non-goal: no consumer asks for datatype entailment beyond simple and RDFS | 2 | entailment |
| variable SERVICE endpoints are deliberately refused; endpoints must be operator-configured | 1 | federated query (SERVICE) |
Corrections
Kept on the page so a changed number never changes silently.
- 2026-09-24: update was 37 of 93. Until this date the page reported update
37/37. The runner discovered only 37 of the 93 approved update tests: the
delete,delete-data,delete-insert,delete-where,clearanddropmanifests declare their tests with Turtle’sarather thanrdf:type, and the parser matched only the latter. On the full 93, quipu passes 93/93. The runner now pins the approved count per class at the pinned suite revision and refuses a run that discovers a different number.
Re-derive these numbers
git clone https://github.com/w3c/rdf-tests /tmp/rdf-tests
git -C /tmp/rdf-tests checkout 369a90d1a60c021b746df2e411da0ff36258a758
cargo build --release --bin quipu --bin quipu-server --features shacl,onnx,server
QUIPU_BIN="$(cargo metadata --no-deps --format-version 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["target_directory"])')/release/quipu"
SUITE="/tmp/rdf-tests/sparql/sparql11"
python3 benchmark/public/sparql11_syntax.py --suite "$SUITE/syntax-query" \
--quipu "$QUIPU_BIN" --output /tmp/sparql11-syntax.json
python3 benchmark/public/sparql11_evaluation.py --suite "$SUITE" \
--quipu "$QUIPU_BIN" --output /tmp/sparql11-evaluation.json
python3 benchmark/public/sparql11_federated.py --suite "$SUITE" \
--quipu "$QUIPU_BIN" --output /tmp/sparql11-federated-query.json
A nonzero exit from an incomplete runner is expected while any test fails: it writes the complete ledger first, then reports that not everything passed. Use the regression gate below to tell worse than committed apart from not yet perfect.
Per-class reproduction commands are embedded in the ledger itself, under
reproduce.per_class.
The regression gate
python3 benchmark/public/check_regression.py \
--baseline benchmark/public/results/sparql11-evaluation.json \
--candidate /tmp/sparql11-evaluation.json
It exits nonzero when a class’s pass count drops or when a test that passed in the committed ledger stops passing, and it names the tests. Improvements exit zero and print the tests that newly pass — a prompt to refresh the baseline. This is what runs in CI on every release, so a published number cannot silently get worse.
Full ledgers
benchmark/public/results/sparql11-syntax.jsonbenchmark/public/results/sparql11-evaluation.jsonbenchmark/public/results/sparql11-federated-query.json
Each row records its class, test identifier, manifest, query and result paths, status, and diagnostic or unsupported reason.
W3C SHACL conformance
Quipu passes 98/98 manifest-reachable SHACL Core cases. SHACL-SPARQL remains a deliberate non-goal: 0/22 pass and 22 are unsupported. Quipu does not advertise SHACL-SPARQL support; the upstream SPARQL validator is incomplete, and support requires the full 22-case manifest gate rather than a partial claim. This score uses the context-free native validator on whole graphs; the write path is measured below.
Pinned W3C Data Shapes revision: 9c863967bceaef1a87c24e4dd761eda763823120.
The pinned manifest exposes 120 approved cases (98 Core + 22 SHACL-SPARQL).
nodeValidator-001.ttl exists in the checkout but is not manifest-reachable and is not scored.
| SHACL class | Passed | Failed | Error | Unsupported | Cases |
|---|---|---|---|---|---|
core-complex-misc | 7 | 0 | 0 | 0 | 7 |
core-node | 32 | 0 | 0 | 0 | 32 |
core-property | 38 | 0 | 0 | 0 | 38 |
core-path | 13 | 0 | 0 | 0 | 13 |
core-targets | 7 | 0 | 0 | 0 | 7 |
core-validation-reports | 1 | 0 | 0 | 0 | 1 |
shacl-sparql | 0 | 0 | 0 | 22 | 22 |
Write-path conformance
The same 98 Core cases are also written through the real /knot handler, whole and split across writes (subject halves, one write per subject, one write per triple, each in both orders). 90/98 agree with the W3C verdict under every split; 7 diverge under at least one; 1 could not be judged under at least one split (the write gate answered cannot verify instead of a SHACL verdict).
A divergence is a case whose final graph violates (or conforms) while the write gate accepted (or refused) every write. These are known defects, listed in the ledger with the splits that expose them; the CI gate fails on any new divergence and on any entry that no longer diverges, so this number can only fall.
| Constraint component | Cases using it | Agree | Diverge | Unjudged |
|---|---|---|---|---|
sh:and | 4 | 3 | 1 | 0 |
sh:class | 19 | 13 | 6 | 0 |
sh:closed | 4 | 3 | 1 | 0 |
sh:datatype | 28 | 27 | 1 | 0 |
sh:disjoint | 3 | 2 | 1 | 0 |
sh:equals | 3 | 2 | 1 | 0 |
sh:hasValue | 5 | 4 | 1 | 0 |
sh:in | 5 | 3 | 2 | 0 |
sh:languageIn | 3 | 2 | 1 | 0 |
sh:lessThan | 3 | 2 | 1 | 0 |
sh:lessThanOrEquals | 2 | 1 | 1 | 0 |
sh:maxCount | 13 | 11 | 1 | 1 |
sh:maxExclusive | 3 | 2 | 1 | 0 |
sh:maxInclusive | 3 | 2 | 1 | 0 |
sh:maxLength | 3 | 2 | 1 | 0 |
sh:minCount | 20 | 19 | 1 | 0 |
sh:minExclusive | 4 | 3 | 1 | 0 |
sh:minInclusive | 4 | 3 | 1 | 0 |
sh:minLength | 3 | 2 | 1 | 0 |
sh:node | 5 | 4 | 1 | 0 |
sh:nodeKind | 6 | 4 | 1 | 1 |
sh:not | 4 | 3 | 1 | 0 |
sh:or | 5 | 4 | 1 | 0 |
sh:pattern | 6 | 5 | 1 | 0 |
sh:property | 55 | 51 | 3 | 1 |
sh:qualifiedValueShape | 4 | 3 | 1 | 0 |
sh:uniqueLang | 3 | 2 | 1 | 0 |
sh:xone | 3 | 2 | 1 | 0 |
Ledger: shacl-write-path.json.
Entailment-regime commitments
2 of 6 regimes are goals (RDF, RDFS): 35/35 of their cases pass. The remaining 4 are deliberate non-goals.
Ledger re-derived 2026-10-21T15:07:06Z by CI run, from quipu 8fb57af0472a.
Local RDFS and OWL extensions beyond a goal regime are not standards-regime claims.
Do not read the goal-regime fraction as “nearly done”. The two numbers have different characters. Most RDF-regime cases are
bind*tests answerable under simple entailment, so they pass without any additional inference — a high RDF score is not evidence of an entailment engine. The RDFS score DOES reflect one: an RDFS closure (rdfs2/3/5/7/9/11) is materialised into the graph’s companion inferred graph and composed into the default graph when the regime is in force, which is what a query likeSELECT ?x WHERE { ex:a ?x ex:c }needs — its predicate is a variable, so the entailed triple has to EXIST and cannot be produced by rewriting the pattern. What remains failing is not more of the same closure: it is container and axiomatic shapes beyond those six rules, and OWL-flavoured cases filed under RDFS.
| Regime | Cases | Passed | Commitment |
|---|---|---|---|
| D | 2 | 0 | deliberate non-goal |
| OWL-Direct | 18 | 0 | deliberate non-goal |
| OWL-RDF-Based | 11 | 0 | deliberate non-goal |
| RDF | 16 | 16 | goal |
| RDFS | 19 | 19 | goal |
| RIF | 4 | 0 | deliberate non-goal |
Machine ledgers: shacl-core.json and sparql11-entailment.json.