From cafba9f61fcb544e189115ab1fa6ef885cdb36e2 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Tue, 13 Oct 2020 10:43:05 +0200 Subject: [PATCH] Add support for RDF.PropertyMaps in BGP and path queries --- CHANGELOG.md | 4 +-- lib/rdf/query.ex | 4 +-- lib/rdf/query/builder.ex | 47 ++++++++++++++++++-------------- test/unit/query/builder_test.exs | 43 +++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b883c32..0b83709 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/lib/rdf/query.ex b/lib/rdf/query.ex index 43e3745..4e0c88d 100644 --- a/lib/rdf/query.ex +++ b/lib/rdf/query.ex @@ -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 diff --git a/lib/rdf/query/builder.ex b/lib/rdf/query/builder.ex index 9322c6b..51b1ef3 100644 --- a/lib/rdf/query/builder.ex +++ b/lib/rdf/query/builder.ex @@ -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 diff --git a/test/unit/query/builder_test.exs b/test/unit/query/builder_test.exs index 6ccfe47..652aff6 100644 --- a/test/unit/query/builder_test.exs +++ b/test/unit/query/builder_test.exs @@ -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