AshR2RML.OBDA.InMemory (AshR2RML v26.8.29)

Copy Markdown View Source

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

spec()

@type spec() ::
  {ash_resource :: module(), mapping_resource :: AshR2RML.Mapping.Resource.t()}

Functions

materialize(ash_resource, mapping_resource, read_opts \\ [])

@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.

materialize_many(specs, read_opts \\ [])

@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.

query(ash_resource, mapping_resource, sparql, read_opts \\ [])

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.

query_many(specs, sparql, read_opts \\ [])

@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.