The SPARQL query path that used to be a demo parser is now the real oxirs-arq engine, end to end — and the “TDB2” store behind it finally survives a restart.
Today we released OxiRS 0.4.0 — the Rust-native Semantic Web / SPARQL 1.2 / GraphQL platform’s biggest correctness release yet, consolidating the previously-unpublished 0.3.3 production-hardening pass (a 272-finding audit, 288 fixes, 455 regression tests) with the 0.3.4 deployment fixes surfaced by the public, query-only sparql.wik.jp rollout. Neither 0.3.3 nor 0.3.4 ever reached crates.io — both ship here, together, as 0.4.0.
No JVM. No fabricated demo responses standing in for real query execution. No RDF store that quietly forgets your data was ever durable in the first place. OxiRS stays the Rust-first replacement for Apache Jena + Fuseki — one dataset speaking both SPARQL 1.2 and GraphQL, compiled to a single static binary or WASM module, with a dependency tree you can actually audit. 0.4.0 is the release where the query engine and the storage engine both stop cutting corners.
Why OxiRS 0.4.0 is a game changer
Most “production-ready” RDF stores accumulate a specific kind of technical debt: a query path that started as a demo and never got replaced, and a storage layer that “works” until the process actually restarts. That debt is invisible in a benchmark and catastrophic in production.
OxiRS 0.4.0 pays it off:
oxirs-tdbis now a real durable on-disk backend. A superblock (v2, with quad roots), fsync-backed writes, a free-page allocator, and GSPO/GPOS/GOSP quad + named-graph indexes replace the previous non-persisting placeholder — reopen round-trips are verified against a 10k-quad multigraph.- Every mutating write goes through a real WAL. Begin/Update/Commit entries are logged through an fsync-ordered write-ahead log, recovery replays committed operations on open, and a crash test proves committed-but-unsynced writes survive a reopen.
- The SPARQL query path is unified for real.
SELECT/ASK/CONSTRUCT/DESCRIBEall execute through a single parse-once dispatch into the actual oxirs-arq engine — no more substring-based query-type routing, no more silent empty200 OKon a parse or execution failure. GRAPH,FROM/FROM NAMED, andSERVICEfederation all work end to end — in both the serial and parallel executors — and native aggregates (COUNT/SUM/AVG/GROUP_CONCAT, expressions inside aggregates,DISTINCT) plusHAVINGrun through the engine’s real grouping machinery.- The axum 0.8 migration is complete, workspace-wide — fuseki, cluster, embed, and chat all moved off the deprecated
:param/*wildcardpath syntax before it became a hard break. - Security got real crypto, not placeholders —
oxirs-didnow does real X25519 ECDH and real Ristretto Schnorr/Pedersen commitments (both were forgeable stand-ins before), andoxirs-chat’s SSO verifies OIDC ID tokens and SAML signatures, failing closed.
All of this lands at 45,199 tests passing with --all-features (44,398 on default features), zero compilation warnings across all 27 crates.
Technical Deep Dive: what’s new under the hood
1. Sorted bulk build meets a real WAL. insert_triples_bulk/insert_quads_bulk now intern and encode a whole batch up front (failing loudly on a bad batch before any mutation), construct each index (SPO/POS/OSP, GSPO/GPOS/GOSP) sorted, and issue one WAL batch plus one sync per batch — both the bulk loader and the fuseki TDB adapter route through it. Optional Unix direct I/O (O_DIRECT on Linux, F_NOCACHE on macOS) is available for callers who want to bypass the page cache.
2. arq_exec.rs is the new single front door. Instead of routing SELECT/ASK/CONSTRUCT/DESCRIBE through separate, drifting code paths, oxirs-fuseki’s new handlers/sparql/arq_exec.rs parses the query exactly once and dispatches into a zero-copy StoreRefDataset view over the real oxirs-arq engine. A graph_result module backs CONSTRUCT/DESCRIBE specifically — per-row blank-node-scoped template instantiation for CONSTRUCT, and a cycle-safe, blank-node-closure-following, now-symmetric Concise Bounded Description for DESCRIBE (both outgoing and incoming arcs).
3. Parser fixes that change real query results, not just edge cases. BIND is now scoped positionally per SPARQL §18.2.2 (a BIND followed by more graph patterns in the same group used to silently produce wrong bindings), a bare FILTER after a top-level UNION is properly group-scoped, and GROUP BY/ORDER BY expression lists — previously silently dropped by a tokenizer/parser mismatch on the trailing BY keyword — now populate correctly.
4. Security work that closes real gaps, not theoretical ones. oxirs-fuseki’s read_only enforcement now covers SPARQL Update, Graph Store Protocol, file upload, RDF Patch, admin dataset management, and the REST API v2 write endpoints — the last of which was a completely unguarded write bypass before. oxirs-cluster’s BFT consensus closes the loop end to end: real 2f+1 quorum, idempotent commit handling, and a real authenticated network service instead of a no-op broadcast stub.
Getting Started
The oxirs CLI stays intentionally off crates.io (publish = false, so it can optionally pull in the C-FFI quarantine adapters from 0.3.2 without exposing them on a published surface). Build it from source:
git clone https://github.com/cool-japan/oxirs.git
cd oxirs
cargo install --path tools/oxirs
Then put the new durable store to work:
# Bulk-load onto a real, durable, WAL-backed on-disk store
oxirs import mykg data.ttl --dataset-type tdb2
# Query it — now through the unified oxirs-arq engine end to end
oxirs query mykg "SELECT ?s ?p ?o WHERE { GRAPH ?g { ?s ?p ?o } } LIMIT 10"
# Serve it — SPARQL 1.2, GraphQL, and a Fuseki-style admin UI, all read/write against the same on-disk store
oxirs serve mykg/oxirs.toml --port 3030
The other 25 OxiRS library crates remain normally published — cargo add oxirs-core for the RDF/SPARQL engine on its own, or cargo add oxirs-arq if you just want the query layer:
[dependencies]
oxirs-core = "0.4.0"
oxirs-arq = "0.4.0"
What’s New in 0.4.0
oxirs-tdbreal durable storage — superblock v2, fsync-backed writes, free-page allocator, WAL-integrated durable writes with crash recovery, and sorted bulk build.- Unified SPARQL query path —
SELECT/ASK/CONSTRUCT/DESCRIBEall execute through a single parse-once dispatch into the real oxirs-arq engine;GRAPH/FROM/FROM NAMED/SERVICEall work for real; native aggregates plusHAVINGrun through the engine’s grouping machinery. - Parser correctness fixes — WHERE-less
ASK/SELECT *, positionally-scopedBIND, group-scopedFILTERafterUNION, populatedGROUP BY/ORDER BYlists, multi-tripleINSERT/DELETE DATAparsing. - axum 0.8 migration complete — fuseki, cluster, embed, and chat routes all moved to the
{param}/{*wildcard}path syntax. - Security hardening — real X25519/Ristretto DID crypto, OIDC/SAML SSO signature verification, real cluster RPC with BFT quorum, and
read_onlydataset enforcement extended to every write path including REST API v2 and admin endpoints. oxirsCLI additions —lint,merge,jena-parity,monitor,detect-format, andinspectsubcommands, transparent.gzI/O acrossimport/riot/rdfcat/tdbbackup, and an interactive REPL with schema-aware completion and new meta-commands.- Algorithmic fixes across the engine —
oxirs-rule’s forward chainer moved to semi-naive evaluation,oxirs-star’squery()now uses indexed lookup instead of a full scan, andoxirs-graphrag’s entity linker uses first-character blocking instead of scoring the whole knowledge base per mention.
Tips
- Migrating to
tdb2? Setdataset_type = "tdb2"explicitly. Unknown dataset types now fail loudly instead of silently falling back to in-memory — this is a deliberate honesty fix, not a regression, so audit your config if you relied on the old silent fallback. - Re-check any code that depended on the old demo query path’s leniency. An unknown function in a
FILTER/HAVINGnow raises a typed error and fails the whole query instead of silently shrinking your result set — if you were unknowingly relying on that behavior, you’ll see it now as a loud 400/500 instead of a quietly wrong 200. DESCRIBEresults just got bigger — on purpose. The new Concise Bounded Description is symmetric (outgoing and incoming arcs), so aDESCRIBEon an object-only resource now returns something instead of an empty graph. If you need the old outgoing-only behavior, filter client-side.- Enable
oxirs-gql’s raw SPARQL passthrough explicitly if you need it. The auto-generated schema’ssparql(query: String!): Stringfield — which bypasses GraphQL depth/complexity limits — is now opt-in viaenable_sparql_field(falseby default). - If you run multiple datasets with
read_only, name one"default". Startup diagnostics now warn (and escalate to error) when a multi-dataset config has aread_onlydataset that isn’t named"default", since that’s the literal key several name-keyed write guards resolve against. - Update your
servehealth checks to use--dry-runfirst. The newserve --dry-runflag validates your config and reports the bind address without opening a socket — useful for CI or a pre-flight check before a real deployment.
This is the foundation
OxiRS is part of the COOLJAPAN ecosystem of Pure-Rust infrastructure, and 0.4.0 is the release where both halves of “production-ready” — a query engine that runs the query you actually wrote, and a store that actually keeps your data — come together. It rides on SciRS2 (0.6.1) for graph analytics and numerics, routes compression through the OxiARC family (0.3.6), handles crypto and transport through OxiCrypto/OxiTLS (0.2.1), and leans on OxiSQL (0.3.3) for its SQLite-compatible GeoSPARQL backend. Alongside siblings like OxiZ (SMT) and OxiRAG, OxiRS gives you an end-to-end auditable, JVM-free, C-free path from bytes on disk to a reasoned SPARQL answer — and now, one that survives a restart.
Repository: https://github.com/cool-japan/oxirs
Star the repo if you want a knowledge graph where “the query engine” and “the storage engine” both mean what they say. The era of demo query parsers pretending to be production SPARQL engines is over. Pure Rust Semantic Web is here — fast, safe, durable, and sovereign.
— KitaSan at COOLJAPAN OÜ July 19, 2026