Add RDF.Query.BGP struct

This commit is contained in:
Marcel Otto 2020-06-08 11:36:22 +02:00
parent 2fca23209b
commit 9cd4478574
6 changed files with 89 additions and 56 deletions

14
lib/rdf/query/bgp.ex Normal file
View file

@ -0,0 +1,14 @@
defmodule RDF.Query.BGP do
@enforce_keys [:triple_patterns]
defstruct [:triple_patterns]
@type variable :: String.t
@type triple_pattern :: {
subject :: variable | RDF.Term.t,
predicate :: variable | RDF.Term.t,
object :: variable | RDF.Term.t
}
@type triple_patterns :: list(triple_pattern)
@type t :: %__MODULE__{triple_patterns: triple_patterns}
end

View file

@ -3,19 +3,14 @@ defmodule RDF.Query.BGP.Matcher do
An interface for various BGP matching algorithm implementations. An interface for various BGP matching algorithm implementations.
""" """
@type variable :: String.t alias RDF.Query.BGP
@type triple_pattern :: {
subject :: variable | RDF.Term.t,
predicate :: variable | RDF.Term.t,
object :: variable | RDF.Term.t
}
@type triple_patterns :: list(triple_pattern)
@type solution :: map @type solution :: map
@type solutions :: [solution] @type solutions :: [solution]
@callback query(data :: RDF.Graph.t, bgp :: BGP.t, opts :: Keyword.t) :: solutions
@callback query(triple_patterns :: [], data :: RDF.Graph.t, opts :: Keyword.t) :: solutions @callback query_stream(data :: RDF.Graph.t, bgp :: BGP.t, opts :: Keyword.t) :: Enumerable.t()
@callback query_stream(triple_patterns :: [], data :: RDF.Graph.t, opts :: Keyword.t) :: Enumerable.t()
end end

View file

@ -1,15 +1,16 @@
defmodule RDF.Query.BGP.Simple do defmodule RDF.Query.BGP.Simple do
@behaviour RDF.Query.BGP.Matcher @behaviour RDF.Query.BGP.Matcher
alias RDF.Query.BGP
alias RDF.Query.BGP.{QueryPlanner, BlankNodeHandler} alias RDF.Query.BGP.{QueryPlanner, BlankNodeHandler}
alias RDF.{Graph, Description} alias RDF.{Graph, Description}
@impl RDF.Query.BGP.Matcher @impl RDF.Query.BGP.Matcher
def query(data, pattern, opts \\ []) def query(data, pattern, opts \\ [])
def query(_, [], _), do: [%{}] # https://www.w3.org/TR/sparql11-query/#emptyGroupPattern def query(_, %BGP{triple_patterns: []}, _), do: [%{}] # https://www.w3.org/TR/sparql11-query/#emptyGroupPattern
def query(data, triple_patterns, opts) do def query(data, %BGP{triple_patterns: triple_patterns}, opts) do
{bnode_state, triple_patterns} = {bnode_state, triple_patterns} =
BlankNodeHandler.preprocess(triple_patterns) BlankNodeHandler.preprocess(triple_patterns)
@ -20,8 +21,8 @@ defmodule RDF.Query.BGP.Simple do
end end
@impl RDF.Query.BGP.Matcher @impl RDF.Query.BGP.Matcher
def query_stream(data, pattern, opts \\ []) do def query_stream(data, bgp, opts \\ []) do
query(data, pattern, opts) query(data, bgp, opts)
|> Stream.into([]) |> Stream.into([])
end end

View file

@ -1,16 +1,17 @@
defmodule RDF.Query.BGP.Stream do defmodule RDF.Query.BGP.Stream do
@behaviour RDF.Query.BGP.Matcher @behaviour RDF.Query.BGP.Matcher
alias RDF.{Graph, Description} alias RDF.Query.BGP
alias RDF.Query.BGP.{QueryPlanner, BlankNodeHandler} alias RDF.Query.BGP.{QueryPlanner, BlankNodeHandler}
alias RDF.{Graph, Description}
@impl RDF.Query.BGP.Matcher @impl RDF.Query.BGP.Matcher
def query_stream(data, pattern, opts \\ []) def query_stream(data, pattern, opts \\ [])
def query_stream(_, [], _), do: stream([%{}]) # https://www.w3.org/TR/sparql11-query/#emptyGroupPattern def query_stream(_, %BGP{triple_patterns: []}, _), do: stream([%{}]) # https://www.w3.org/TR/sparql11-query/#emptyGroupPattern
def query_stream(data, triple_patterns, opts) do def query_stream(data, %BGP{triple_patterns: triple_patterns}, opts) do
{bnode_state, triple_patterns} = {bnode_state, triple_patterns} =
BlankNodeHandler.preprocess(triple_patterns) BlankNodeHandler.preprocess(triple_patterns)
@ -21,8 +22,8 @@ defmodule RDF.Query.BGP.Stream do
end end
@impl RDF.Query.BGP.Matcher @impl RDF.Query.BGP.Matcher
def query(data, pattern, opts \\ []) do def query(data, bgp, opts \\ []) do
query_stream(data, pattern, opts) query_stream(data, bgp, opts)
|> Enum.to_list() |> Enum.to_list()
end end

