Change BGP matching query results to return variable keys as atoms

This commit is contained in:
Marcel Otto 2020-06-08 21:56:50 +02:00
parent 9cd4478574
commit f9e451f006
9 changed files with 265 additions and 240 deletions

View file

@ -17,7 +17,7 @@ alias RDF.Query.BGP
test_graph = RDF.Turtle.read_file!("test/data/TURTLE-TESTS/manifest.ttl", base: "http://www.w3.org/2013/TurtleTests/")
all_query = [{"s", "p", "o"}]
all_query = %BGP{triple_patterns: [{:s, :p, :o}]}
Benchee.run(%{
"take 1 from BGP.Simple" => fn -> BGP.Simple.query_stream(test_graph, all_query) |> Enum.take(1) end,
"take 1 from BGP.Stream" => fn -> BGP.Stream.query_stream(test_graph, all_query) |> Enum.take(1) end,
@ -25,18 +25,19 @@ Benchee.run(%{
# rdft:approval rdft:Approved - count: 287
approved_query = [
{"test_case", RDFT.approval, RDF.iri(RDFT.Approved)},
{"test_case", MF.name, "name"},
{"test_case", RDFS.comment, "comment"},
]
approved_query = %BGP{triple_patterns: [
{:test_case, RDFT.approval, RDF.iri(RDFT.Approved)},
{:test_case, MF.name, :name},
{:test_case, RDFS.comment, :comment},
]}
# rdft:approval rdft:Proposed - count: 4
proposed_query = [
{"test_case", RDFT.approval, RDF.iri(RDFT.Proposed)},
{"test_case", MF.name, "name"},
{"test_case", RDFS.comment, "comment"},
]
proposed_query = %BGP{triple_patterns: [
{:test_case, RDFT.approval, RDF.iri(RDFT.Proposed)},
{:test_case, MF.name, :name},
{:test_case, RDFS.comment, :comment},
]}
Benchee.run(%{
"APPROVED from BGP.Simple" => fn -> BGP.Simple.query(test_graph, approved_query) end,
"PROPOSED from BGP.Simple" => fn -> BGP.Simple.query(test_graph, proposed_query) end,

View file

@ -11,4 +11,23 @@ defmodule RDF.Query.BGP do
@type triple_patterns :: list(triple_pattern)
@type t :: %__MODULE__{triple_patterns: triple_patterns}
def variables(%__MODULE__{triple_patterns: triple_patterns}), do: variables(triple_patterns)
def variables(triple_patterns) when is_list(triple_patterns) do
triple_patterns
|> Enum.reduce([], fn triple_pattern, vars -> variables(triple_pattern) ++ vars end)
|> Enum.uniq()
end
def variables({s, p, o}) when is_atom(s) and is_atom(p) and is_atom(o), do: [s, p, o]
def variables({s, p, _}) when is_atom(s) and is_atom(p), do: [s, p]
def variables({s, _, o}) when is_atom(s) and is_atom(o), do: [s, o]
def variables({_, p, o}) when is_atom(p) and is_atom(o), do: [p, o]
def variables({s, _, _}) when is_atom(s), do: [s]
def variables({_, p, _}) when is_atom(p), do: [p]
def variables({_, _, o}) when is_atom(o), do: [o]
def variables(_), do: []
end

View file

@ -1,9 +1,9 @@
defmodule RDF.Query.BGP.BlankNodeHandler do
@moduledoc false
alias RDF.Query.BGP
alias RDF.BlankNode
@blank_node_prefix "_:"
@default_remove_bnode_query_variables Application.get_env(:rdf, :default_remove_bnode_query_variables, true)
def preprocess(triple_patterns) do
@ -14,31 +14,42 @@ defmodule RDF.Query.BGP.BlankNodeHandler do
end)
end
defp convert_blank_nodes({%BlankNode{} = s, %BlankNode{} = p, %BlankNode{} = o}), do: {true, {to_string(s), to_string(p), to_string(o)}}
defp convert_blank_nodes({s, %BlankNode{} = p, %BlankNode{} = o}), do: {true, {s, to_string(p), to_string(o)}}
defp convert_blank_nodes({%BlankNode{} = s, p, %BlankNode{} = o}), do: {true, {to_string(s), p, to_string(o)}}
defp convert_blank_nodes({%BlankNode{} = s, %BlankNode{} = p, o}), do: {true, {to_string(s), to_string(p), o}}
defp convert_blank_nodes({%BlankNode{} = s, p, o}), do: {true, {to_string(s), p, o}}
defp convert_blank_nodes({s, %BlankNode{} = p, o}), do: {true, {s, to_string(p), o}}
defp convert_blank_nodes({s, p, %BlankNode{} = o}), do: {true, {s, p, to_string(o)}}
defp convert_blank_nodes({%BlankNode{} = s, %BlankNode{} = p, %BlankNode{} = o}), do: {true, {bnode_var(s), bnode_var(p), bnode_var(o)}}
defp convert_blank_nodes({s, %BlankNode{} = p, %BlankNode{} = o}), do: {true, {s, bnode_var(p), bnode_var(o)}}
defp convert_blank_nodes({%BlankNode{} = s, p, %BlankNode{} = o}), do: {true, {bnode_var(s), p, bnode_var(o)}}
defp convert_blank_nodes({%BlankNode{} = s, %BlankNode{} = p, o}), do: {true, {bnode_var(s), bnode_var(p), o}}
defp convert_blank_nodes({%BlankNode{} = s, p, o}), do: {true, {bnode_var(s), p, o}}
defp convert_blank_nodes({s, %BlankNode{} = p, o}), do: {true, {s, bnode_var(p), o}}
defp convert_blank_nodes({s, p, %BlankNode{} = o}), do: {true, {s, p, bnode_var(o)}}
defp convert_blank_nodes(triple_pattern), do: {false, triple_pattern}
defp bnode_var(bnode), do: bnode |> to_string() |> String.to_atom()
def postprocess(solutions, has_blank_nodes, opts) do
def postprocess(solutions, bgp, has_blank_nodes, opts) do
if has_blank_nodes and
Keyword.get(opts, :remove_bnode_query_variables, @default_remove_bnode_query_variables) do
Enum.map(solutions, &remove_blank_nodes/1)
bnode_vars = bgp |> bnodes() |> Enum.map(&bnode_var/1)
Enum.map(solutions, &(Map.drop(&1, bnode_vars)))
else
solutions
end
end
defp remove_blank_nodes(solution) do
solution
|> Enum.filter(fn
{@blank_node_prefix <> _, _} -> false
_ -> true
end)
|> Map.new
defp bnodes(%BGP{triple_patterns: triple_patterns}), do: bnodes(triple_patterns)
defp bnodes(triple_patterns) when is_list(triple_patterns) do
triple_patterns
|> Enum.reduce([], fn triple_pattern, vars -> bnodes(triple_pattern) ++ vars end)
|> Enum.uniq()
end
defp bnodes({%BlankNode{} = s, %BlankNode{} = p, %BlankNode{} = o}), do: [s, p, o]
defp bnodes({%BlankNode{} = s, %BlankNode{} = p, _}), do: [s, p]
defp bnodes({%BlankNode{} = s, _, %BlankNode{} = o}) , do: [s, o]
defp bnodes({_, %BlankNode{} = p, %BlankNode{} = o}), do: [p, o]
defp bnodes({%BlankNode{} = s, _, _}), do: [s]
defp bnodes({_, %BlankNode{} = p, _}), do: [p]
defp bnodes({_, _, %BlankNode{} = o}), do: [o]
defp bnodes(_), do: []
end

View file

@ -1,13 +1,15 @@
defmodule RDF.Query.BGP.QueryPlanner do
@moduledoc false
def query_plan(triple_patterns, solved \\ MapSet.new, plan \\ [])
alias RDF.Query.BGP
def query_plan(triple_patterns, solved \\ [], plan \\ [])
def query_plan([], _, plan), do: Enum.reverse(plan)
def query_plan(triple_patterns, solved, plan) do
[next_best | rest] = Enum.sort_by(triple_patterns, &triple_priority/1)
new_solved = MapSet.union(solved, variables(next_best))
new_solved = Enum.uniq(BGP.variables(next_best) ++ solved)
query_plan(
mark_solved_variables(rest, new_solved),
@ -15,33 +17,24 @@ defmodule RDF.Query.BGP.QueryPlanner do
[next_best | plan])
end
defp variables({v1, v2, v3}) when is_binary(v1) and is_binary(v2) and is_binary(v3), do: MapSet.new([v1, v2, v3])
defp variables({_, v2, v3}) when is_binary(v2) and is_binary(v3), do: MapSet.new([v2, v3])
defp variables({v1, _, v3}) when is_binary(v1) and is_binary(v3), do: MapSet.new([v1, v3])
defp variables({v1, v2, _}) when is_binary(v1) and is_binary(v2), do: MapSet.new([v1, v2])
defp variables({v1, _, _}) when is_binary(v1), do: MapSet.new([v1])
defp variables({_, v2, _}) when is_binary(v2), do: MapSet.new([v2])
defp variables({_, _, v3}) when is_binary(v3), do: MapSet.new([v3])
defp variables(_), do: MapSet.new()
defp triple_priority({v, v, v}), do: triple_priority({v, :p, :o})
defp triple_priority({v, v, o}), do: triple_priority({v, :p, o})
defp triple_priority({v, p, v}), do: triple_priority({v, p, :o})
defp triple_priority({s, v, v}), do: triple_priority({s, v, :o})
defp triple_priority({v, v, v}), do: triple_priority({v, "p", "o"})
defp triple_priority({v, v, o}), do: triple_priority({v, "p", o})
defp triple_priority({v, p, v}), do: triple_priority({v, p, "o"})
defp triple_priority({s, v, v}), do: triple_priority({s, v, "o"})
defp triple_priority({s, p, o}) do
{sp, pp, op} = {value_priority(s), value_priority(p), value_priority(o)}
<<(sp + pp + op) :: size(2), sp :: size(1), pp :: size(1), op :: size(1)>>
end
defp value_priority(value) when is_binary(value), do: 1
defp value_priority(_), do: 0
defp value_priority(value) when is_atom(value), do: 1
defp value_priority(_), do: 0
defp mark_solved_variables(triple_patterns, solved) do
Enum.map triple_patterns, fn {s, p, o} ->
{
(if is_binary(s) and MapSet.member?(solved, s), do: {s}, else: s),
(if is_binary(p) and MapSet.member?(solved, p), do: {p}, else: p),
(if is_binary(o) and MapSet.member?(solved, o), do: {o}, else: o)
(if is_atom(s) and s in solved, do: {s}, else: s),
(if is_atom(p) and p in solved, do: {p}, else: p),
(if is_atom(o) and o in solved, do: {o}, else: o)
}
end
end

View file

@ -11,13 +11,13 @@ defmodule RDF.Query.BGP.Simple do
def query(_, %BGP{triple_patterns: []}, _), do: [%{}] # https://www.w3.org/TR/sparql11-query/#emptyGroupPattern
def query(data, %BGP{triple_patterns: triple_patterns}, opts) do
{bnode_state, triple_patterns} =
{bnode_state, preprocessed_triple_patterns} =
BlankNodeHandler.preprocess(triple_patterns)
triple_patterns
preprocessed_triple_patterns
|> QueryPlanner.query_plan()
|> do_query(data)
|> BlankNodeHandler.postprocess(bnode_state, opts)
|> BlankNodeHandler.postprocess(triple_patterns, bnode_state, opts)
end
@impl RDF.Query.BGP.Matcher
@ -88,7 +88,7 @@ defmodule RDF.Query.BGP.Simple do
defp match(%Graph{descriptions: descriptions}, {subject_variable, _, _} = triple_pattern)
when is_binary(subject_variable) do
when is_atom(subject_variable) do
Enum.reduce(descriptions, [], fn ({subject, description}, acc) ->
case match(description, solve_variables(subject_variable, subject, triple_pattern)) do
nil -> acc
@ -108,7 +108,7 @@ defmodule RDF.Query.BGP.Simple do
end
defp match(%Description{predications: predications}, {_, variable, variable})
when is_binary(variable) do
when is_atom(variable) do
Enum.reduce(predications, [], fn ({predicate, objects}, solutions) ->
if Map.has_key?(objects, predicate) do
[%{variable => predicate} | solutions]
@ -119,7 +119,7 @@ defmodule RDF.Query.BGP.Simple do
end
defp match(%Description{predications: predications}, {_, predicate_variable, object_variable})
when is_binary(predicate_variable) and is_binary(object_variable) do
when is_atom(predicate_variable) and is_atom(object_variable) do
Enum.reduce(predications, [], fn ({predicate, objects}, solutions) ->
solutions ++
Enum.map(objects, fn {object, _} ->
@ -129,7 +129,7 @@ defmodule RDF.Query.BGP.Simple do
end
defp match(%Description{predications: predications},
{_, predicate_variable, object}) when is_binary(predicate_variable) do
{_, predicate_variable, object}) when is_atom(predicate_variable) do
Enum.reduce(predications, [], fn ({predicate, objects}, solutions) ->
if Map.has_key?(objects, object) do
[%{predicate_variable => predicate} | solutions]
@ -145,7 +145,7 @@ defmodule RDF.Query.BGP.Simple do
nil -> []
objects -> cond do
# object_or_variable is a variable
is_binary(object_or_variable) ->
is_atom(object_or_variable) ->
Enum.map(objects, fn {object, _} ->
%{object_or_variable => object}
end)

View file

@ -12,13 +12,13 @@ defmodule RDF.Query.BGP.Stream do
def query_stream(_, %BGP{triple_patterns: []}, _), do: stream([%{}]) # https://www.w3.org/TR/sparql11-query/#emptyGroupPattern
def query_stream(data, %BGP{triple_patterns: triple_patterns}, opts) do
{bnode_state, triple_patterns} =
{bnode_state, preprocessed_triple_patterns} =
BlankNodeHandler.preprocess(triple_patterns)
triple_patterns
preprocessed_triple_patterns
|> QueryPlanner.query_plan()
|> do_query(data)
|> BlankNodeHandler.postprocess(bnode_state, opts)
|> BlankNodeHandler.postprocess(triple_patterns, bnode_state, opts)
end
@impl RDF.Query.BGP.Matcher
@ -92,7 +92,7 @@ defmodule RDF.Query.BGP.Stream do
defp match(%Graph{descriptions: descriptions}, {subject_variable, _, _} = triple_pattern)
when is_binary(subject_variable) do
when is_atom(subject_variable) do
Stream.flat_map(descriptions, fn {subject, description} ->
case match(description, solve_variables(subject_variable, subject, triple_pattern)) do
nil -> []
@ -112,7 +112,7 @@ defmodule RDF.Query.BGP.Stream do
end
defp match(%Description{predications: predications}, {_, variable, variable})
when is_binary(variable) do
when is_atom(variable) do
matches =
Stream.filter(predications, fn {predicate, objects} -> Map.has_key?(objects, predicate) end)
@ -122,7 +122,7 @@ defmodule RDF.Query.BGP.Stream do
end
defp match(%Description{predications: predications}, {_, predicate_variable, object_variable})
when is_binary(predicate_variable) and is_binary(object_variable) do
when is_atom(predicate_variable) and is_atom(object_variable) do
Stream.flat_map(predications, fn {predicate, objects} ->
Stream.map(objects, fn {object, _} ->
%{predicate_variable => predicate, object_variable => object}
@ -131,7 +131,7 @@ defmodule RDF.Query.BGP.Stream do
end
defp match(%Description{predications: predications},
{_, predicate_variable, object}) when is_binary(predicate_variable) do
{_, predicate_variable, object}) when is_atom(predicate_variable) do
matches =
Stream.filter(predications, fn {_, objects} -> Map.has_key?(objects, object) end)
@ -146,7 +146,7 @@ defmodule RDF.Query.BGP.Stream do
nil -> nil
objects -> cond do
# object_or_variable is a variable
is_binary(object_or_variable) ->
is_atom(object_or_variable) ->
Stream.map(objects, fn {object, _} ->
%{object_or_variable => object}
end)

View file

@ -10,28 +10,28 @@ defmodule RDF.Query.BGP.QueryPlannerTest do
end
test "single" do
assert QueryPlanner.query_plan([{"a", "b", "c"}]) == [{"a", "b", "c"}]
assert QueryPlanner.query_plan([{:a, :b, :c}]) == [{:a, :b, :c}]
end
test "multiple connected" do
assert QueryPlanner.query_plan([
{"a", "b", "c"},
{"a", "d", ~L"foo"}
{:a, :b, :c},
{:a, :d, ~L"foo"}
]) == [
{"a", "d", ~L"foo"},
{{"a"}, "b", "c"}
{:a, :d, ~L"foo"},
{{:a}, :b, :c}
]
assert QueryPlanner.query_plan([
{"s", "p", "o"},
{"s2", "p2", "o2"},
{"s", "p", "o2"},
{"s4", "p4", ~L"foo"}
{:s, :p, :o},
{:s2, :p2, :o2},
{:s, :p, :o2},
{:s4, :p4, ~L"foo"}
]) == [
{"s4", "p4", ~L"foo"},
{"s", "p", "o"},
{{"s"}, {"p"}, "o2"},
{"s2", "p2", {"o2"}},
{:s4, :p4, ~L"foo"},
{:s, :p, :o},
{{:s}, {:p}, :o2},
{:s2, :p2, {:o2}},
]
end
end

View file

@ -21,28 +21,28 @@ defmodule RDF.Query.BGP.SimpleTest do
end
test "single {s ?p ?o}" do
assert query(@example_graph, bgp({EX.s1, "p", "o"})) ==
assert query(@example_graph, bgp({EX.s1, :p, :o})) ==
[
%{"p" => EX.p1, "o" => EX.o1},
%{"p" => EX.p2, "o" => EX.o2}
%{p: EX.p1, o: EX.o1},
%{p: EX.p2, o: EX.o2}
]
end
test "single {?s ?p o}" do
assert query(@example_graph, bgp({"s", "p", EX.o2})) ==
assert query(@example_graph, bgp({:s, :p, EX.o2})) ==
[
%{"s" => EX.s3, "p" => EX.p3},
%{"s" => EX.s1, "p" => EX.p2}
%{s: EX.s3, p: EX.p3},
%{s: EX.s1, p: EX.p2}
]
end
test "single {?s p ?o}" do
assert query(@example_graph, bgp({"s", EX.p3, "o"})) ==
[%{"s" => EX.s3, "o" => EX.o2}]
assert query(@example_graph, bgp({:s, EX.p3, :o})) ==
[%{s: EX.s3, o: EX.o2}]
end
test "with no solutions" do
assert query(Graph.new(), bgp({"a", "b", "c"})) == []
assert query(Graph.new(), bgp({:a, :b, :c})) == []
end
test "with solutions on one triple pattern but none on another one" do
@ -52,8 +52,8 @@ defmodule RDF.Query.BGP.SimpleTest do
])
assert query(example_graph, bgp [
{"a", EX.p1, ~L"unmatched" },
{"a", EX.y, EX.z}
{:a, EX.p1, ~L"unmatched" },
{:a, EX.y, EX.z}
]) == []
end
@ -64,8 +64,8 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.y, EX.x, EX.y}
])
assert query(example_graph, bgp({"a", "a", "b"})) ==
[%{"a" => EX.y, "b" => EX.x}]
assert query(example_graph, bgp({:a, :a, :b})) ==
[%{a: EX.y, b: EX.x}]
end
test "repeated variable: {?a ?b ?a}" do
@ -75,8 +75,8 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.y, EX.x, EX.y}
])
assert query(example_graph, bgp({"a", "b", "a"})) ==
[%{"a" => EX.y, "b" => EX.x}]
assert query(example_graph, bgp({:a, :b, :a})) ==
[%{a: EX.y, b: EX.x}]
end
test "repeated variable: {?b ?a ?a}" do
@ -86,8 +86,8 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.y, EX.x, EX.y}
])
assert query(example_graph, bgp({"b", "a", "a"})) ==
[%{"a" => EX.y, "b" => EX.x}]
assert query(example_graph, bgp({:b, :a, :a})) ==
[%{a: EX.y, b: EX.x}]
end
test "repeated variable: {?a ?a ?a}" do
@ -98,33 +98,33 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.y, EX.y, EX.y},
])
assert query(example_graph, bgp({"a", "a", "a"})) == [%{"a" => EX.y}]
assert query(example_graph, bgp({:a, :a, :a})) == [%{a: EX.y}]
end
test "two connected triple patterns with a match" do
assert query(@example_graph, bgp [
{EX.s1, "p", "o"},
{EX.s3, "p2", "o" }
{EX.s1, :p, :o},
{EX.s3, :p2, :o }
]) == [%{
"p" => EX.p2,
"p2" => EX.p3,
"o" => EX.o2
p: EX.p2,
p2: EX.p3,
o: EX.o2
}]
assert query(@example_graph, bgp [
{EX.s1, "p", "o1"},
{EX.s1, "p", "o2"}
{EX.s1, :p, :o1},
{EX.s1, :p, :o2}
]) ==
[
%{
"p" => EX.p1,
"o1" => EX.o1,
"o2" => EX.o1,
p: EX.p1,
o1: EX.o1,
o2: EX.o1,
},
%{
"p" => EX.p2,
"o1" => EX.o2,
"o2" => EX.o2,
p: EX.p2,
o1: EX.o2,
o2: EX.o2,
},
]
@ -135,9 +135,9 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.s3, EX.p3, EX.o1}
]),
bgp [
{EX.s1, EX.p1, "o"},
{EX.s3, "p", "o"}
]) == [%{"p" => EX.p3, "o" => EX.o1}]
{EX.s1, EX.p1, :o},
{EX.s3, :p, :o}
]) == [%{p: EX.p3, o: EX.o1}]
end
test "a triple pattern with dependent variables from separate triple patterns" do
@ -148,94 +148,94 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.s3, EX.p2, EX.o1}
]),
bgp [
{EX.s1, EX.p1, "o"},
{EX.s2, "p", EX.o2},
{"s", "p", "o"}
{EX.s1, EX.p1, :o},
{EX.s2, :p, EX.o2},
{:s, :p, :o}
]
) == [
%{
"s" => EX.s3,
"p" => EX.p2,
"o" => EX.o1,
s: EX.s3,
p: EX.p2,
o: EX.o1,
},
]
end
test "when no solutions" do
assert query(@example_graph, bgp({EX.s, EX.p, "o"})) == []
assert query(@example_graph, bgp({EX.s, EX.p, :o})) == []
end
test "multiple triple patterns with a constant unmatched triple has no solutions" do
assert query(@example_graph, bgp [
{EX.s1, "p", "o"},
{EX.s1, :p, :o},
{EX.s, EX.p, EX.o}
]) == []
end
test "independent triple patterns lead to cross-products" do
assert query(@example_graph, bgp [
{EX.s1, "p1", "o"},
{"s", "p2", EX.o2}
{EX.s1, :p1, :o},
{:s, :p2, EX.o2}
]) == [
%{
"p1" => EX.p1,
"o" => EX.o1,
"s" => EX.s3,
"p2" => EX.p3,
p1: EX.p1,
o: EX.o1,
s: EX.s3,
p2: EX.p3,
},
%{
"p1" => EX.p2,
"o" => EX.o2,
"s" => EX.s3,
"p2" => EX.p3,
p1: EX.p2,
o: EX.o2,
s: EX.s3,
p2: EX.p3,
},
%{
"p1" => EX.p1,
"o" => EX.o1,
"s" => EX.s1,
"p2" => EX.p2,
p1: EX.p1,
o: EX.o1,
s: EX.s1,
p2: EX.p2,
},
%{
"p1" => EX.p2,
"o" => EX.o2,
"s" => EX.s1,
"p2" => EX.p2,
p1: EX.p2,
o: EX.o2,
s: EX.s1,
p2: EX.p2,
},
]
end
test "blank nodes behave like variables, but don't appear in the solution" do
assert query(@example_graph, bgp [
{EX.s1, "p", RDF.bnode("o")},
{EX.s3, "p2", RDF.bnode("o")}
]) == [%{"p" => EX.p2, "p2" => EX.p3}]
{EX.s1, :p, RDF.bnode("o")},
{EX.s3, :p2, RDF.bnode("o")}
]) == [%{p: EX.p2, p2: EX.p3}]
end
test "cross-product with blank nodes" do
assert query(@example_graph, bgp [
{EX.s1, "p1", "o"},
{RDF.bnode("s"), "p2", EX.o2}
{EX.s1, :p1, :o},
{RDF.bnode("s"), :p2, EX.o2}
]) ==
[
%{
"p1" => EX.p1,
"o" => EX.o1,
"p2" => EX.p3,
p1: EX.p1,
o: EX.o1,
p2: EX.p3,
},
%{
"p1" => EX.p2,
"o" => EX.o2,
"p2" => EX.p3,
p1: EX.p2,
o: EX.o2,
p2: EX.p3,
},
%{
"p1" => EX.p1,
"o" => EX.o1,
"p2" => EX.p2,
p1: EX.p1,
o: EX.o1,
p2: EX.p2,
},
%{
"p1" => EX.p2,
"o" => EX.o2,
"p2" => EX.p2,
p1: EX.p2,
o: EX.o2,
p2: EX.p2,
},
]
end

