core: add RDF.Data.merge/2

This commit is contained in:
Marcel Otto 2017-06-08 21:50:46 +02:00
parent 06fb5a4044
commit f5593aeef7
2 changed files with 227 additions and 7 deletions

View file

@ -3,8 +3,26 @@ defprotocol RDF.Data do
An abstraction over the different data structures for collections of RDF statements.
"""
@doc """
Adds statements to a RDF data structure.
As opposed to the specific `add` functions on the RDF data structures, which
always return the same structure type than the first argument, `merge` might
result in another RDF data structure, eg. merging two `RDF.Description` with
different subjects results in a `RDF.Graph` or adding a quad to a `RDF.Graph`
with a different name than the graph context of the quad results in a
`RDF.Dataset`. But it is always guaranteed that the resulting structure has
a `RDF.Data` implementation.
"""
def merge(data, statements)
@doc """
Deletes statements from a RDF data structure.
As opposed to the `delete` functions on RDF data structures directly, this
function only deletes exactly matching structures.
TODO: rename this function to make the different semantics explicit
"""
def delete(data, statements)
@ -64,10 +82,40 @@ defprotocol RDF.Data do
end
defimpl RDF.Data, for: RDF.Description do
def merge(%RDF.Description{subject: subject} = description, {s, _, _} = triple) do
with ^subject <- RDF.Statement.convert_subject(s) do
RDF.Description.add(description, triple)
else
_ ->
RDF.Graph.new(description)
|> RDF.Graph.add(triple)
end
end
def merge(description, {_, _, _, _} = quad),
do: RDF.Dataset.new(description) |> RDF.Dataset.add(quad)
def merge(%RDF.Description{subject: subject} = description,
%RDF.Description{subject: other_subject} = other_description)
when other_subject == subject,
do: RDF.Description.add(description, other_description)
def merge(description, %RDF.Description{} = other_description),
do: RDF.Graph.new(description) |> RDF.Graph.add(other_description)
def merge(description, %RDF.Graph{} = graph),
do: RDF.Data.merge(graph, description)
def merge(description, %RDF.Dataset{} = dataset),
do: RDF.Data.merge(dataset, description)
def delete(%RDF.Description{subject: subject} = description,
%RDF.Description{subject: other_subject})
when subject != other_subject, do: description
def delete(description, statements), do: RDF.Description.delete(description, statements)
def pop(description), do: RDF.Description.pop(description)
def include?(description, statements),
@ -75,11 +123,11 @@ defimpl RDF.Data, for: RDF.Description do
def statements(description), do: RDF.Description.statements(description)
def description(%RDF.Description{subject: subject} = description, requested_subject) do
with ^subject <- RDF.Statement.convert_subject(requested_subject) do
def description(%RDF.Description{subject: subject} = description, s) do
with ^subject <- RDF.Statement.convert_subject(s) do
description
else
_ -> RDF.Description.new(requested_subject)
_ -> RDF.Description.new(s)
end
end
@ -94,10 +142,43 @@ defimpl RDF.Data, for: RDF.Description do
def statement_count(description), do: RDF.Description.count(description)
end
defimpl RDF.Data, for: RDF.Graph do
def merge(%RDF.Graph{name: name} = graph, {_, _, _, graph_context} = quad) do
with ^name <- RDF.Statement.convert_graph_name(graph_context) do
RDF.Graph.add(graph, quad)
else
_ ->
RDF.Dataset.new(graph)
|> RDF.Dataset.add(quad)
end
end
def merge(graph, {_, _, _} = triple),
do: RDF.Graph.add(graph, triple)
def merge(description, {_, _, _, _} = quad),
do: RDF.Dataset.new(description) |> RDF.Dataset.add(quad)
def merge(graph, %RDF.Description{} = description),
do: RDF.Graph.add(graph, description)
def merge(%RDF.Graph{name: name} = graph,
%RDF.Graph{name: other_name} = other_graph)
when other_name == name,
do: RDF.Graph.add(graph, other_graph)
def merge(graph, %RDF.Graph{} = other_graph),
do: RDF.Dataset.new(graph) |> RDF.Dataset.add(other_graph)
def merge(graph, %RDF.Dataset{} = dataset),
do: RDF.Data.merge(dataset, graph)
def delete(%RDF.Graph{name: name} = graph, %RDF.Graph{name: other_name})
when name != other_name, do: graph
def delete(graph, statements), do: RDF.Graph.delete(graph, statements)
def pop(graph), do: RDF.Graph.pop(graph)
def include?(graph, statements), do: RDF.Graph.include?(graph, statements)
@ -116,10 +197,24 @@ defimpl RDF.Data, for: RDF.Graph do
def statement_count(graph), do: RDF.Graph.triple_count(graph)
end
defimpl RDF.Data, for: RDF.Dataset do
def merge(dataset, {_, _, _} = triple),
do: RDF.Dataset.add(dataset, triple)
def merge(dataset, {_, _, _, _} = quad),
do: RDF.Dataset.add(dataset, quad)
def merge(dataset, %RDF.Description{} = description),
do: RDF.Dataset.add(dataset, description)
def merge(dataset, %RDF.Graph{} = graph),
do: RDF.Dataset.add(dataset, graph)
def merge(dataset, %RDF.Dataset{} = other_dataset),
do: RDF.Dataset.add(dataset, other_dataset)
def delete(%RDF.Dataset{name: name} = dataset, %RDF.Dataset{name: other_name})
when name != other_name, do: dataset
def delete(dataset, statements), do: RDF.Dataset.delete(dataset, statements)
def pop(dataset), do: RDF.Dataset.pop(dataset)
def include?(dataset, statements), do: RDF.Dataset.include?(dataset, statements)

View file

@ -14,24 +14,62 @@ defmodule RDF.DataTest do
EX.S2
|> EX.p2(EX.O3, EX.O4)
)
named_graph = %Graph{graph | name: uri(EX.NamedGraph)}
dataset =
Dataset.new
|> Dataset.add(graph)
|> Dataset.add((
|> Dataset.add(
Graph.new(EX.NamedGraph)
|> Graph.add(description)
|> Graph.add({EX.S3, EX.p3, EX.O5})
|> Graph.add({EX.S, EX.p3, EX.O5})), EX.NamedGraph
)
|> Graph.add({EX.S, EX.p3, EX.O5}))
{:ok,
description: description,
graph: graph,
graph: graph, named_graph: named_graph,
dataset: dataset,
}
end
describe "RDF.Data protocol implementation of RDF.Description" do
test "merge of a single triple with different subject", %{description: description} do
assert RDF.Data.merge(description, {EX.Other, EX.p1, EX.O3}) ==
Graph.new(description) |> Graph.add({EX.Other, EX.p1, EX.O3})
end
test "merge of a single triple with same subject", %{description: description} do
assert RDF.Data.merge(description, {EX.S, EX.p1, EX.O3}) ==
Description.add(description, {EX.S, EX.p1, EX.O3})
end
test "merge of a single quad", %{description: description} do
assert RDF.Data.merge(description, {EX.Other, EX.p1, EX.O3, EX.Graph}) ==
Dataset.new(description) |> Dataset.add({EX.Other, EX.p1, EX.O3, EX.Graph})
assert RDF.Data.merge(description, {EX.S, EX.p1, EX.O3, EX.Graph}) ==
Dataset.new(description) |> Dataset.add({EX.S, EX.p1, EX.O3, EX.Graph})
end
test "merge of a description with different subject", %{description: description} do
assert RDF.Data.merge(description, Description.new({EX.Other, EX.p1, EX.O3})) ==
Graph.new(description) |> Graph.add({EX.Other, EX.p1, EX.O3})
end
test "merge of a description with same subject", %{description: description} do
assert RDF.Data.merge(description, Description.new({EX.S, EX.p1, EX.O3})) ==
Description.add(description, {EX.S, EX.p1, EX.O3})
end
test "merge of a graph", %{graph: graph} do
assert RDF.Data.merge(Description.new({EX.Other, EX.p1, EX.O3}), graph) ==
Graph.add(graph, {EX.Other, EX.p1, EX.O3})
end
test "merge of a dataset", %{dataset: dataset} do
assert RDF.Data.merge(Description.new({EX.Other, EX.p1, EX.O3}), dataset) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
end
test "delete", %{description: description} do
assert RDF.Data.delete(description, {EX.S, EX.p1, EX.O2}) ==
Description.delete(description, {EX.S, EX.p1, EX.O2})
@ -43,6 +81,7 @@ defmodule RDF.DataTest do
%Description{description | subject: EX.Other}) == description
end
test "pop", %{description: description} do
assert RDF.Data.pop(description) == Description.pop(description)
end
@ -97,6 +136,60 @@ defmodule RDF.DataTest do
describe "RDF.Data protocol implementation of RDF.Graph" do
test "merge of a single triple", %{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, {EX.Other, EX.p, EX.O}) ==
Graph.add(graph, {EX.Other, EX.p, EX.O})
assert RDF.Data.merge(named_graph, {EX.Other, EX.p, EX.O}) ==
Graph.add(named_graph, {EX.Other, EX.p, EX.O})
end
test "merge of a single quad with the same graph context",
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, {EX.Other, EX.p, EX.O, nil}) ==
Graph.add(graph, {EX.Other, EX.p, EX.O})
assert RDF.Data.merge(named_graph, {EX.Other, EX.p, EX.O, EX.NamedGraph}) ==
Graph.add(named_graph, {EX.Other, EX.p, EX.O})
end
test "merge of a single quad with a different graph context",
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, {EX.S, EX.p1, EX.O3, EX.NamedGraph}) ==
Dataset.new(graph) |> Dataset.add({EX.S, EX.p1, EX.O3, EX.NamedGraph})
assert RDF.Data.merge(named_graph, {EX.S, EX.p1, EX.O3, nil}) ==
Dataset.new(named_graph) |> Dataset.add({EX.S, EX.p1, EX.O3, nil})
end
test "merge of a description", %{graph: graph} do
assert RDF.Data.merge(graph, Description.new({EX.Other, EX.p1, EX.O3})) ==
Graph.add(graph, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(graph, Description.new({EX.S, EX.p1, EX.O3})) ==
Graph.add(graph, {EX.S, EX.p1, EX.O3})
end
test "merge of a graph with the same name",
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, Graph.add(graph, {EX.Other, EX.p1, EX.O3})) ==
Graph.add(graph, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(named_graph, Graph.add(named_graph, {EX.Other, EX.p1, EX.O3})) ==
Graph.add(named_graph, {EX.Other, EX.p1, EX.O3})
end
test "merge of a graph with a different name",
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, named_graph) ==
Dataset.new(graph) |> Dataset.add(named_graph)
assert RDF.Data.merge(named_graph, graph) ==
Dataset.new(named_graph) |> Dataset.add(graph)
end
test "merge of a dataset", %{dataset: dataset} do
assert RDF.Data.merge(Graph.new({EX.Other, EX.p1, EX.O3}), dataset) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(Graph.new(EX.NamedGraph, {EX.Other, EX.p1, EX.O3}), dataset) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3, EX.NamedGraph})
end
test "delete", %{graph: graph} do
assert RDF.Data.delete(graph, {EX.S, EX.p1, EX.O2}) ==
Graph.delete(graph, {EX.S, EX.p1, EX.O2})
@ -163,6 +256,38 @@ defmodule RDF.DataTest do
describe "RDF.Data protocol implementation of RDF.Dataset" do
test "merge of a single triple", %{dataset: dataset} do
assert RDF.Data.merge(dataset, {EX.Other, EX.p, EX.O}) ==
Dataset.add(dataset, {EX.Other, EX.p, EX.O})
end
test "merge of a single quad", %{dataset: dataset} do
assert RDF.Data.merge(dataset, {EX.Other, EX.p, EX.O, nil}) ==
Dataset.add(dataset, {EX.Other, EX.p, EX.O})
assert RDF.Data.merge(dataset, {EX.Other, EX.p, EX.O, EX.NamedGraph}) ==
Dataset.add(dataset, {EX.Other, EX.p, EX.O, EX.NamedGraph})
end
test "merge of a description", %{dataset: dataset} do
assert RDF.Data.merge(dataset, Description.new({EX.Other, EX.p1, EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
end
test "merge of a graph", %{dataset: dataset} do
assert RDF.Data.merge(dataset, Graph.new({EX.Other, EX.p1, EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(dataset, Graph.new(EX.NamedGraph, {EX.Other, EX.p1, EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3, EX.NamedGraph})
end
test "merge of a dataset", %{dataset: dataset} do
assert RDF.Data.merge(dataset, Dataset.new({EX.Other, EX.p1, EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(dataset, Dataset.new(EX.NamedDataset, {EX.Other, EX.p1, EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
end
test "delete", %{dataset: dataset} do
assert RDF.Data.delete(dataset, {EX.S, EX.p1, EX.O2}) ==
Dataset.delete(dataset, {EX.S, EX.p1, EX.O2})