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. - `RDF.PropertyMap` which allow definition of atoms for RDF properties.
Such property maps can be provided to all RDF data structure functions 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 accepting input data and BGP query patterns with the `:context` opt,
from the property map in the input data. allowing the use of the atoms from the property map in the input data.
- to `RDF.Description` - to `RDF.Description`
- `RDF.Description.subject/1` - `RDF.Description.subject/1`
- `RDF.Description.change_subject/2` - `RDF.Description.change_subject/2`

View file

@ -73,7 +73,7 @@ defmodule RDF.Query do
end end
def execute(query, graph, opts) do 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) execute(bgp, graph, opts)
end end
end end
@ -141,7 +141,7 @@ defmodule RDF.Query do
end end
def stream(query, graph, opts) do 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) stream(bgp, graph, opts)
end end
end end

View file

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

View file

@ -212,6 +212,30 @@ defmodule RDF.Query.BuilderTest do
{~B"s", :p, :o} {~B"s", :p, :o}
]) ])
end 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 end
describe "path/2" do describe "path/2" do
@ -247,5 +271,24 @@ defmodule RDF.Query.BuilderTest do
{:el0, EX.p2(), :o} {:el0, EX.p2(), :o}
]) ])
end 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
end end