View file

@ -22,27 +22,28 @@ defmodule RDF.Query.BGP.StreamTest do
end
test "single {s ?p ?o}" do
assert query(@example_graph, bgp({EX.s1, "p", "o"})) ==
[
%{"p" => EX.p1, "o" => EX.o1},
%{"p" => EX.p2, "o" => EX.o2}
]
assert query(@example_graph, bgp({EX.s1, :p, :o})) ==
[
%{p: EX.p1, o: EX.o1},
%{p: EX.p2, o: EX.o2}
]
end
test "single {?s ?p o}" do
assert query(@example_graph, bgp({"s", "p", EX.o2})) ==
[
%{"s" => EX.s1, "p" => EX.p2},
%{"s" => EX.s3, "p" => EX.p3},
]
assert query(@example_graph, bgp({:s, :p, EX.o2})) ==
[
%{s: EX.s1, p: EX.p2},
%{s: EX.s3, p: EX.p3},
]
end
test "single {?s p ?o}" do
assert query(@example_graph, bgp({"s", EX.p3, "o"})) == [%{"s" => EX.s3, "o" => EX.o2}]
assert query(@example_graph, bgp({:s, EX.p3, :o})) ==
[%{s: EX.s3, o: EX.o2}]
end
test "with no solutions" do
assert query(Graph.new(), bgp({"a", "b", "c"})) == []
assert query(Graph.new(), bgp({:a, :b, :c})) == []
end
test "with solutions on one triple pattern but none on another one" do
@ -52,8 +53,8 @@ defmodule RDF.Query.BGP.StreamTest do
])
assert query(example_graph, bgp [
{"a", EX.p1, ~L"unmatched" },
{"a", EX.y, EX.z}
{:a, EX.p1, ~L"unmatched" },
{:a, EX.y, EX.z}
]) == []
end
@ -64,8 +65,8 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.y, EX.x, EX.y}
])
assert query(example_graph, bgp({"a", "a", "b"})) ==
[%{"a" => EX.y, "b" => EX.x}]
assert query(example_graph, bgp({:a, :a, :b})) ==
[%{a: EX.y, b: EX.x}]
end
test "repeated variable: {?a ?b ?a}" do
@ -75,8 +76,8 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.y, EX.x, EX.y}
])
assert query(example_graph, bgp({"a", "b", "a"})) ==
[%{"a" => EX.y, "b" => EX.x}]
assert query(example_graph, bgp({:a, :b, :a})) ==
[%{a: EX.y, b: EX.x}]
end
test "repeated variable: {?b ?a ?a}" do
@ -86,8 +87,8 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.y, EX.x, EX.y}
])
assert query(example_graph, bgp({"b", "a", "a"})) ==
[%{"a" => EX.y, "b" => EX.x}]
assert query(example_graph, bgp({:b, :a, :a})) ==
[%{a: EX.y, b: EX.x}]
end
test "repeated variable: {?a ?a ?a}" do
@ -98,35 +99,35 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.y, EX.y, EX.y},
])
assert query(example_graph, bgp({"a", "a", "a"})) == [%{"a" => EX.y}]
assert query(example_graph, bgp({:a, :a, :a})) == [%{a: EX.y}]
end
test "two connected triple patterns with a match" do
assert query(@example_graph, bgp [
{EX.s1, "p", "o"},
{EX.s3, "p2", "o" }
{EX.s1, :p, :o},
{EX.s3, :p2, :o }
]) == [%{
"p" => EX.p2,
"p2" => EX.p3,
"o" => EX.o2
p: EX.p2,
p2: EX.p3,
o: EX.o2
}]
assert query(@example_graph, bgp [
{EX.s1, "p", "o1"},
{EX.s1, "p", "o2"}
{EX.s1, :p, :o1},
{EX.s1, :p, :o2}
]) ==
[
%{
"p" => EX.p1,
"o1" => EX.o1,
"o2" => EX.o1,
},
%{
"p" => EX.p2,
"o1" => EX.o2,
"o2" => EX.o2,
},
]
[
%{
p: EX.p1,
o1: EX.o1,
o2: EX.o1,
},
%{
p: EX.p2,
o1: EX.o2,
o2: EX.o2,
},
]
assert query(
Graph.new([
@ -135,9 +136,9 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.s3, EX.p3, EX.o1}
]),
bgp [
{EX.s1, EX.p1, "o"},
{EX.s3, "p", "o"}
]) == [%{"p" => EX.p3, "o" => EX.o1}]
{EX.s1, EX.p1, :o},
{EX.s3, :p, :o}
]) == [%{p: EX.p3, o: EX.o1}]
end
test "a triple pattern with dependent variables from separate triple patterns" do
@ -148,26 +149,26 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.s3, EX.p2, EX.o1}
]),
bgp [
{EX.s1, EX.p1, "o"},
{EX.s2, "p", EX.o2},
{"s", "p", "o"}
{EX.s1, EX.p1, :o},
{EX.s2, :p, EX.o2},
{:s, :p, :o}
]
) == [
%{
"s" => EX.s3,
"p" => EX.p2,
"o" => EX.o1,
s: EX.s3,
p: EX.p2,
o: EX.o1,
},
]
end
test "when no solutions" do
assert query(@example_graph, bgp({EX.s, EX.p, "o"})) == []
assert query(@example_graph, bgp({EX.s, EX.p, :o})) == []
end
test "multiple triple patterns with a constant unmatched triple has no solutions" do
assert query(@example_graph, bgp [
{EX.s1, "p", "o"},
{EX.s1, :p, :o},
{EX.s, EX.p, EX.o}
]) == []
end
@ -175,71 +176,71 @@ defmodule RDF.Query.BGP.StreamTest do
test "independent triple patterns lead to cross-products" do
assert MapSet.new(
query(@example_graph, bgp [
{EX.s1, "p1", "o"},
{"s", "p2", EX.o2}
{EX.s1, :p1, :o},
{:s, :p2, EX.o2}
])
) == MapSet.new([
%{
"p1" => EX.p1,
"o" => EX.o1,
"s" => EX.s3,
"p2" => EX.p3,
p1: EX.p1,
o: EX.o1,
s: EX.s3,
p2: EX.p3,
},
%{
"p1" => EX.p2,
"o" => EX.o2,
"s" => EX.s3,
"p2" => EX.p3,
p1: EX.p2,
o: EX.o2,
s: EX.s3,
p2: EX.p3,
},
%{
"p1" => EX.p1,
"o" => EX.o1,
"s" => EX.s1,
"p2" => EX.p2,
p1: EX.p1,
o: EX.o1,
s: EX.s1,
p2: EX.p2,
},
%{
"p1" => EX.p2,
"o" => EX.o2,
"s" => EX.s1,
"p2" => EX.p2,
p1: EX.p2,
o: EX.o2,
s: EX.s1,
p2: EX.p2,
},
])
end
test "blank nodes behave like variables, but don't appear in the solution" do
assert query(@example_graph, bgp [
{EX.s1, "p", RDF.bnode("o")},
{EX.s3, "p2", RDF.bnode("o")}
]) == [%{"p" => EX.p2, "p2" => EX.p3}]
{EX.s1, :p, RDF.bnode("o")},
{EX.s3, :p2, RDF.bnode("o")}
]) == [%{p: EX.p2, p2: EX.p3}]
end
test "cross-product with blank nodes" do
assert MapSet.new(
query(@example_graph, bgp [
{EX.s1, "p1", "o"},
{RDF.bnode("s"), "p2", EX.o2}
{EX.s1, :p1, :o},
{RDF.bnode("s"), :p2, EX.o2}
])
) ==
MapSet.new([
%{
"p1" => EX.p1,
"o" => EX.o1,
"p2" => EX.p3,
p1: EX.p1,
o: EX.o1,
p2: EX.p3,
},
%{
"p1" => EX.p2,
"o" => EX.o2,
"p2" => EX.p3,
p1: EX.p2,
o: EX.o2,
p2: EX.p3,
},
%{
"p1" => EX.p1,
"o" => EX.o1,
"p2" => EX.p2,
p1: EX.p1,
o: EX.o1,
p2: EX.p2,
},
%{
"p1" => EX.p2,
"o" => EX.o2,
"p2" => EX.p2,
p1: EX.p2,
o: EX.o2,
p2: EX.p2,
},
])
end