Change format for BGP queries to be inline with the common input formats
This commit is contained in:
parent
aed8e5bae6
commit
7fda16a40e
3 changed files with 119 additions and 65 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -7,6 +7,15 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
|
||||||
|
|
||||||
## Unreleased
|
## 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
|
### Added
|
||||||
|
|
||||||
- to `RDF.Description`
|
- to `RDF.Description`
|
||||||
|
@ -23,6 +32,9 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
|
||||||
|
|
||||||
### Changed
|
### 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;
|
- `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
|
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
|
- 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
|
### 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
|
- `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
|
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`
|
functions like `RDF.Graph.subjects/1`, `RDF.Graph.subject_count/1`, `RDF.Graph.descriptions/1`
|
||||||
|
|
|
@ -19,56 +19,41 @@ defmodule RDF.Query.Builder do
|
||||||
end
|
end
|
||||||
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 ->
|
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)}
|
{:ok, List.wrap(triple_pattern)}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp triple_patterns(triple_pattern) when is_tuple(triple_pattern),
|
defp triple_patterns({subject, predicate, objects}) do
|
||||||
do: triple_patterns([triple_pattern])
|
with {:ok, subject_pattern} <- subject_pattern(subject) do
|
||||||
|
do_triple_patterns(subject_pattern, {predicate, objects})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp triple_pattern({subject, predicate, object})
|
defp triple_patterns({subject, predications}) when is_map(predications) do
|
||||||
when not is_list(predicate) and not is_list(object) do
|
triple_patterns({subject, Map.to_list(predications)})
|
||||||
with {:ok, subject_pattern} <- subject_pattern(subject),
|
end
|
||||||
{:ok, predicate_pattern} <- predicate_pattern(predicate),
|
|
||||||
{:ok, object_pattern} <- object_pattern(object) do
|
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}}
|
{:ok, {subject_pattern, predicate_pattern, object_pattern}}
|
||||||
end
|
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)
|
end)
|
||||||
|> triple_patterns()
|
|
||||||
else
|
|
||||||
{:error,
|
|
||||||
%RDF.Query.InvalidError{
|
|
||||||
message: "Invalid use of predicate-object pair brackets"
|
|
||||||
}}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ defmodule RDF.Query.BuilderTest do
|
||||||
|
|
||||||
alias RDF.Query.Builder
|
alias RDF.Query.Builder
|
||||||
|
|
||||||
describe "new/1" do
|
describe "bgp/1" do
|
||||||
test "empty triple pattern" do
|
test "empty triple pattern" do
|
||||||
assert Builder.bgp([]) == ok_bgp_struct([])
|
assert Builder.bgp([]) == ok_bgp_struct([])
|
||||||
end
|
end
|
||||||
|
@ -104,19 +104,16 @@ defmodule RDF.Query.BuilderTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "multiple objects to the same subject-predicate" do
|
test "multiple objects to the same subject-predicate" do
|
||||||
assert Builder.bgp([{EX.s(), EX.p(), EX.o1(), EX.o2()}]) ==
|
result =
|
||||||
ok_bgp_struct([
|
ok_bgp_struct([
|
||||||
{EX.s(), EX.p(), EX.o1()},
|
{EX.s(), EX.p(), EX.o1()},
|
||||||
{EX.s(), EX.p(), EX.o2()}
|
{EX.s(), EX.p(), EX.o2()}
|
||||||
])
|
])
|
||||||
|
|
||||||
assert Builder.bgp({EX.s(), EX.p(), EX.o1(), EX.o2()}) ==
|
assert Builder.bgp([{EX.s(), EX.p(), [EX.o1(), EX.o2()]}]) == result
|
||||||
ok_bgp_struct([
|
assert Builder.bgp({EX.s(), EX.p(), [EX.o1(), EX.o2()]}) == result
|
||||||
{EX.s(), EX.p(), EX.o1()},
|
|
||||||
{EX.s(), EX.p(), EX.o2()}
|
|
||||||
])
|
|
||||||
|
|
||||||
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([
|
ok_bgp_struct([
|
||||||
{EX.s(), EX.p(), :o},
|
{EX.s(), EX.p(), :o},
|
||||||
{EX.s(), EX.p(), XSD.false()},
|
{EX.s(), EX.p(), XSD.false()},
|
||||||
|
@ -126,13 +123,7 @@ defmodule RDF.Query.BuilderTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "multiple predicate-object pairs to the same subject" do
|
test "multiple predicate-object pairs to the same subject" do
|
||||||
assert Builder.bgp([
|
result =
|
||||||
{
|
|
||||||
EX.s(),
|
|
||||||
[EX.p1(), EX.o1()],
|
|
||||||
[EX.p2(), EX.o2()]
|
|
||||||
}
|
|
||||||
]) ==
|
|
||||||
ok_bgp_struct([
|
ok_bgp_struct([
|
||||||
{EX.s(), EX.p1(), EX.o1()},
|
{EX.s(), EX.p1(), EX.o1()},
|
||||||
{EX.s(), EX.p2(), EX.o2()}
|
{EX.s(), EX.p2(), EX.o2()}
|
||||||
|
@ -141,9 +132,21 @@ defmodule RDF.Query.BuilderTest do
|
||||||
assert Builder.bgp([
|
assert Builder.bgp([
|
||||||
{
|
{
|
||||||
EX.s(),
|
EX.s(),
|
||||||
[:a, :o?],
|
[
|
||||||
[EX.p1(), 42, 3.14],
|
{EX.p1(), EX.o1()},
|
||||||
[EX.p2(), "foo", true]
|
{EX.p2(), EX.o2()}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]) == result
|
||||||
|
|
||||||
|
assert Builder.bgp([
|
||||||
|
{
|
||||||
|
EX.s(),
|
||||||
|
[
|
||||||
|
{:a, :o?},
|
||||||
|
{EX.p1(), [42, 3.14]},
|
||||||
|
{EX.p2(), ["foo", true]}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]) ==
|
]) ==
|
||||||
ok_bgp_struct([
|
ok_bgp_struct([
|
||||||
|
@ -154,9 +157,61 @@ defmodule RDF.Query.BuilderTest do
|
||||||
{EX.s(), EX.p2(), XSD.true()}
|
{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()}])
|
ok_bgp_struct([{EX.s(), EX.p(), EX.o()}])
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "path/2" do
|
describe "path/2" do
|
||||||
|
|
Loading…
Reference in a new issue