Runs SPARQL queries via a real, native oxigraph engine (a Rustler NIF over
~/ggen/crates/ggen-graph-wasm's OxigraphEngine, native/ggen_graph_nif)
instead of the pure-Elixir sparql hex package used by
GgenIgniter.Query.run/2.
Same [map()] row-list contract as GgenIgniter.Query.run/2 and
GgenIgniter.Query.Qlever.run/2 -- a drop-in alternative engine, not a
replacement API.
Real motivation, not just "one more engine option": GgenIgniter.Query.run/2
(the sparql 0.3.12 hex package) has a confirmed, already-pinned bug --
it raises Protocol.UndefinedError on real gate queries shaped like
FILTER NOT EXISTS { ... } with an unprojected variable inside a UNION,
combined with BIND (see test/ash_r2rml_gate_integration_test.exs).
Oxigraph is a full, independent SPARQL 1.1 engine -- routing that same
query shape through this module is a real, testable way to check whether
it resolves that blocker (see test/ggen_igniter_oxigraph_engine_test.exs).
A third, real divergence from GgenIgniter.Query.run/2 (confirmed)
GgenIgniter.Query.run/2 has no error-handling branch: it pattern-matches
%SPARQL.Query.Result{results: rows} = SPARQL.execute_query(graph, query)
unconditionally. This module, by contrast, explicitly branches on
{:ok, rows} | {:error, reason} (see run/2 below) and always raises a
clear, named RuntimeError. Confirmed real consequences, proven in
test/ggen_igniter_ask_construct_engine_divergence_test.exs:
- ASK:
GgenIgniter.Query.run/2does NOT return a boolean -- it silently returns the ASK query's WHERE-clause bindings as ordinary SELECT-shaped rows (a real wrong-answer divergence, not a crash). This module raises a clearRuntimeErrornaming why (onlyQueryResults::Solutionsis handled; ASK'sQueryResults::BooleanhitsOxigraphEngineError::NotSelectQuery). - CONSTRUCT:
GgenIgniter.Query.run/2raises a raw, unwrappedMatchError(the real%RDF.Graph{}on the wrong side of the match). This module raises the same clearRuntimeErroras ASK. - A malformed query:
GgenIgniter.Query.run/2again raises a rawMatchErroragainstSPARQL.execute_query/2's real{:error, reason}tuple. This module raises a clearRuntimeErrornaming the real reason. - Bare
FILTER NOT EXISTS(noUNION, noBIND):GgenIgniter.Query.run/2still raisesProtocol.UndefinedErroreven in this minimal shape -- broader than the UNION+BIND-specific crashGgenIgniter.Query.run/2's own moduledoc documents. This module evaluates it correctly.
This module's own write-scope does not extend to fixing
GgenIgniter.Query.run/2 (lib/ggen_igniter/query.ex is a sibling file,
not under lib/ggen_igniter/query/); see
.ggen_igniter_factory/ledger-agent5.jsonl for the logged follow-up.
Term normalization: PLAIN values by default, raw as an explicit opt-in
Real, confirmed, already-fixed bug: this engine used to return every
binding as oxigraph's raw N-Triples-style Term::to_string() serialization
-- IRIs angle-bracket-wrapped (<https://example.org/...>), literals
quoted and datatype/language-tagged ("42"^^<http://www.w3.org/2001/XMLSchema#integer>,
"hola"@es) -- unlike GgenIgniter.Query.run/2 (the sparql hex engine),
which returns plain unwrapped values (RDF.IRI.to_string/1,
RDF.Literal.value/1). Confirmed real consequences of the old behavior,
pinned in test/ggen_igniter_e2e_all_engines_test.exs and
test/ggen_igniter_engine_mode_matrix_test.exs before this fix: a
module-name literal rendered straight into a template produced
syntactically-invalid Elixir (defmodule "AuditTrail.Resource" do), and
--for-each's <%= module_name %>-templated output PATHS carried literal
embedded quote characters.
Fixed AT THE SOURCE, in the Rust NIF itself
(native/ggen_graph_nif/src/oxigraph_engine.rs's normalize_term/1), not
by post-processing the NIF's string output in this Elixir wrapper: oxrdf's
own typed accessors (Literal::value(), NamedNode::as_str()) read each
term's real semantic content directly, so there is no string-parsing/regex
step anywhere in this path, and no template or test needs its own
quote-stripping workaround anymore.
run/2 (and run/3 with no raw: true) now return these PLAIN values by
default. The real datatype IRI and language tag are NOT silently discarded
-- they remain available, in full, via the explicit raw: true opt-in
(run/3), which returns oxigraph's original pre-fix raw term strings
verbatim (the exact same shape this engine produced before this fix), for
any caller that genuinely needs to inspect a binding's datatype/language.
Disclosed, honest scope limit (see normalize_term/1's own doc comment for
the full reasoning): the plain default returns each literal's LEXICAL
STRING uniformly, regardless of datatype -- an xsd:boolean "true" literal
normalizes to the string "true", not the Elixir boolean true, even
though GgenIgniter.Query.run/2's RDF.Literal.value/1 really does return
a native true/false (and native integers for xsd:integer) for those
two datatypes specifically, via RDF.ex's own per-datatype elixir_mapping/2
coercion. This fix's guarantee is that no stray </>/"/^^/@lang
wrapper syntax leaks into a value -- not a full reimplementation of RDF.ex's
XSD-datatype-to-native-Elixir-type coercion table, which is a materially
larger, separate scope.
Summary
Functions
Serializes graph back to Turtle (it is already fully loaded in memory by
GgenIgniter.Ontology.load!/1 -- no re-read from disk) and runs query
against it via the native oxigraph NIF. Raises RuntimeError with a clear
message on any NIF-reported error, never letting a raw {:error, reason}
tuple surface uncaught (same discipline as the recent
GgenIgniter.Query.Qlever.run/2 bugfix).
Same contract as run/2, plus an opts keyword list
Functions
@spec run(RDF.Graph.t(), String.t()) :: [map()]
Serializes graph back to Turtle (it is already fully loaded in memory by
GgenIgniter.Ontology.load!/1 -- no re-read from disk) and runs query
against it via the native oxigraph NIF. Raises RuntimeError with a clear
message on any NIF-reported error, never letting a raw {:error, reason}
tuple surface uncaught (same discipline as the recent
GgenIgniter.Query.Qlever.run/2 bugfix).
Returns PLAIN, normalized values by default -- see this module's own
moduledoc ("Term normalization") for the full rationale and its one
disclosed scope limit. Equivalent to run(graph, query, []); pass
raw: true (see run/3) for the explicit opt-in that returns oxigraph's
original raw N-Triples-style term strings instead.
@spec run(RDF.Graph.t(), String.t(), keyword()) :: [map()]
Same contract as run/2, plus an opts keyword list:
raw: true-- returns oxigraph's ORIGINAL, unprocessed N-Triples-style term strings (IRIs angle-bracket-wrapped, literals quoted and datatype-IRI/language-tag-suffixed) instead of the plain, normalized default. A real, explicit, documented opt-in for a caller that genuinely needs a binding's datatype IRI or language tag -- see this module's moduledoc ("Term normalization") for the full rationale. Defaults tofalse(the plain, normalized shape).