Add support for RDF.PropertyMaps in BGP and path queries

This commit is contained in:
Marcel Otto 2020-10-13 10:43:05 +02:00
parent d3c6336782
commit cafba9f61f
4 changed files with 74 additions and 24 deletions

View file

@ -20,8 +20,8 @@ are specified.
- `RDF.PropertyMap` which allow definition of atoms for RDF properties.
Such property maps can be provided to all RDF data structure functions
accepting input data with the `:context` opt, allowing the use of the atoms
from the property map in the input data.
accepting input data and BGP query patterns with the `:context` opt,
allowing the use of the atoms from the property map in the input data.
- to `RDF.Description`
- `RDF.Description.subject/1`
- `RDF.Description.change_subject/2`

View file

@ -73,7 +73,7 @@ defmodule RDF.Query do
end
def execute(query, graph, opts) do
with {:ok, bgp} <- Builder.bgp(query) do
with {:ok, bgp} <- Builder.bgp(query, opts) do
execute(bgp, graph, opts)
end
end
@ -141,7 +141,7 @@ defmodule RDF.Query do
end
def stream(query, graph, opts) do
with {:ok, bgp} <- Builder.bgp(query) do
with {:ok, bgp} <- Builder.bgp(query, opts) do
stream(bgp, graph, opts)
end
end

View file

@ -2,51 +2,53 @@ defmodule RDF.Query.Builder do
@moduledoc false
alias RDF.Query.BGP
alias RDF.{IRI, BlankNode, Literal, Namespace}
alias RDF.{IRI, BlankNode, Literal, Namespace, PropertyMap}
import RDF.Utils.Guards
import RDF.Utils
def bgp(query) do
with {:ok, triple_patterns} <- triple_patterns(query) do
def bgp(query, opts \\ []) do
property_map = if context = Keyword.get(opts, :context), do: PropertyMap.new(context)
with {:ok, triple_patterns} <- triple_patterns(query, property_map) do
{:ok, %BGP{triple_patterns: triple_patterns}}
end
end
def bgp!(query) do
case bgp(query) do
def bgp!(query, opts \\ []) do
case bgp(query, opts) do
{:ok, bgp} -> bgp
{:error, error} -> raise error
end
end
defp triple_patterns(query) when is_list(query) or is_map(query) do
defp triple_patterns(query, property_map) when is_list(query) or is_map(query) do
flat_map_while_ok(query, fn triple ->
with {:ok, triple_pattern} <- triple_patterns(triple) do
with {:ok, triple_pattern} <- triple_patterns(triple, property_map) do
{:ok, List.wrap(triple_pattern)}
end
end)
end
defp triple_patterns({subject, predicate, objects}) do
defp triple_patterns({subject, predicate, objects}, property_map) do
with {:ok, subject_pattern} <- subject_pattern(subject) do
do_triple_patterns(subject_pattern, {predicate, objects})
do_triple_patterns(subject_pattern, {predicate, objects}, property_map)
end
end
defp triple_patterns({subject, predications}) when is_map(predications) do
triple_patterns({subject, Map.to_list(predications)})
defp triple_patterns({subject, predications}, property_map) when is_map(predications) do
triple_patterns({subject, Map.to_list(predications)}, property_map)
end
defp triple_patterns({subject, predications}) do
defp triple_patterns({subject, predications}, property_map) do
with {:ok, subject_pattern} <- subject_pattern(subject) do
predications
|> List.wrap()
|> flat_map_while_ok(&do_triple_patterns(subject_pattern, &1))
|> flat_map_while_ok(&do_triple_patterns(subject_pattern, &1, property_map))
end
end
defp do_triple_patterns(subject_pattern, {predicate, objects}) do
with {:ok, predicate_pattern} <- predicate_pattern(predicate) do
defp do_triple_patterns(subject_pattern, {predicate, objects}, property_map) do
with {:ok, predicate_pattern} <- predicate_pattern(predicate, property_map) do
objects
|> List.wrap()
|> map_while_ok(fn object ->
@ -70,8 +72,8 @@ defmodule RDF.Query.Builder do
end
end
defp predicate_pattern(predicate) do
value = variable(predicate) || resource(predicate) || property(predicate)
defp predicate_pattern(predicate, property_map) do
value = variable(predicate) || resource(predicate) || property(predicate, property_map)
if value do
{:ok, value}
@ -127,8 +129,13 @@ defmodule RDF.Query.Builder do
defp resource(_), do: nil
defp property(:a), do: RDF.type()
defp property(_), do: nil
defp property(:a, _), do: RDF.type()
defp property(term, property_map) when is_atom(term) and not is_nil(property_map) do
PropertyMap.iri(property_map, term)
end
defp property(_, _), do: nil
defp literal(%Literal{} = literal), do: literal
defp literal(value), do: Literal.coerce(value)
@ -144,7 +151,7 @@ defmodule RDF.Query.Builder do
def path([subject | rest], opts) do
path_pattern(subject, rest, [], 0, Keyword.get(opts, :with_elements, false))
|> bgp()
|> bgp(opts)
end
def path!(query, opts \\ []) do

View file

@ -212,6 +212,30 @@ defmodule RDF.Query.BuilderTest do
{~B"s", :p, :o}
])
end
test "with contexts" do
assert Builder.bgp(
%{
s?: %{
p1: :o?,
p2: [42, true]
},
o?: [p3: ["foo", "bar"]]
},
context: %{
p1: EX.p1(),
p2: EX.p2(),
p3: EX.p3()
}
) ==
ok_bgp_struct([
{:o, EX.p3(), ~L"foo"},
{:o, EX.p3(), ~L"bar"},
{:s, EX.p1(), :o},
{:s, EX.p2(), XSD.integer(42)},
{:s, EX.p2(), XSD.true()}
])
end
end
describe "path/2" do
@ -247,5 +271,24 @@ defmodule RDF.Query.BuilderTest do
{:el0, EX.p2(), :o}
])
end
test "with contexts" do
property_map = %{
p1: EX.p1(),
p2: EX.p2()
}
assert Builder.path([EX.s(), :p1, :p2, EX.o()], context: property_map) ==
ok_bgp_struct([
{EX.s(), EX.p1(), RDF.bnode("0")},
{RDF.bnode("0"), EX.p2(), EX.o()}
])
assert Builder.path([EX.s(), :p1, :p2, :o?], context: property_map, with_elements: true) ==
ok_bgp_struct([
{EX.s(), EX.p1(), :el0},
{:el0, EX.p2(), :o}
])
end
end
end