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.
"""
@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)
alias RDF.Query.BGP
@type solution :: map
@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

View file

@ -1,15 +1,16 @@
defmodule RDF.Query.BGP.Simple do
@behaviour RDF.Query.BGP.Matcher
alias RDF.Query.BGP
alias RDF.Query.BGP.{QueryPlanner, BlankNodeHandler}
alias RDF.{Graph, Description}
@impl RDF.Query.BGP.Matcher
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} =
BlankNodeHandler.preprocess(triple_patterns)
@ -20,8 +21,8 @@ defmodule RDF.Query.BGP.Simple do
end
@impl RDF.Query.BGP.Matcher
def query_stream(data, pattern, opts \\ []) do
query(data, pattern, opts)
def query_stream(data, bgp, opts \\ []) do
query(data, bgp, opts)
|> Stream.into([])
end

View file

@ -1,16 +1,17 @@
defmodule RDF.Query.BGP.Stream do
@behaviour RDF.Query.BGP.Matcher
alias RDF.{Graph, Description}
alias RDF.Query.BGP
alias RDF.Query.BGP.{QueryPlanner, BlankNodeHandler}
alias RDF.{Graph, Description}
@impl RDF.Query.BGP.Matcher
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} =
BlankNodeHandler.preprocess(triple_patterns)
@ -21,8 +22,8 @@ defmodule RDF.Query.BGP.Stream do
end
@impl RDF.Query.BGP.Matcher
def query(data, pattern, opts \\ []) do
query_stream(data, pattern, opts)
def query(data, bgp, opts \\ []) do
query_stream(data, bgp, opts)
|> Enum.to_list()
end

View file

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

View file

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