In-process OBDA counterpart to AshR2RML.OBDA.Ontop, for any Ash data layer Ontop
cannot reach.
Ontop rewrites SPARQL to SQL against a JDBC-connected relational database; that
has no analogue for Ash.DataLayer.Ets (no SQL surface at all) or, in fact, any
non-JDBC-reachable Ash data layer. Instead of faking Ontop, this module takes the
opposite, honest path: it materializes the real rows of one or more Ash
resources -- via a real Ash.read!/2, with no data-layer gate of its own -- into a
real RDF.Graph using the canonical mapping IR (AshR2RML.Mapping.Resource, the
same IR the R2RML renderer consumes), then executes the query with the
already-existing AshR2RML.SPARQL.Local engine (SPARQL.ex, full SPARQL
algebra) against that graph. No hand-rolled query language, no partial BGP
matcher standing in for SPARQL.
Because there is no data-layer check anywhere in this module, it works against any
real Ash data layer a resource happens to use -- confirmed for real (not just by
absence-of-a-check) against Ash.DataLayer.Ets, AshCsv.DataLayer, and
AshCubDB.DataLayer in test/obda_in_memory_other_data_layers_test.exs. The
"ETS-side" framing in earlier revisions of this moduledoc undersold that generality.
query/4 / materialize/3 handle one resource. query_many/2 / materialize_many/2
merge several resources' triples into a single graph -- including real rr:RefObjectMap
relationship triples resolved from each resource's reference_object_maps against the
other resources in the same call -- so one SPARQL query can traverse a real Ash
relationship (e.g. Person -[memberOf]-> Organization) the same way it would through
Ontop's SQL join.
Materialization intentionally supports what R2RML's own admitted correspondence
table supports: template/column subject maps, datatype-property predicate-object
maps, and rr:joinCondition reference object maps with one or more join
columns -- a genuine composite-key join (joins: [%JoinCondition{}, %JoinCondition{}, ...])
resolves the object IRI by substituting every child column's real value into the
parent's own subject template in turn, producing an IRI only when every column pair
resolves and the resulting candidate is a valid IRI (see resolve_composite_object_iri/3).
The one relationship shape this module does not resolve for real is the
:join_table many-to-many shape (joins: [], metadata.kind == :many_to_many, per
AshR2RML.SemanticAdapter.convert_references/2): the actual FK pairs for that shape
live on a bridge table that is never itself a mapped Ash resource anywhere in a
materialize_many/2 call, so there is no real row data here to resolve the join
against without fabricating it. This is deliberately a typed
:REFUSED_UNSUPPORTED_SPARQL_FEATURE refusal returned from materialize/3 and
materialize_many/2, not a silent skip -- the caller is told exactly which
relationship was dropped and why, rather than discovering a missing edge in the
materialized graph with no signal at all. Anything else the current mapping IR does
not model (e.g. custom graph maps beyond the default graph) remains a typed refusal
or a silently-skipped triple, never a fabricated one.
Summary
Functions
Materializes real rows read from ash_resource (via a real Ash read action)
into an RDF.Graph shaped by mapping_resource.
Materializes several resources into one shared RDF.Graph, including relationship
triples for any reference_object_maps whose parent_resource is also present in
specs -- this is what lets one SPARQL query join across resources.
Materializes ash_resource/mapping_resource and executes sparql against the
resulting real RDF graph, returning an Observation shaped identically to the
observations AshR2RML.OBDA.Ontop and AshR2RML.SPARQL.Local already produce so
it composes with AshR2RML.SPARQL.Differential.compare/3.
Materializes several resources into one graph (see materialize_many/2) and executes
one SPARQL query across all of them -- a real cross-resource join, resolved from the
same mapping IR every other rendering surface uses.
Types
@type spec() :: {ash_resource :: module(), mapping_resource :: AshR2RML.Mapping.Resource.t()}
Functions
@spec materialize(module(), AshR2RML.Mapping.Resource.t(), keyword()) :: {:ok, RDF.Graph.t()} | {:error, AshR2RML.Refusal.t()}
Materializes real rows read from ash_resource (via a real Ash read action)
into an RDF.Graph shaped by mapping_resource.
read_opts is passed through to Ash.read!/2 verbatim (e.g. domain:,
actor:) so this always executes a real Ash action against the real ETS
table -- never a fabricated row set.
@spec materialize_many( [spec()], keyword() ) :: {:ok, RDF.Graph.t()} | {:error, AshR2RML.Refusal.t()}
Materializes several resources into one shared RDF.Graph, including relationship
triples for any reference_object_maps whose parent_resource is also present in
specs -- this is what lets one SPARQL query join across resources.
read_opts applies uniformly to every resource's Ash.read!/2 call (e.g. a shared
domain:/actor:); pass distinct options per resource by reading each resource's
rows yourself and preferring query/4's single-resource form instead if that's needed.
@spec query(module(), AshR2RML.Mapping.Resource.t(), String.t(), keyword()) :: {:ok, AshR2RML.SPARQL.Observation.t()} | {:error, AshR2RML.Refusal.t()}
Materializes ash_resource/mapping_resource and executes sparql against the
resulting real RDF graph, returning an Observation shaped identically to the
observations AshR2RML.OBDA.Ontop and AshR2RML.SPARQL.Local already produce so
it composes with AshR2RML.SPARQL.Differential.compare/3.
@spec query_many([spec()], String.t(), keyword()) :: {:ok, AshR2RML.SPARQL.Observation.t()} | {:error, AshR2RML.Refusal.t()}
Materializes several resources into one graph (see materialize_many/2) and executes
one SPARQL query across all of them -- a real cross-resource join, resolved from the
same mapping IR every other rendering surface uses.