From af5cc26d7fef8b0f8e10858ec6d70a34b2171ebe Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Mon, 16 May 2022 22:03:03 +0200 Subject: [PATCH] Make RDF.Data.merge/3 implementations commutative --- CHANGELOG.md | 4 ++++ lib/rdf/data.ex | 26 ++++++++++++++++++++++-- test/support/external_data_impl.ex | 32 ++++++++++++++++++++++++++++++ test/unit/data_test.exs | 17 ++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 test/support/external_data_impl.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f2e956..b56d222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ This project adheres to [Semantic Versioning](http://semver.org/) and ### Changed +- `RDF.Data.merge/3` is now commutative, i.e. structs which implement the + `RDF.Data` protocol can be given also as the second argument + (previously custom structs with `RDF.Data` protocol implementations always + had to be given as the first argument) - several performance improvements diff --git a/lib/rdf/data.ex b/lib/rdf/data.ex index c9f21ad..7dbbf45 100644 --- a/lib/rdf/data.ex +++ b/lib/rdf/data.ex @@ -3,8 +3,6 @@ defprotocol RDF.Data do An abstraction over the different data structures for collections of RDF statements. """ - @type t :: RDF.Description.t() | RDF.Graph.t() | RDF.Dataset.t() - @doc """ Adds statements to a RDF data structure. @@ -166,6 +164,14 @@ defimpl RDF.Data, for: RDF.Description do def merge(description, %Dataset{} = dataset, opts), do: RDF.Data.merge(dataset, description, opts) + def merge(description, %_{} = other, opts) do + if RDF.Data.impl_for(other) do + RDF.Data.merge(other, description, opts) + else + raise ArgumentError, "no RDF.Data implementation found for #{inspect(other)}" + end + end + def delete(description, input, opts \\ []) def delete( @@ -272,6 +278,14 @@ defimpl RDF.Data, for: RDF.Graph do def merge(graph, %Dataset{} = dataset, opts), do: RDF.Data.merge(dataset, graph, opts) + def merge(graph, %_{} = other, opts) do + if RDF.Data.impl_for(other) do + RDF.Data.merge(other, graph, opts) + else + raise ArgumentError, "no RDF.Data implementation found for #{inspect(other)}" + end + end + def delete(graph, input, opts \\ []) def delete(%Graph{name: name} = graph, %Graph{name: other_name}, _opts) @@ -340,6 +354,14 @@ defimpl RDF.Data, for: RDF.Dataset do def merge(dataset, %Dataset{} = other_dataset, opts), do: Dataset.add(dataset, other_dataset, opts) + def merge(dataset, %_{} = other, opts) do + if RDF.Data.impl_for(other) do + RDF.Data.merge(other, dataset, opts) + else + raise ArgumentError, "no RDF.Data implementation found for #{inspect(other)}" + end + end + def delete(dataset, input, opts \\ []) def delete(%Dataset{name: name} = dataset, %Dataset{name: other_name}, _opts) diff --git a/test/support/external_data_impl.ex b/test/support/external_data_impl.ex new file mode 100644 index 0000000..2ba8ac3 --- /dev/null +++ b/test/support/external_data_impl.ex @@ -0,0 +1,32 @@ +defmodule External do + defstruct [] + + import RDF.Sigils + def data, do: RDF.triple(~I, ~I, 42) + + defimpl RDF.Data do + def merge(%External{}, data, opts) do + RDF.Data.merge(data, External.data(), opts) + end + + def delete(_external, _, _opts), do: nil + def pop(_external), do: nil + def empty?(_external), do: false + def include?(_external, _, _opts \\ []), do: false + def describes?(_external, _), do: false + def description(_external, _), do: nil + def descriptions(_external), do: [] + def statements(_external), do: [] + def subjects(_external), do: [] + def predicates(_external), do: [] + def objects(_external), do: [] + def resources(_external), do: [] + def subject_count(_external), do: 0 + def statement_count(_external), do: 0 + + def values(_external, _opts \\ []), do: nil + def map(_external, _fun), do: nil + + def equal?(_, _), do: false + end +end diff --git a/test/unit/data_test.exs b/test/unit/data_test.exs index a20d17e..be99e91 100644 --- a/test/unit/data_test.exs +++ b/test/unit/data_test.exs @@ -625,4 +625,21 @@ defmodule RDF.DataTest do refute RDF.Data.equal?(dataset, graph) end end + + test "external RDF.Data protocol implementations" do + description = EX.S |> EX.p(EX.O2) + + assert RDF.Data.merge(description, %External{}) == + Description.add(description, External.data()) + + graph = RDF.graph({EX.S, EX.p(), EX.O2}) + + assert RDF.Data.merge(graph, %External{}) == + Graph.add(graph, External.data()) + + dataset = RDF.dataset({EX.S, EX.p(), EX.O2}) + + assert RDF.Data.merge(dataset, %External{}) == + Dataset.add(dataset, External.data()) + end end