Add RDF.Query.BGP struct
This commit is contained in:
parent
2fca23209b
commit
9cd4478574
6 changed files with 89 additions and 56 deletions
14
lib/rdf/query/bgp.ex
Normal file
14
lib/rdf/query/bgp.ex
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}
|
||||
]) ==
|
||||
|
|
|
@ -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}
|
||||
])
|
||||
|
|
Loading…
Reference in a new issue