View file

@ -1,6 +1,7 @@
defmodule RDF.Query.BGP.SimpleTest do defmodule RDF.Query.BGP.SimpleTest do
use RDF.Test.Case use RDF.Test.Case
alias RDF.Query.BGP
import RDF.Query.BGP.Simple, only: [query: 2] import RDF.Query.BGP.Simple, only: [query: 2]
@example_graph Graph.new([ @example_graph Graph.new([
@ -9,12 +10,18 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.s3, EX.p3, EX.o2} {EX.s3, EX.p3, EX.o2}
]) ])
defp bgp(), do: %BGP{triple_patterns: []}
defp bgp(triple_patterns) when is_list(triple_patterns),
do: %BGP{triple_patterns: triple_patterns}
defp bgp({_, _, _} = triple_pattern),
do: %BGP{triple_patterns: [triple_pattern]}
test "empty bgp" do test "empty bgp" do
assert query(@example_graph, []) == [%{}] assert query(@example_graph, bgp()) == [%{}]
end end
test "single {s ?p ?o}" do test "single {s ?p ?o}" do
assert query(@example_graph, [{EX.s1, "p", "o"}]) == assert query(@example_graph, bgp({EX.s1, "p", "o"})) ==
[ [
%{"p" => EX.p1, "o" => EX.o1}, %{"p" => EX.p1, "o" => EX.o1},
%{"p" => EX.p2, "o" => EX.o2} %{"p" => EX.p2, "o" => EX.o2}
@ -22,7 +29,7 @@ defmodule RDF.Query.BGP.SimpleTest do
end end
test "single {?s ?p o}" do test "single {?s ?p o}" do
assert query(@example_graph, [{"s", "p", EX.o2}]) == assert query(@example_graph, bgp({"s", "p", EX.o2})) ==
[ [
%{"s" => EX.s3, "p" => EX.p3}, %{"s" => EX.s3, "p" => EX.p3},
%{"s" => EX.s1, "p" => EX.p2} %{"s" => EX.s1, "p" => EX.p2}
@ -30,11 +37,12 @@ defmodule RDF.Query.BGP.SimpleTest do
end end
test "single {?s p ?o}" do test "single {?s p ?o}" do
assert query(@example_graph, [{"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 end
test "with no solutions" do test "with no solutions" do
assert query(Graph.new(), [{"a", "b", "c"}]) == [] assert query(Graph.new(), bgp({"a", "b", "c"})) == []
end end
test "with solutions on one triple pattern but none on another one" do test "with solutions on one triple pattern but none on another one" do
@ -43,7 +51,7 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.y, EX.y, EX.z}, {EX.y, EX.y, EX.z},
]) ])
assert query(example_graph, [ assert query(example_graph, bgp [
{"a", EX.p1, ~L"unmatched" }, {"a", EX.p1, ~L"unmatched" },
{"a", EX.y, EX.z} {"a", EX.y, EX.z}
]) == [] ]) == []
@ -56,7 +64,8 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.y, EX.x, EX.y} {EX.y, EX.x, EX.y}
]) ])
assert query(example_graph, [{"a", "a", "b"}]) == [%{"a" => EX.y, "b" => EX.x}] assert query(example_graph, bgp({"a", "a", "b"})) ==
[%{"a" => EX.y, "b" => EX.x}]
end end
test "repeated variable: {?a ?b ?a}" do test "repeated variable: {?a ?b ?a}" do
@ -66,7 +75,8 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.y, EX.x, EX.y} {EX.y, EX.x, EX.y}
]) ])
assert query(example_graph, [{"a", "b", "a"}]) == [%{"a" => EX.y, "b" => EX.x}] assert query(example_graph, bgp({"a", "b", "a"})) ==
[%{"a" => EX.y, "b" => EX.x}]
end end
test "repeated variable: {?b ?a ?a}" do test "repeated variable: {?b ?a ?a}" do
@ -76,7 +86,8 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.y, EX.x, EX.y} {EX.y, EX.x, EX.y}
]) ])
assert query(example_graph, [{"b", "a", "a"}]) == [%{"a" => EX.y, "b" => EX.x}] assert query(example_graph, bgp({"b", "a", "a"})) ==
[%{"a" => EX.y, "b" => EX.x}]
end end
test "repeated variable: {?a ?a ?a}" do test "repeated variable: {?a ?a ?a}" do
@ -87,11 +98,11 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.y, EX.y, EX.y}, {EX.y, EX.y, EX.y},
]) ])
assert query(example_graph, [{"a", "a", "a"}]) == [%{"a" => EX.y}] assert query(example_graph, bgp({"a", "a", "a"})) == [%{"a" => EX.y}]
end end
test "two connected triple patterns with a match" do test "two connected triple patterns with a match" do
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p", "o"}, {EX.s1, "p", "o"},
{EX.s3, "p2", "o" } {EX.s3, "p2", "o" }
]) == [%{ ]) == [%{
@ -100,7 +111,7 @@ defmodule RDF.Query.BGP.SimpleTest do
"o" => EX.o2 "o" => EX.o2
}] }]
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p", "o1"}, {EX.s1, "p", "o1"},
{EX.s1, "p", "o2"} {EX.s1, "p", "o2"}
]) == ]) ==
@ -123,7 +134,7 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.s3, EX.p2, EX.o2}, {EX.s3, EX.p2, EX.o2},
{EX.s3, EX.p3, EX.o1} {EX.s3, EX.p3, EX.o1}
]), ]),
[ bgp [
{EX.s1, EX.p1, "o"}, {EX.s1, EX.p1, "o"},
{EX.s3, "p", "o"} {EX.s3, "p", "o"}
]) == [%{"p" => EX.p3, "o" => EX.o1}] ]) == [%{"p" => EX.p3, "o" => EX.o1}]
@ -136,7 +147,7 @@ defmodule RDF.Query.BGP.SimpleTest do
{EX.s2, EX.p2, EX.o2}, {EX.s2, EX.p2, EX.o2},
{EX.s3, EX.p2, EX.o1} {EX.s3, EX.p2, EX.o1}
]), ]),
[ bgp [
{EX.s1, EX.p1, "o"}, {EX.s1, EX.p1, "o"},
{EX.s2, "p", EX.o2}, {EX.s2, "p", EX.o2},
{"s", "p", "o"} {"s", "p", "o"}
@ -151,18 +162,18 @@ defmodule RDF.Query.BGP.SimpleTest do
end end
test "when no solutions" do test "when no solutions" do
assert query(@example_graph, [{EX.s, EX.p, "o"}]) == [] assert query(@example_graph, bgp({EX.s, EX.p, "o"})) == []
end end
test "multiple triple patterns with a constant unmatched triple has no solutions" do test "multiple triple patterns with a constant unmatched triple has no solutions" do
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p", "o"}, {EX.s1, "p", "o"},
{EX.s, EX.p, EX.o} {EX.s, EX.p, EX.o}
]) == [] ]) == []
end end
test "independent triple patterns lead to cross-products" do test "independent triple patterns lead to cross-products" do
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p1", "o"}, {EX.s1, "p1", "o"},
{"s", "p2", EX.o2} {"s", "p2", EX.o2}
]) == [ ]) == [
@ -194,14 +205,14 @@ defmodule RDF.Query.BGP.SimpleTest do
end end
test "blank nodes behave like variables, but don't appear in the solution" do test "blank nodes behave like variables, but don't appear in the solution" do
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p", RDF.bnode("o")}, {EX.s1, "p", RDF.bnode("o")},
{EX.s3, "p2", RDF.bnode("o")} {EX.s3, "p2", RDF.bnode("o")}
]) == [%{"p" => EX.p2, "p2" => EX.p3}] ]) == [%{"p" => EX.p2, "p2" => EX.p3}]
end end
test "cross-product with blank nodes" do test "cross-product with blank nodes" do
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p1", "o"}, {EX.s1, "p1", "o"},
{RDF.bnode("s"), "p2", EX.o2} {RDF.bnode("s"), "p2", EX.o2}
]) == ]) ==

View file

@ -1,6 +1,7 @@
defmodule RDF.Query.BGP.StreamTest do defmodule RDF.Query.BGP.StreamTest do
use RDF.Test.Case use RDF.Test.Case
alias RDF.Query.BGP
import RDF.Query.BGP.Stream, only: [query: 2] import RDF.Query.BGP.Stream, only: [query: 2]
@example_graph Graph.new([ @example_graph Graph.new([
@ -9,12 +10,19 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.s3, EX.p3, EX.o2} {EX.s3, EX.p3, EX.o2}
]) ])
defp bgp(), do: %BGP{triple_patterns: []}
defp bgp(triple_patterns) when is_list(triple_patterns),
do: %BGP{triple_patterns: triple_patterns}
defp bgp({_, _, _} = triple_pattern),
do: %BGP{triple_patterns: [triple_pattern]}
test "empty bgp" do test "empty bgp" do
assert query(@example_graph, []) == [%{}] assert query(@example_graph, bgp()) == [%{}]
end end
test "single {s ?p ?o}" do test "single {s ?p ?o}" do
assert query(@example_graph, [{EX.s1, "p", "o"}]) == assert query(@example_graph, bgp({EX.s1, "p", "o"})) ==
[ [
%{"p" => EX.p1, "o" => EX.o1}, %{"p" => EX.p1, "o" => EX.o1},
%{"p" => EX.p2, "o" => EX.o2} %{"p" => EX.p2, "o" => EX.o2}
@ -22,7 +30,7 @@ defmodule RDF.Query.BGP.StreamTest do
end end
test "single {?s ?p o}" do test "single {?s ?p o}" do
assert query(@example_graph, [{"s", "p", EX.o2}]) == assert query(@example_graph, bgp({"s", "p", EX.o2})) ==
[ [
%{"s" => EX.s1, "p" => EX.p2}, %{"s" => EX.s1, "p" => EX.p2},
%{"s" => EX.s3, "p" => EX.p3}, %{"s" => EX.s3, "p" => EX.p3},
@ -30,11 +38,11 @@ defmodule RDF.Query.BGP.StreamTest do
end end
test "single {?s p ?o}" do test "single {?s p ?o}" do
assert query(@example_graph, [{"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 end
test "with no solutions" do test "with no solutions" do
assert query(Graph.new(), [{"a", "b", "c"}]) == [] assert query(Graph.new(), bgp({"a", "b", "c"})) == []
end end
test "with solutions on one triple pattern but none on another one" do test "with solutions on one triple pattern but none on another one" do
@ -43,7 +51,7 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.y, EX.y, EX.z}, {EX.y, EX.y, EX.z},
]) ])
assert query(example_graph, [ assert query(example_graph, bgp [
{"a", EX.p1, ~L"unmatched" }, {"a", EX.p1, ~L"unmatched" },
{"a", EX.y, EX.z} {"a", EX.y, EX.z}
]) == [] ]) == []
@ -56,7 +64,8 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.y, EX.x, EX.y} {EX.y, EX.x, EX.y}
]) ])
assert query(example_graph, [{"a", "a", "b"}]) == [%{"a" => EX.y, "b" => EX.x}] assert query(example_graph, bgp({"a", "a", "b"})) ==
[%{"a" => EX.y, "b" => EX.x}]
end end
test "repeated variable: {?a ?b ?a}" do test "repeated variable: {?a ?b ?a}" do
@ -66,7 +75,8 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.y, EX.x, EX.y} {EX.y, EX.x, EX.y}
]) ])
assert query(example_graph, [{"a", "b", "a"}]) == [%{"a" => EX.y, "b" => EX.x}] assert query(example_graph, bgp({"a", "b", "a"})) ==
[%{"a" => EX.y, "b" => EX.x}]
end end
test "repeated variable: {?b ?a ?a}" do test "repeated variable: {?b ?a ?a}" do
@ -76,7 +86,8 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.y, EX.x, EX.y} {EX.y, EX.x, EX.y}
]) ])
assert query(example_graph, [{"b", "a", "a"}]) == [%{"a" => EX.y, "b" => EX.x}] assert query(example_graph, bgp({"b", "a", "a"})) ==
[%{"a" => EX.y, "b" => EX.x}]
end end
test "repeated variable: {?a ?a ?a}" do test "repeated variable: {?a ?a ?a}" do
@ -87,11 +98,11 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.y, EX.y, EX.y}, {EX.y, EX.y, EX.y},
]) ])
assert query(example_graph, [{"a", "a", "a"}]) == [%{"a" => EX.y}] assert query(example_graph, bgp({"a", "a", "a"})) == [%{"a" => EX.y}]
end end
test "two connected triple patterns with a match" do test "two connected triple patterns with a match" do
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p", "o"}, {EX.s1, "p", "o"},
{EX.s3, "p2", "o" } {EX.s3, "p2", "o" }
]) == [%{ ]) == [%{
@ -100,7 +111,7 @@ defmodule RDF.Query.BGP.StreamTest do
"o" => EX.o2 "o" => EX.o2
}] }]
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p", "o1"}, {EX.s1, "p", "o1"},
{EX.s1, "p", "o2"} {EX.s1, "p", "o2"}
]) == ]) ==
@ -123,7 +134,7 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.s3, EX.p2, EX.o2}, {EX.s3, EX.p2, EX.o2},
{EX.s3, EX.p3, EX.o1} {EX.s3, EX.p3, EX.o1}
]), ]),
[ bgp [
{EX.s1, EX.p1, "o"}, {EX.s1, EX.p1, "o"},
{EX.s3, "p", "o"} {EX.s3, "p", "o"}
]) == [%{"p" => EX.p3, "o" => EX.o1}] ]) == [%{"p" => EX.p3, "o" => EX.o1}]
@ -136,7 +147,7 @@ defmodule RDF.Query.BGP.StreamTest do
{EX.s2, EX.p2, EX.o2}, {EX.s2, EX.p2, EX.o2},
{EX.s3, EX.p2, EX.o1} {EX.s3, EX.p2, EX.o1}
]), ]),
[ bgp [
{EX.s1, EX.p1, "o"}, {EX.s1, EX.p1, "o"},
{EX.s2, "p", EX.o2}, {EX.s2, "p", EX.o2},
{"s", "p", "o"} {"s", "p", "o"}
@ -151,11 +162,11 @@ defmodule RDF.Query.BGP.StreamTest do
end end
test "when no solutions" do test "when no solutions" do
assert query(@example_graph, [{EX.s, EX.p, "o"}]) == [] assert query(@example_graph, bgp({EX.s, EX.p, "o"})) == []
end end
test "multiple triple patterns with a constant unmatched triple has no solutions" do test "multiple triple patterns with a constant unmatched triple has no solutions" do
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p", "o"}, {EX.s1, "p", "o"},
{EX.s, EX.p, EX.o} {EX.s, EX.p, EX.o}
]) == [] ]) == []
@ -163,7 +174,7 @@ defmodule RDF.Query.BGP.StreamTest do
test "independent triple patterns lead to cross-products" do test "independent triple patterns lead to cross-products" do
assert MapSet.new( assert MapSet.new(
query(@example_graph, [ query(@example_graph, bgp [
{EX.s1, "p1", "o"}, {EX.s1, "p1", "o"},
{"s", "p2", EX.o2} {"s", "p2", EX.o2}
]) ])
@ -196,7 +207,7 @@ defmodule RDF.Query.BGP.StreamTest do
end end
test "blank nodes behave like variables, but don't appear in the solution" do test "blank nodes behave like variables, but don't appear in the solution" do
assert query(@example_graph, [ assert query(@example_graph, bgp [
{EX.s1, "p", RDF.bnode("o")}, {EX.s1, "p", RDF.bnode("o")},
{EX.s3, "p2", RDF.bnode("o")} {EX.s3, "p2", RDF.bnode("o")}
]) == [%{"p" => EX.p2, "p2" => EX.p3}] ]) == [%{"p" => EX.p2, "p2" => EX.p3}]
@ -204,7 +215,7 @@ defmodule RDF.Query.BGP.StreamTest do
test "cross-product with blank nodes" do test "cross-product with blank nodes" do
assert MapSet.new( assert MapSet.new(
query(@example_graph, [ query(@example_graph, bgp [
{EX.s1, "p1", "o"}, {EX.s1, "p1", "o"},
{RDF.bnode("s"), "p2", EX.o2} {RDF.bnode("s"), "p2", EX.o2}
]) ])