Change format for BGP queries to be inline with the common input formats

This commit is contained in:
Marcel Otto 2020-09-30 16:38:29 +02:00
parent aed8e5bae6
commit 7fda16a40e
3 changed files with 119 additions and 65 deletions

View file

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

View file

@ -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_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({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
defp triple_patterns({subject, predications}) when is_map(predications) do
triple_patterns({subject, Map.to_list(predications)})
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
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
defp triple_pattern(combined_objects_triple_pattern)
when is_tuple(combined_objects_triple_pattern) do
[subject | rest] = Tuple.to_list(combined_objects_triple_pattern)
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
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"
}}
end
end
end

View file

@ -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()}]) ==
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,13 +123,7 @@ 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()]
}
]) ==
result =
ok_bgp_struct([
{EX.s(), EX.p1(), EX.o1()},
{EX.s(), EX.p2(), EX.o2()}
@ -141,9 +132,21 @@ defmodule RDF.Query.BuilderTest do
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