Add RDF-star support on the BGP query engine RDF.Query.BGP.Stream
This commit is contained in:
parent
711d1a4fd3
commit
b67db534dd
4 changed files with 484 additions and 222 deletions
143
lib/rdf/query/bgp/helper.ex
Normal file
143
lib/rdf/query/bgp/helper.ex
Normal file
|
@ -0,0 +1,143 @@
|
|||
defmodule RDF.Query.BGP.Helper do
|
||||
@moduledoc !"""
|
||||
Shared functions between the `RDF.Query.BGP.Simple` and `RDF.Query.BGP.Stream` engines.
|
||||
"""
|
||||
|
||||
import RDF.Guards
|
||||
|
||||
def solvable?(term) when is_tuple(term) and tuple_size(term) == 1, do: true
|
||||
def solvable?({s, p, o}), do: solvable?(p) or solvable?(s) or solvable?(o)
|
||||
def solvable?(_), do: false
|
||||
|
||||
def apply_solutions(triple_pattern, solutions) do
|
||||
if solver = solver(triple_pattern) do
|
||||
Stream.map(solutions, solver)
|
||||
else
|
||||
solutions
|
||||
end
|
||||
end
|
||||
|
||||
defp solver(triple_pattern) do
|
||||
if solver = solver_fun(triple_pattern) do
|
||||
&{&1, solver.(&1)}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({{s}, {p}, {o}}), do: &{&1[s], &1[p], &1[o]}
|
||||
defp solver_fun({{s}, p, {o}}), do: &{&1[s], p, &1[o]}
|
||||
|
||||
defp solver_fun({{s}, {p}, o}) do
|
||||
if o_solver = solver_fun(o) do
|
||||
&{&1[s], &1[p], o_solver.(&1)}
|
||||
else
|
||||
&{&1[s], &1[p], o}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({{s}, p, o}) do
|
||||
if o_solver = solver_fun(o) do
|
||||
&{&1[s], p, o_solver.(&1)}
|
||||
else
|
||||
&{&1[s], p, o}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({s, {p}, {o}}) do
|
||||
if s_solver = solver_fun(s) do
|
||||
&{s_solver.(&1), &1[p], &1[o]}
|
||||
else
|
||||
&{s, &1[p], &1[o]}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({s, p, {o}}) do
|
||||
if s_solver = solver_fun(s) do
|
||||
&{s_solver.(&1), p, &1[o]}
|
||||
else
|
||||
&{s, p, &1[o]}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({s, {p}, o}) do
|
||||
s_solver = solver_fun(s)
|
||||
o_solver = solver_fun(o)
|
||||
|
||||
cond do
|
||||
s_solver && o_solver -> &{s_solver.(&1), &1[p], o_solver.(&1)}
|
||||
s_solver -> &{s_solver.(&1), &1[p], o}
|
||||
o_solver -> &{s, &1[p], o_solver.(&1)}
|
||||
true -> &{s, &1[p], o}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({s, p, o}) do
|
||||
s_solver = solver_fun(s)
|
||||
o_solver = solver_fun(o)
|
||||
|
||||
cond do
|
||||
s_solver && o_solver -> &{s_solver.(&1), p, o_solver.(&1)}
|
||||
s_solver -> &{s_solver.(&1), p, o}
|
||||
o_solver -> &{s, p, o_solver.(&1)}
|
||||
true -> fn _ -> {s, p, o} end
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun(_), do: nil
|
||||
|
||||
def solve_variables(var, val, {s, p, o}),
|
||||
do: {solve_variables(var, val, s), solve_variables(var, val, p), solve_variables(var, val, o)}
|
||||
|
||||
def solve_variables(var, val, var), do: val
|
||||
def solve_variables(_, _, term), do: term
|
||||
|
||||
def solve_variables(bindings, pattern) do
|
||||
Enum.reduce(bindings, pattern, fn {var, val}, pattern ->
|
||||
solve_variables(var, val, pattern)
|
||||
end)
|
||||
end
|
||||
|
||||
def quoted_triple_with_variables?({s, p, o}) do
|
||||
is_atom(s) or is_atom(p) or is_atom(o) or
|
||||
quoted_triple_with_variables?(s) or
|
||||
quoted_triple_with_variables?(p) or
|
||||
quoted_triple_with_variables?(o)
|
||||
end
|
||||
|
||||
def quoted_triple_with_variables?(_), do: false
|
||||
|
||||
def match_triple(triple, triple), do: %{}
|
||||
def match_triple({s, p, o}, {var, p, o}) when is_atom(var), do: %{var => s}
|
||||
def match_triple({s, p, o}, {s, var, o}) when is_atom(var), do: %{var => p}
|
||||
def match_triple({s, p, o}, {s, p, var}) when is_atom(var), do: %{var => o}
|
||||
|
||||
def match_triple({s, p1, o1}, {triple_pattern, p2, o2}) when is_triple(triple_pattern) do
|
||||
if bindings = match_triple({"solved", p1, o1}, {"solved", p2, o2}) do
|
||||
if nested_bindings = match_triple(s, triple_pattern) do
|
||||
Map.merge(bindings, nested_bindings)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def match_triple({s1, p1, o}, {s2, p2, triple_pattern}) when is_triple(triple_pattern) do
|
||||
if bindings = match_triple({s1, p1, "solved"}, {s2, p2, "solved"}) do
|
||||
if nested_bindings = match_triple(o, triple_pattern) do
|
||||
Map.merge(bindings, nested_bindings)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def match_triple({s, p, o}, {var1, var2, o}) when is_atom(var1) and is_atom(var2),
|
||||
do: %{var1 => s, var2 => p}
|
||||
|
||||
def match_triple({s, p, o}, {var1, p, var2}) when is_atom(var1) and is_atom(var2),
|
||||
do: %{var1 => s, var2 => o}
|
||||
|
||||
def match_triple({s, p, o}, {s, var1, var2}) when is_atom(var1) and is_atom(var2),
|
||||
do: %{var1 => p, var2 => o}
|
||||
|
||||
def match_triple({s, p, o}, {var1, var2, var3})
|
||||
when is_atom(var1) and is_atom(var2) and is_atom(var3),
|
||||
do: %{var1 => s, var2 => p, var3 => o}
|
||||
|
||||
def match_triple(_, _), do: nil
|
||||
end
|
|
@ -8,6 +8,7 @@ defmodule RDF.Query.BGP.Simple do
|
|||
alias RDF.{Graph, Description}
|
||||
|
||||
import RDF.Guards
|
||||
import RDF.Query.BGP.Helper
|
||||
|
||||
@impl RDF.Query.BGP.Matcher
|
||||
def execute(bgp, graph, opts \\ [])
|
||||
|
@ -58,81 +59,6 @@ defmodule RDF.Query.BGP.Simple do
|
|||
end
|
||||
end
|
||||
|
||||
defp apply_solutions(triple_pattern, solutions) do
|
||||
if solver = solver(triple_pattern) do
|
||||
Stream.map(solutions, solver)
|
||||
else
|
||||
solutions
|
||||
end
|
||||
end
|
||||
|
||||
defp solver(triple_pattern) do
|
||||
if solver = solver_fun(triple_pattern) do
|
||||
&{&1, solver.(&1)}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({{s}, {p}, {o}}), do: &{&1[s], &1[p], &1[o]}
|
||||
defp solver_fun({{s}, p, {o}}), do: &{&1[s], p, &1[o]}
|
||||
|
||||
defp solver_fun({{s}, {p}, o}) do
|
||||
if o_solver = solver_fun(o) do
|
||||
&{&1[s], &1[p], o_solver.(&1)}
|
||||
else
|
||||
&{&1[s], &1[p], o}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({{s}, p, o}) do
|
||||
if o_solver = solver_fun(o) do
|
||||
&{&1[s], p, o_solver.(&1)}
|
||||
else
|
||||
&{&1[s], p, o}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({s, {p}, {o}}) do
|
||||
if s_solver = solver_fun(s) do
|
||||
&{s_solver.(&1), &1[p], &1[o]}
|
||||
else
|
||||
&{s, &1[p], &1[o]}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({s, p, {o}}) do
|
||||
if s_solver = solver_fun(s) do
|
||||
&{s_solver.(&1), p, &1[o]}
|
||||
else
|
||||
&{s, p, &1[o]}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({s, {p}, o}) do
|
||||
s_solver = solver_fun(s)
|
||||
o_solver = solver_fun(o)
|
||||
|
||||
cond do
|
||||
s_solver && o_solver -> &{s_solver.(&1), &1[p], o_solver.(&1)}
|
||||
s_solver -> &{s_solver.(&1), &1[p], o}
|
||||
o_solver -> &{s, &1[p], o_solver.(&1)}
|
||||
true -> &{s, &1[p], o}
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun({s, p, o}) do
|
||||
s_solver = solver_fun(s)
|
||||
o_solver = solver_fun(o)
|
||||
|
||||
cond do
|
||||
s_solver && o_solver -> &{s_solver.(&1), p, o_solver.(&1)}
|
||||
s_solver -> &{s_solver.(&1), p, o}
|
||||
o_solver -> &{s, p, o_solver.(&1)}
|
||||
true -> fn _ -> {s, p, o} end
|
||||
end
|
||||
end
|
||||
|
||||
defp solver_fun(_), do: nil
|
||||
|
||||
defp merging_match({dependent_solution, triple_pattern}, graph) do
|
||||
case match(graph, triple_pattern) do
|
||||
nil ->
|
||||
|
@ -262,65 +188,4 @@ defmodule RDF.Query.BGP.Simple do
|
|||
acc
|
||||
end)
|
||||
end
|
||||
|
||||
defp match_triple(triple, triple), do: %{}
|
||||
defp match_triple({s, p, o}, {var, p, o}) when is_atom(var), do: %{var => s}
|
||||
defp match_triple({s, p, o}, {s, var, o}) when is_atom(var), do: %{var => p}
|
||||
defp match_triple({s, p, o}, {s, p, var}) when is_atom(var), do: %{var => o}
|
||||
|
||||
defp match_triple({s, p1, o1}, {triple_pattern, p2, o2}) when is_triple(triple_pattern) do
|
||||
if bindings = match_triple({"solved", p1, o1}, {"solved", p2, o2}) do
|
||||
if nested_bindings = match_triple(s, triple_pattern) do
|
||||
Map.merge(bindings, nested_bindings)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp match_triple({s1, p1, o}, {s2, p2, triple_pattern}) when is_triple(triple_pattern) do
|
||||
if bindings = match_triple({s1, p1, "solved"}, {s2, p2, "solved"}) do
|
||||
if nested_bindings = match_triple(o, triple_pattern) do
|
||||
Map.merge(bindings, nested_bindings)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp match_triple({s, p, o}, {var1, var2, o}) when is_atom(var1) and is_atom(var2),
|
||||
do: %{var1 => s, var2 => p}
|
||||
|
||||
defp match_triple({s, p, o}, {var1, p, var2}) when is_atom(var1) and is_atom(var2),
|
||||
do: %{var1 => s, var2 => o}
|
||||
|
||||
defp match_triple({s, p, o}, {s, var1, var2}) when is_atom(var1) and is_atom(var2),
|
||||
do: %{var1 => p, var2 => o}
|
||||
|
||||
defp match_triple({s, p, o}, {var1, var2, var3})
|
||||
when is_atom(var1) and is_atom(var2) and is_atom(var3),
|
||||
do: %{var1 => s, var2 => p, var3 => o}
|
||||
|
||||
defp match_triple(_, _), do: nil
|
||||
|
||||
defp solvable?(term) when is_tuple(term) and tuple_size(term) == 1, do: true
|
||||
defp solvable?({s, p, o}), do: solvable?(p) or solvable?(s) or solvable?(o)
|
||||
defp solvable?(_), do: false
|
||||
|
||||
defp quoted_triple_with_variables?({s, p, o}) do
|
||||
is_atom(s) or is_atom(p) or is_atom(o) or
|
||||
quoted_triple_with_variables?(s) or
|
||||
quoted_triple_with_variables?(p) or
|
||||
quoted_triple_with_variables?(o)
|
||||
end
|
||||
|
||||
defp quoted_triple_with_variables?(_), do: false
|
||||
|
||||
defp solve_variables(var, val, {s, p, o}),
|
||||
do: {solve_variables(var, val, s), solve_variables(var, val, p), solve_variables(var, val, o)}
|
||||
|
||||
defp solve_variables(var, val, var), do: val
|
||||
defp solve_variables(_, _, term), do: term
|
||||
|
||||
defp solve_variables(bindings, pattern) do
|
||||
Enum.reduce(bindings, pattern, fn {var, val}, pattern ->
|
||||
solve_variables(var, val, pattern)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,9 @@ defmodule RDF.Query.BGP.Stream do
|
|||
alias RDF.Query.BGP.{QueryPlanner, BlankNodeHandler}
|
||||
alias RDF.{Graph, Description}
|
||||
|
||||
import RDF.Query.BGP.Helper
|
||||
import RDF.Guards
|
||||
|
||||
@impl RDF.Query.BGP.Matcher
|
||||
def stream(bgp, graph, opts \\ [])
|
||||
|
||||
|
@ -32,10 +35,6 @@ defmodule RDF.Query.BGP.Stream do
|
|||
do_execute(remaining, graph, match(graph, triple_pattern))
|
||||
end
|
||||
|
||||
# CAUTION: Careful with using Enum.empty?/1 on the solution stream!! The first match must be
|
||||
# searched for every call in the query loop repeatedly then, which can have dramatic effects potentially.
|
||||
# Only use it very close to the data (in the match/1 functions operating on data directly).
|
||||
|
||||
defp do_execute(triple_patterns, graph, solutions)
|
||||
|
||||
defp do_execute(_, _, nil), do: to_stream([])
|
||||
|
@ -46,50 +45,24 @@ defmodule RDF.Query.BGP.Stream do
|
|||
do_execute(remaining, graph, match_with_solutions(graph, triple_pattern, solutions))
|
||||
end
|
||||
|
||||
defp match_with_solutions(graph, {s, p, o} = triple_pattern, existing_solutions)
|
||||
when is_tuple(s) or is_tuple(p) or is_tuple(o) do
|
||||
triple_pattern
|
||||
|> apply_solutions(existing_solutions)
|
||||
|> Stream.flat_map(&merging_match(&1, graph))
|
||||
end
|
||||
|
||||
defp match_with_solutions(graph, triple_pattern, existing_solutions) do
|
||||
if solutions = match(graph, triple_pattern) do
|
||||
Stream.flat_map(solutions, fn solution ->
|
||||
Stream.map(existing_solutions, &Map.merge(solution, &1))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp apply_solutions(triple_pattern, solutions) do
|
||||
apply_solution =
|
||||
case triple_pattern do
|
||||
{{s}, {p}, {o}} -> fn solution -> {solution, {solution[s], solution[p], solution[o]}} end
|
||||
{{s}, {p}, o} -> fn solution -> {solution, {solution[s], solution[p], o}} end
|
||||
{{s}, p, {o}} -> fn solution -> {solution, {solution[s], p, solution[o]}} end
|
||||
{{s}, p, o} -> fn solution -> {solution, {solution[s], p, o}} end
|
||||
{s, {p}, {o}} -> fn solution -> {solution, {s, solution[p], solution[o]}} end
|
||||
{s, {p}, o} -> fn solution -> {solution, {s, solution[p], o}} end
|
||||
{s, p, {o}} -> fn solution -> {solution, {s, p, solution[o]}} end
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
if apply_solution do
|
||||
Stream.map(solutions, apply_solution)
|
||||
defp match_with_solutions(graph, {s, p, o} = triple_pattern, existing_solutions) do
|
||||
if solvable?(p) or solvable?(s) or solvable?(o) do
|
||||
triple_pattern
|
||||
|> apply_solutions(existing_solutions)
|
||||
|> Stream.flat_map(&merging_match(&1, graph))
|
||||
else
|
||||
solutions
|
||||
if solutions = match(graph, triple_pattern) do
|
||||
Stream.flat_map(solutions, fn solution ->
|
||||
Stream.map(existing_solutions, &Map.merge(solution, &1))
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp merging_match({dependent_solution, triple_pattern}, graph) do
|
||||
case match(graph, triple_pattern) do
|
||||
nil ->
|
||||
[]
|
||||
|
||||
solutions ->
|
||||
Stream.map(solutions, fn solution ->
|
||||
Map.merge(dependent_solution, solution)
|
||||
end)
|
||||
nil -> []
|
||||
solutions -> Stream.map(solutions, &Map.merge(dependent_solution, &1))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -97,32 +70,39 @@ defmodule RDF.Query.BGP.Stream 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 ->
|
||||
[]
|
||||
|
||||
solutions ->
|
||||
Stream.map(solutions, fn solution ->
|
||||
Map.put(solution, subject_variable, subject)
|
||||
end)
|
||||
nil -> []
|
||||
solutions -> Stream.map(solutions, &Map.put(&1, subject_variable, subject))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp match(%Graph{} = graph, {subject, _, _} = triple_pattern) do
|
||||
case graph[subject] do
|
||||
nil -> nil
|
||||
description -> match(description, triple_pattern)
|
||||
if quoted_triple_with_variables?(subject) do
|
||||
graph
|
||||
|> matching_subject_triples(subject)
|
||||
|> Stream.flat_map(fn {description, subject_solutions} ->
|
||||
case match(description, solve_variables(subject_solutions, triple_pattern)) do
|
||||
nil -> []
|
||||
solutions -> Stream.map(solutions, &Map.merge(&1, subject_solutions))
|
||||
end
|
||||
end)
|
||||
else
|
||||
case graph[subject] do
|
||||
nil -> nil
|
||||
description -> match(description, triple_pattern)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp match(%Description{predications: predications}, {_, variable, variable})
|
||||
when is_atom(variable) do
|
||||
matches =
|
||||
Stream.filter(predications, fn {predicate, objects} -> Map.has_key?(objects, predicate) end)
|
||||
|
||||
unless Enum.empty?(matches) do
|
||||
Stream.map(matches, fn {predicate, _} -> %{variable => predicate} end)
|
||||
end
|
||||
Stream.flat_map(predications, fn {predicate, objects} ->
|
||||
if Map.has_key?(objects, predicate) do
|
||||
[%{variable => predicate}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp match(%Description{predications: predications}, {_, predicate_variable, object_variable})
|
||||
|
@ -134,33 +114,35 @@ defmodule RDF.Query.BGP.Stream do
|
|||
end)
|
||||
end
|
||||
|
||||
defp match(
|
||||
%Description{predications: predications},
|
||||
{_, predicate_variable, object}
|
||||
)
|
||||
defp match(%Description{predications: predications}, {_, predicate_variable, object})
|
||||
when is_atom(predicate_variable) do
|
||||
matches = Stream.filter(predications, fn {_, objects} -> Map.has_key?(objects, object) end)
|
||||
|
||||
unless Enum.empty?(matches) do
|
||||
Stream.map(matches, fn {predicate, _} -> %{predicate_variable => predicate} end)
|
||||
if quoted_triple_with_variables?(object) do
|
||||
Stream.flat_map(predications, fn {predicate, objects} ->
|
||||
objects
|
||||
|> matching_object_triples(object)
|
||||
|> Stream.map(&Map.put(&1, predicate_variable, predicate))
|
||||
end)
|
||||
else
|
||||
Stream.flat_map(predications, fn {predicate, objects} ->
|
||||
if Map.has_key?(objects, object) do
|
||||
[%{predicate_variable => predicate}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp match(
|
||||
%Description{predications: predications},
|
||||
{_, predicate, object_or_variable}
|
||||
) do
|
||||
case predications[predicate] do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
objects ->
|
||||
defp match(%Description{predications: predications}, {_, predicate, object_or_variable}) do
|
||||
if objects = predications[predicate] do
|
||||
if quoted_triple_with_variables?(object_or_variable) do
|
||||
matching_object_triples(objects, object_or_variable)
|
||||
matching_object_triples(objects, object_or_variable)
|
||||
else
|
||||
cond do
|
||||
# object_or_variable is a variable
|
||||
is_atom(object_or_variable) ->
|
||||
Stream.map(objects, fn {object, _} ->
|
||||
%{object_or_variable => object}
|
||||
end)
|
||||
Stream.map(objects, fn {object, _} -> %{object_or_variable => object} end)
|
||||
|
||||
# object_or_variable is a object
|
||||
Map.has_key?(objects, object_or_variable) ->
|
||||
|
@ -170,17 +152,29 @@ defmodule RDF.Query.BGP.Stream do
|
|||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp solve_variables(var, val, {var, var, var}), do: {val, val, val}
|
||||
defp solve_variables(var, val, {s, var, var}), do: {s, val, val}
|
||||
defp solve_variables(var, val, {var, p, var}), do: {val, p, val}
|
||||
defp solve_variables(var, val, {var, var, o}), do: {val, val, o}
|
||||
defp solve_variables(var, val, {var, p, o}), do: {val, p, o}
|
||||
defp solve_variables(var, val, {s, var, o}), do: {s, val, o}
|
||||
defp solve_variables(var, val, {s, p, var}), do: {s, p, val}
|
||||
defp solve_variables(_, _, pattern), do: pattern
|
||||
defp matching_subject_triples(graph, triple_pattern) do
|
||||
Stream.flat_map(graph.descriptions, fn
|
||||
{subject, description} when is_triple(subject) ->
|
||||
case match_triple(subject, triple_pattern) do
|
||||
nil -> []
|
||||
solutions -> [{description, solutions}]
|
||||
end
|
||||
|
||||
_ ->
|
||||
[]
|
||||
end)
|
||||
end
|
||||
|
||||
defp matching_object_triples(objects, triple_pattern) do
|
||||
Stream.flat_map(objects, fn
|
||||
{object, _} when is_triple(object) -> match_triple(object, triple_pattern) |> List.wrap()
|
||||
_ -> []
|
||||
end)
|
||||
end
|
||||
|
||||
defp to_stream(enum), do: Stream.into(enum, [])
|
||||
end
|
||||
|
|
260
test/unit/query/bgp/stream_star_test.exs
Normal file
260
test/unit/query/bgp/stream_star_test.exs
Normal file
|
@ -0,0 +1,260 @@
|
|||
defmodule RDF.Query.BGP.StreamStarTest do
|
||||
use RDF.Query.Test.Case
|
||||
|
||||
import RDF.Query.BGP.Stream, only: [execute: 2]
|
||||
|
||||
@example_graph Graph.new([
|
||||
{{EX.qs1(), EX.qp(), EX.qo1()}, EX.p1(), EX.o1()},
|
||||
{{EX.qs1(), EX.qp(), EX.qo1()}, EX.p2(), {EX.qs2(), EX.qp(), EX.qo2()}},
|
||||
{EX.s3(), EX.p3(), {EX.qs2(), EX.qp(), EX.qo2()}}
|
||||
])
|
||||
|
||||
test "quoted triples in results" do
|
||||
assert bgp_struct({{EX.qs1(), EX.qp(), EX.qo1()}, :p, :o}) |> execute(@example_graph) ==
|
||||
[
|
||||
%{p: EX.p1(), o: EX.o1()},
|
||||
%{p: EX.p2(), o: {EX.qs2(), EX.qp(), EX.qo2()}}
|
||||
]
|
||||
|
||||
assert bgp_struct({:s, :p, {EX.qs2(), EX.qp(), EX.qo2()}}) |> execute(@example_graph) ==
|
||||
[
|
||||
%{s: {EX.qs1(), EX.qp(), EX.qo1()}, p: EX.p2()},
|
||||
%{s: EX.s3(), p: EX.p3()}
|
||||
]
|
||||
|
||||
assert bgp_struct({:s, EX.p2(), :o}) |> execute(@example_graph) ==
|
||||
[%{s: {EX.qs1(), EX.qp(), EX.qo1()}, o: {EX.qs2(), EX.qp(), EX.qo2()}}]
|
||||
end
|
||||
|
||||
test "connected triple patterns with quoted triples" do
|
||||
assert bgp_struct([
|
||||
{EX.s(), EX.p(), :o},
|
||||
{{EX.qs(), EX.qp(), EX.qo()}, :p, :o}
|
||||
])
|
||||
|> execute(
|
||||
Graph.new([
|
||||
{EX.s(), EX.p(), EX.o()},
|
||||
{{EX.qs(), EX.qp(), EX.qo()}, EX.p2(), EX.o()}
|
||||
])
|
||||
) == [
|
||||
%{
|
||||
p: EX.p2(),
|
||||
o: EX.o()
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "triple patterns connected via a shared quoted triple" do
|
||||
assert bgp_struct([
|
||||
{:s, EX.p1(), EX.o1()},
|
||||
{:s, EX.p2(), {EX.qs2(), EX.qp(), EX.qo2()}}
|
||||
])
|
||||
|> execute(@example_graph) ==
|
||||
[%{s: {EX.qs1(), EX.qp(), EX.qo1()}}]
|
||||
end
|
||||
|
||||
test "variables in quoted triples on subject position" do
|
||||
assert bgp_struct({{:s, EX.qp(), EX.qo1()}, EX.p1(), EX.o1()}) |> execute(@example_graph) ==
|
||||
[%{s: EX.qs1()}]
|
||||
|
||||
assert bgp_struct({{EX.qs1(), :p, EX.qo1()}, EX.p1(), EX.o1()}) |> execute(@example_graph) ==
|
||||
[%{p: EX.qp()}]
|
||||
|
||||
assert bgp_struct({{EX.qs1(), EX.qp(), :o}, EX.p1(), EX.o1()}) |> execute(@example_graph) ==
|
||||
[%{o: EX.qo1()}]
|
||||
|
||||
assert bgp_struct({{:s, :p, EX.qo1()}, EX.p1(), EX.o1()}) |> execute(@example_graph) ==
|
||||
[%{s: EX.qs1(), p: EX.qp()}]
|
||||
|
||||
assert bgp_struct({{:s, EX.qp(), :o}, EX.p1(), EX.o1()}) |> execute(@example_graph) ==
|
||||
[%{s: EX.qs1(), o: EX.qo1()}]
|
||||
|
||||
assert bgp_struct({{EX.qs1(), :p, :o}, EX.p1(), EX.o1()}) |> execute(@example_graph) ==
|
||||
[%{p: EX.qp(), o: EX.qo1()}]
|
||||
|
||||
assert bgp_struct({{:s, :p, :o}, EX.p1(), EX.o1()}) |> execute(@example_graph) ==
|
||||
[%{s: EX.qs1(), p: EX.qp(), o: EX.qo1()}]
|
||||
end
|
||||
|
||||
test "variables in quoted triples on object position" do
|
||||
assert bgp_struct({EX.s3(), EX.p3(), {:s, EX.qp(), EX.qo2()}}) |> execute(@example_graph) ==
|
||||
[%{s: EX.qs2()}]
|
||||
|
||||
assert bgp_struct({EX.s3(), EX.p3(), {EX.qs2(), :p, EX.qo2()}}) |> execute(@example_graph) ==
|
||||
[%{p: EX.qp()}]
|
||||
|
||||
assert bgp_struct({EX.s3(), EX.p3(), {EX.qs2(), EX.qp(), :o}}) |> execute(@example_graph) ==
|
||||
[%{o: EX.qo2()}]
|
||||
|
||||
assert bgp_struct({EX.s3(), EX.p3(), {:s, :p, EX.qo2()}}) |> execute(@example_graph) == [
|
||||
%{s: EX.qs2(), p: EX.qp()}
|
||||
]
|
||||
|
||||
assert bgp_struct({EX.s3(), EX.p3(), {:s, EX.qp(), :o}}) |> execute(@example_graph) == [
|
||||
%{s: EX.qs2(), o: EX.qo2()}
|
||||
]
|
||||
|
||||
assert bgp_struct({EX.s3(), EX.p3(), {EX.qs2(), :p, :o}}) |> execute(@example_graph) == [
|
||||
%{p: EX.qp(), o: EX.qo2()}
|
||||
]
|
||||
|
||||
assert bgp_struct({EX.s3(), EX.p3(), {:s, :p, :o}}) |> execute(@example_graph) == [
|
||||
%{s: EX.qs2(), p: EX.qp(), o: EX.qo2()}
|
||||
]
|
||||
|
||||
# when the outer predicate is a variable
|
||||
assert bgp_struct({EX.s3(), :p, {:qs, EX.qp(), EX.qo2()}}) |> execute(@example_graph) ==
|
||||
[%{qs: EX.qs2(), p: EX.p3()}]
|
||||
|
||||
assert bgp_struct({EX.s3(), :p, {EX.qs2(), :qp, :qo}}) |> execute(@example_graph) == [
|
||||
%{qp: EX.qp(), qo: EX.qo2(), p: EX.p3()}
|
||||
]
|
||||
end
|
||||
|
||||
test "variables in quoted triples on subject and object position" do
|
||||
assert bgp_struct({{:s1, EX.qp(), EX.qo1()}, EX.p2(), {:s2, EX.qp(), EX.qo2()}})
|
||||
|> execute(@example_graph) ==
|
||||
[%{s1: EX.qs1(), s2: EX.qs2()}]
|
||||
|
||||
assert bgp_struct({{:s1, :p, EX.qo1()}, EX.p2(), {:s2, :p, EX.qo2()}})
|
||||
|> execute(@example_graph) ==
|
||||
[%{s1: EX.qs1(), s2: EX.qs2(), p: EX.qp()}]
|
||||
|
||||
assert bgp_struct({{:s1, :p, :o1}, EX.p2(), {:s2, :p, :o2}})
|
||||
|> execute(@example_graph) ==
|
||||
[%{s1: EX.qs1(), o1: EX.qo1(), s2: EX.qs2(), o2: EX.qo2(), p: EX.qp()}]
|
||||
|
||||
assert bgp_struct({{:s, EX.qp(), EX.qo1()}, EX.p2(), {:s, EX.qp(), EX.qo2()}})
|
||||
|> execute(@example_graph) ==
|
||||
[]
|
||||
|
||||
assert bgp_struct({{:s1, :p, EX.qo1()}, :p, {:s2, EX.qp(), EX.qo2()}})
|
||||
|> execute(@example_graph) ==
|
||||
[]
|
||||
end
|
||||
|
||||
test "triple patterns with interdependent variables" do
|
||||
assert bgp_struct([
|
||||
{{:qs1, :qp, EX.qo1()}, EX.p1(), EX.o1()},
|
||||
{:s, :p, {:qs2, :qp, EX.qo2()}}
|
||||
])
|
||||
|> execute(@example_graph) ==
|
||||
[
|
||||
%{
|
||||
qs1: EX.qs1(),
|
||||
qs2: EX.qs2(),
|
||||
qp: EX.qp(),
|
||||
s: {EX.qs1(), EX.qp(), EX.qo1()},
|
||||
p: EX.p2()
|
||||
},
|
||||
%{qs1: EX.qs1(), qs2: EX.qs2(), qp: EX.qp(), s: EX.s3(), p: EX.p3()}
|
||||
]
|
||||
|
||||
assert bgp_struct([
|
||||
{{:qs1, :qp, EX.qo1()}, :p, :o},
|
||||
{EX.s3(), EX.p3(), {:qs2, :qp, EX.qo2()}}
|
||||
])
|
||||
|> execute(@example_graph) ==
|
||||
[
|
||||
%{qs1: EX.qs1(), qs2: EX.qs2(), qp: EX.qp(), o: EX.o1(), p: EX.p1()},
|
||||
%{
|
||||
qs1: EX.qs1(),
|
||||
qs2: EX.qs2(),
|
||||
qp: EX.qp(),
|
||||
o: {EX.qs2(), EX.qp(), EX.qo2()},
|
||||
p: EX.p2()
|
||||
}
|
||||
]
|
||||
|
||||
assert bgp_struct([
|
||||
{{EX.qs1(), EX.qp(), :qo1}, EX.p1(), :o},
|
||||
{{EX.qs1(), EX.qp(), :qo1}, EX.p2(), {:qs2, EX.qp(), EX.qo2()}},
|
||||
{:s, EX.p3(), {:qs2, EX.qp(), EX.qo2()}}
|
||||
])
|
||||
|> execute(@example_graph) ==
|
||||
[
|
||||
%{
|
||||
s: EX.s3(),
|
||||
o: EX.o1(),
|
||||
qs2: EX.qs2(),
|
||||
qo1: EX.qo1()
|
||||
}
|
||||
]
|
||||
|
||||
assert bgp_struct([
|
||||
{:qt, EX.p1(), :o},
|
||||
{:qt, EX.p2(), {:qs2, EX.qp(), EX.qo2()}},
|
||||
{:s, EX.p3(), {:qs2, EX.qp(), EX.qo2()}}
|
||||
])
|
||||
|> execute(@example_graph) ==
|
||||
[
|
||||
%{
|
||||
s: EX.s3(),
|
||||
o: EX.o1(),
|
||||
qs2: EX.qs2(),
|
||||
qt: {EX.qs1(), EX.qp(), EX.qo1()}
|
||||
}
|
||||
]
|
||||
|
||||
assert bgp_struct([
|
||||
{:qt, EX.p1(), :o},
|
||||
{:qt, EX.p2(), {:qs2, EX.qp(), EX.qo2()}},
|
||||
{:s, EX.p3(), {:qs2, EX.qp(), EX.qo2()}},
|
||||
{
|
||||
{{:qs3, EX.b(), ~B"c"}, EX.d(), :qo3},
|
||||
EX.f(),
|
||||
{{{EX.g(), EX.h(), {:s, EX.j(), ~L"k"}}, EX.m(), EX.n()}, EX.o(), EX.p()}
|
||||
}
|
||||
])
|
||||
|> execute(
|
||||
Graph.add(@example_graph, {
|
||||
{{EX.a(), EX.b(), ~B"c"}, EX.d(), EX.e()},
|
||||
EX.f(),
|
||||
{{{EX.g(), EX.h(), {EX.s3(), EX.j(), ~L"k"}}, EX.m(), EX.n()}, EX.o(), EX.p()}
|
||||
})
|
||||
) ==
|
||||
[
|
||||
%{
|
||||
s: EX.s3(),
|
||||
o: EX.o1(),
|
||||
qs2: EX.qs2(),
|
||||
qs3: EX.a(),
|
||||
qo3: EX.e(),
|
||||
qt: {EX.qs1(), EX.qp(), EX.qo1()}
|
||||
}
|
||||
]
|
||||
|
||||
{
|
||||
{{:a, :b, ~B"c"}, :d, :e},
|
||||
:f,
|
||||
{{{:g, :h, {EX.s3(), :j, ~L"k"}}, :m, :n}, :o, :p}
|
||||
}
|
||||
end
|
||||
|
||||
test "blank nodes in quoted triple patterns" do
|
||||
assert bgp_struct({{:s, EX.qp(), ~B"o"}, EX.p1(), EX.o1()})
|
||||
|> execute(@example_graph) ==
|
||||
[%{s: EX.qs1()}]
|
||||
|
||||
assert bgp_struct({:s, EX.p3(), {~B"s", ~B"p", ~B"o"}})
|
||||
|> execute(@example_graph) ==
|
||||
[%{s: EX.s3()}]
|
||||
|
||||
assert bgp_struct([
|
||||
{~B"s", EX.p3(), ~B"quoted triple"},
|
||||
{
|
||||
{{EX.a(), EX.b(), ~B"c"}, EX.d(), EX.e()},
|
||||
EX.f(),
|
||||
{{{EX.g(), EX.h(), {~B"s", EX.j(), ~L"k"}}, EX.m(), :n}, EX.o(), EX.p()}
|
||||
}
|
||||
])
|
||||
|> execute(
|
||||
Graph.add(@example_graph, {
|
||||
{{EX.a(), EX.b(), ~B"c"}, EX.d(), EX.e()},
|
||||
EX.f(),
|
||||
{{{EX.g(), EX.h(), {EX.s3(), EX.j(), ~L"k"}}, EX.m(), EX.n()}, EX.o(), EX.p()}
|
||||
})
|
||||
) ==
|
||||
[%{n: EX.n()}]
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue