From 7fda16a40e7dbf0a0c6499adf664ea1d53c38df0 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Wed, 30 Sep 2020 16:38:29 +0200 Subject: [PATCH] Change format for BGP queries to be inline with the common input formats --- CHANGELOG.md | 14 ++++ lib/rdf/query/builder.ex | 61 +++++++---------- test/unit/query/builder_test.exs | 109 +++++++++++++++++++++++-------- 3 files changed, 119 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c84f26b..88bf195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ This project adheres to [Semantic Versioning](http://semver.org/) and ## Unreleased +The API of the all three RDF datastructures `RDF.Dataset`, `RDF.Graph` and +`RDF.Description` were changed, so that the functions taking input data consist only +of one field in order to open the possibility of introducing options on these +functions. The supported ways with which RDF statements can be passed to the +RDF data structures were extended and unified to be supported across all functions +accepting input data. This includes also the way in which patterns for BGP queries +are specified. + + ### Added - to `RDF.Description` @@ -23,6 +32,9 @@ This project adheres to [Semantic Versioning](http://semver.org/) and ### Changed +- the format for the specification of BGP queries with `RDF.Graph.query/2`, + `RDF.Graph.query_stream/2` and `RDF.Query.bgp/1` has been changed to be consistent with + the supported formats for input data in the rest of the library - `RDF.Description.new` now requires the `subject` to be passed always as first argument; if you want to add some initial data this must be done with the `:init` option - for consistency reasons the internal `:id` struct field of `RDF.BlankNode` was renamed @@ -30,6 +42,8 @@ This project adheres to [Semantic Versioning](http://semver.org/) and ### Fixed +- the `put` functions on `RDF.Description`, `RDF.Graph` and `RDF.Dataset` didn't add all + statements properly under certain circumstances - `RDF.Graph.put/2` ignores empty descriptions; this should be the final piece to ensure that `RDF.Graph`s never contain empty descriptions, which would distort results of functions like `RDF.Graph.subjects/1`, `RDF.Graph.subject_count/1`, `RDF.Graph.descriptions/1` diff --git a/lib/rdf/query/builder.ex b/lib/rdf/query/builder.ex index 9fb2c33..9322c6b 100644 --- a/lib/rdf/query/builder.ex +++ b/lib/rdf/query/builder.ex @@ -19,56 +19,41 @@ defmodule RDF.Query.Builder do end end - defp triple_patterns(query) when is_list(query) do + defp triple_patterns(query) when is_list(query) or is_map(query) do flat_map_while_ok(query, fn triple -> - with {:ok, triple_pattern} <- triple_pattern(triple) do + with {:ok, triple_pattern} <- triple_patterns(triple) do {:ok, List.wrap(triple_pattern)} end end) end - defp triple_patterns(triple_pattern) when is_tuple(triple_pattern), - do: triple_patterns([triple_pattern]) - - defp triple_pattern({subject, predicate, object}) - when not is_list(predicate) and not is_list(object) do - with {:ok, subject_pattern} <- subject_pattern(subject), - {:ok, predicate_pattern} <- predicate_pattern(predicate), - {:ok, object_pattern} <- object_pattern(object) do - {:ok, {subject_pattern, predicate_pattern, object_pattern}} + defp triple_patterns({subject, predicate, objects}) do + with {:ok, subject_pattern} <- subject_pattern(subject) do + do_triple_patterns(subject_pattern, {predicate, objects}) end end - defp triple_pattern(combined_objects_triple_pattern) - when is_tuple(combined_objects_triple_pattern) do - [subject | rest] = Tuple.to_list(combined_objects_triple_pattern) + defp triple_patterns({subject, predications}) when is_map(predications) do + triple_patterns({subject, Map.to_list(predications)}) + end - case rest do - [predicate | objects] when not is_list(predicate) -> - if Enum.all?(objects, &(not is_list(&1))) do - objects - |> Enum.map(fn object -> {subject, predicate, object} end) - |> triple_patterns() - else - {:error, - %RDF.Query.InvalidError{ - message: "Invalid use of predicate-object pair brackets" - }} - end + defp triple_patterns({subject, predications}) do + with {:ok, subject_pattern} <- subject_pattern(subject) do + predications + |> List.wrap() + |> flat_map_while_ok(&do_triple_patterns(subject_pattern, &1)) + end + end - predicate_object_pairs -> - if Enum.all?(predicate_object_pairs, &(is_list(&1) and length(&1) > 1)) do - predicate_object_pairs - |> Enum.flat_map(fn [predicate | objects] -> - Enum.map(objects, fn object -> {subject, predicate, object} end) - end) - |> triple_patterns() - else - {:error, - %RDF.Query.InvalidError{ - message: "Invalid use of predicate-object pair brackets" - }} + defp do_triple_patterns(subject_pattern, {predicate, objects}) do + with {:ok, predicate_pattern} <- predicate_pattern(predicate) do + objects + |> List.wrap() + |> map_while_ok(fn object -> + with {:ok, object_pattern} <- object_pattern(object) do + {:ok, {subject_pattern, predicate_pattern, object_pattern}} end + end) end end diff --git a/test/unit/query/builder_test.exs b/test/unit/query/builder_test.exs index 7cd0845..6ccfe47 100644 --- a/test/unit/query/builder_test.exs +++ b/test/unit/query/builder_test.exs @@ -3,7 +3,7 @@ defmodule RDF.Query.BuilderTest do alias RDF.Query.Builder - describe "new/1" do + describe "bgp/1" do test "empty triple pattern" do assert Builder.bgp([]) == ok_bgp_struct([]) end @@ -104,19 +104,16 @@ defmodule RDF.Query.BuilderTest do end test "multiple objects to the same subject-predicate" do - assert Builder.bgp([{EX.s(), EX.p(), EX.o1(), EX.o2()}]) == - ok_bgp_struct([ - {EX.s(), EX.p(), EX.o1()}, - {EX.s(), EX.p(), EX.o2()} - ]) + result = + ok_bgp_struct([ + {EX.s(), EX.p(), EX.o1()}, + {EX.s(), EX.p(), EX.o2()} + ]) - assert Builder.bgp({EX.s(), EX.p(), EX.o1(), EX.o2()}) == - ok_bgp_struct([ - {EX.s(), EX.p(), EX.o1()}, - {EX.s(), EX.p(), EX.o2()} - ]) + assert Builder.bgp([{EX.s(), EX.p(), [EX.o1(), EX.o2()]}]) == result + assert Builder.bgp({EX.s(), EX.p(), [EX.o1(), EX.o2()]}) == result - assert Builder.bgp({EX.s(), EX.p(), :o?, false, 42, "foo"}) == + assert Builder.bgp({EX.s(), EX.p(), [:o?, false, 42, "foo"]}) == ok_bgp_struct([ {EX.s(), EX.p(), :o}, {EX.s(), EX.p(), XSD.false()}, @@ -126,24 +123,30 @@ defmodule RDF.Query.BuilderTest do end test "multiple predicate-object pairs to the same subject" do - assert Builder.bgp([ - { - EX.s(), - [EX.p1(), EX.o1()], - [EX.p2(), EX.o2()] - } - ]) == - ok_bgp_struct([ - {EX.s(), EX.p1(), EX.o1()}, - {EX.s(), EX.p2(), EX.o2()} - ]) + result = + ok_bgp_struct([ + {EX.s(), EX.p1(), EX.o1()}, + {EX.s(), EX.p2(), EX.o2()} + ]) assert Builder.bgp([ { EX.s(), - [:a, :o?], - [EX.p1(), 42, 3.14], - [EX.p2(), "foo", true] + [ + {EX.p1(), EX.o1()}, + {EX.p2(), EX.o2()} + ] + } + ]) == result + + assert Builder.bgp([ + { + EX.s(), + [ + {:a, :o?}, + {EX.p1(), [42, 3.14]}, + {EX.p2(), ["foo", true]} + ] } ]) == ok_bgp_struct([ @@ -154,9 +157,61 @@ defmodule RDF.Query.BuilderTest do {EX.s(), EX.p2(), XSD.true()} ]) - assert Builder.bgp([{EX.s(), [EX.p(), EX.o()]}]) == + assert Builder.bgp([{EX.s(), {EX.p(), EX.o()}}]) == ok_bgp_struct([{EX.s(), EX.p(), EX.o()}]) end + + test "triple patterns with maps" do + assert Builder.bgp(%{ + EX.S => {EX.p(), :o?}, + o?: [ + {EX.p2(), 42}, + {EX.p3(), "foo"} + ] + }) == + ok_bgp_struct([ + {RDF.iri(EX.S), EX.p(), :o}, + {:o, EX.p2(), RDF.literal(42)}, + {:o, EX.p3(), RDF.literal("foo")} + ]) + + assert Builder.bgp(%{ + EX.s() => %{ + :a => :o1?, + :p? => :o2?, + EX.p1() => [42, 3.14], + EX.p2() => ["foo", true] + } + }) == + ok_bgp_struct([ + {EX.s(), RDF.type(), :o1}, + {EX.s(), :p, :o2}, + {EX.s(), EX.p1(), RDF.literal(42)}, + {EX.s(), EX.p1(), RDF.literal(3.14)}, + {EX.s(), EX.p2(), RDF.literal("foo")}, + {EX.s(), EX.p2(), XSD.true()} + ]) + + assert Builder.bgp([ + %{EX.S => {EX.p(), :o?}}, + {EX.S2, EX.p(), :o?} + ]) == + ok_bgp_struct([ + {RDF.iri(EX.S), EX.p(), :o}, + {RDF.iri(EX.S2), EX.p(), :o} + ]) + end + + test "triple patterns with descriptions" do + assert Builder.bgp([ + EX.p(~B"s", EX.O), + {:_s, :p?, :o?} + ]) == + ok_bgp_struct([ + {~B"s", EX.p(), RDF.iri(EX.O)}, + {~B"s", :p, :o} + ]) + end end describe "path/2" do