From 722aa2e124fc1cef7219c7a91c5c371f46eaf02f Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Sun, 7 Nov 2021 22:52:00 +0100 Subject: [PATCH] Add more ways to annotate deleted statements --- lib/rdf/graph.ex | 143 +++++++++++++++++++----------- test/unit/star/graph_test.exs | 158 +++++++++++++++++++++++++++++++--- 2 files changed, 241 insertions(+), 60 deletions(-) diff --git a/lib/rdf/graph.ex b/lib/rdf/graph.ex index 9faaa50..b0cd07a 100644 --- a/lib/rdf/graph.ex +++ b/lib/rdf/graph.ex @@ -254,7 +254,7 @@ defmodule RDF.Graph do fn description -> Description.add(description, statements, opts) end ) } - |> handle_annotation_additions(subject, statements, opts) + |> handle_addition_annotations({subject, statements}, opts) end @doc """ @@ -269,6 +269,16 @@ defmodule RDF.Graph do options. They have different addition semantics similar to the `add_annotations/3`, `put_annotations/3` and `put_annotation_properties/3` counterparts. + What should happen with the annotations of statement which got delete during the + overwrite, can be controlled with these keyword options: + + - `:delete_annotations_on_deleted`: deletes all or some annotations of the deleted + statements (see `delete_annotations/3` on possible values) + - `:add_annotations_on_deleted`, `:put_annotations_on_deleted`, + `:put_annotation_properties_on_deleted`: add annotations about the deleted + statements with the respective addition semantics similar to the keyword + options with the `_on_deleted` suffix mentioned above + ## Examples iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) @@ -297,8 +307,8 @@ defmodule RDF.Graph do else new_graph end - |> handle_annotation_deletions(graph, input, opts) - |> handle_annotation_additions(input, opts) + |> handle_overwrite_annotations(graph, input, opts) + |> handle_addition_annotations(input, opts) end def put(%__MODULE__{} = graph, input, opts) do @@ -317,6 +327,16 @@ defmodule RDF.Graph do options. They have different addition semantics similar to the `add_annotations/3`, `put_annotations/3` and `put_annotation_properties/3` counterparts. + What should happen with the annotations of statement which got delete during the + overwrite, can be controlled with these keyword options: + + - `:delete_annotations_on_deleted`: deletes all or some annotations of the deleted + statements (see `delete_annotations/3` on possible values) + - `:add_annotations_on_deleted`, `:put_annotations_on_deleted`, + `:put_annotation_properties_on_deleted`: add annotations about the deleted + statements with the respective addition semantics similar to the keyword + options with the `_on_deleted` suffix mentioned above + ## Examples iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) @@ -350,8 +370,8 @@ defmodule RDF.Graph do else new_graph end - |> handle_annotation_deletions(graph, input, opts) - |> handle_annotation_additions(input, opts) + |> handle_overwrite_annotations(graph, input, opts) + |> handle_addition_annotations(input, opts) end def put_properties(%__MODULE__{} = graph, input, opts) do @@ -367,9 +387,13 @@ defmodule RDF.Graph do use `RDF.Data.delete/2`. The optional `:delete_annotations` keyword option allows to set which of - annotations of the deleted statements should be deleted also. + RDF-star annotations of the deleted statements should be deleted. Any of the possible values of `delete_annotations/3` can be provided here. By default no annotations of the deleted statements will be removed. + Alternatively, the `:add_annotations`, `:put_annotations` or `:put_annotation_properties` + keyword options can be used to add annotations about the deleted statements + with the addition semantics similar to the respective `add_annotations/3`, + `put_annotations/3` and `put_annotation_properties/3` counterparts. """ @spec delete(t, input, keyword) :: t def delete(graph, input, opts \\ []) @@ -421,15 +445,7 @@ defmodule RDF.Graph do else graph end - |> do_delete_delete_annotations(subject, input, opts) - end - - defp do_delete_delete_annotations(graph, subject, statements, opts) do - if delete_annotations = Keyword.get(opts, :delete_annotations, false) do - delete_annotations(graph, Description.new(subject, init: statements), delete_annotations) - else - graph - end + |> handle_deletion_annotations({subject, input}, opts) end @doc """ @@ -438,9 +454,13 @@ defmodule RDF.Graph do If `subjects` contains subjects that are not in `graph`, they're simply ignored. The optional `:delete_annotations` keyword option allows to set which of - annotations of the deleted statements should be deleted also. + RDF-star annotations of the deleted statements should be deleted. Any of the possible values of `delete_annotations/3` can be provided here. By default no annotations of the deleted statements will be removed. + Alternatively, the `:add_annotations`, `:put_annotations` or `:put_annotation_properties` + keyword options can be used to add annotations about the deleted statements + with the addition semantics similar to the respective `add_annotations/3`, + `put_annotations/3` and `put_annotation_properties/3` counterparts. """ @spec delete_descriptions( t, @@ -460,81 +480,104 @@ defmodule RDF.Graph do {deleted_description, descriptions} -> %__MODULE__{graph | descriptions: descriptions} - |> delete_annotations(deleted_description, Keyword.get(opts, :delete_annotations, false)) + |> handle_deletion_annotations(deleted_description, opts) end end defdelegate delete_subjects(graph, subjects), to: __MODULE__, as: :delete_descriptions defdelegate delete_subjects(graph, subjects, opts), to: __MODULE__, as: :delete_descriptions - # We've duplicated this logic from handle_annotation_additions/3 instead of delegating to it - # with Description.new(subject, init: statements), since we don't want to perform the - # creation of descriptions unnecessarily when no annotations are given. - defp handle_annotation_additions(graph, subject, statements, opts) do + defp clear_annotation_opts(opts), + do: Keyword.drop(opts, ~w[add_annotations put_annotations put_annotation_properties]a) + + defp handle_addition_annotations(graph, statements, opts) do cond do Enum.empty?(opts) -> graph put_annotations = Keyword.get(opts, :put_annotations) -> - put_annotations( - graph, - Description.new(subject, Keyword.put(opts, :init, statements)), - put_annotations - ) + put_annotations(graph, annotation_statements(statements, opts), put_annotations) put_annotation_properties = Keyword.get(opts, :put_annotation_properties) -> put_annotation_properties( graph, - Description.new(subject, Keyword.put(opts, :init, statements)), + annotation_statements(statements, opts), put_annotation_properties ) add_annotations = Keyword.get(opts, :add_annotations) -> - add_annotations( - graph, - Description.new(subject, Keyword.put(opts, :init, statements)), - add_annotations - ) + add_annotations(graph, annotation_statements(statements, opts), add_annotations) true -> graph end end - defp handle_annotation_additions(graph, statements, opts) do + defp handle_overwrite_annotations(graph, original_graph, statements, opts) do cond do Enum.empty?(opts) -> graph - put_annotations = Keyword.get(opts, :put_annotations) -> - put_annotations(graph, statements, put_annotations) + delete_annotations = Keyword.get(opts, :delete_annotations_on_deleted) -> + delete_annotations(graph, deletions(original_graph, statements), delete_annotations) - put_annotation_properties = Keyword.get(opts, :put_annotation_properties) -> - put_annotation_properties(graph, statements, put_annotation_properties) + put_annotations = Keyword.get(opts, :put_annotations_on_deleted) -> + put_annotations(graph, deletions(original_graph, statements), put_annotations) - add_annotations = Keyword.get(opts, :add_annotations) -> - add_annotations(graph, statements, add_annotations) + put_annotation_properties = Keyword.get(opts, :put_annotation_properties_on_deleted) -> + put_annotation_properties( + graph, + deletions(original_graph, statements), + put_annotation_properties + ) + + add_annotations = Keyword.get(opts, :add_annotations_on_deleted) -> + add_annotations(graph, deletions(original_graph, statements), add_annotations) true -> graph end end - defp handle_annotation_deletions(graph, original_graph, input, opts) do - if delete_annotations = Keyword.get(opts, :delete_annotations, false) do - diff = - original_graph - |> take(Map.keys(input.descriptions)) - |> RDF.Diff.diff(input) + defp handle_deletion_annotations(graph, statements, opts) do + cond do + Enum.empty?(opts) -> + graph - delete_annotations(graph, diff.deletions, delete_annotations) - else - graph + delete_annotations = Keyword.get(opts, :delete_annotations) -> + delete_annotations(graph, annotation_statements(statements, opts), delete_annotations) + + put_annotations = Keyword.get(opts, :put_annotations) -> + put_annotations(graph, annotation_statements(statements, opts), put_annotations) + + put_annotation_properties = Keyword.get(opts, :put_annotation_properties) -> + put_annotation_properties( + graph, + annotation_statements(statements, opts), + put_annotation_properties + ) + + add_annotations = Keyword.get(opts, :add_annotations) -> + add_annotations(graph, annotation_statements(statements, opts), add_annotations) + + true -> + graph end end - defp clear_annotation_opts(opts), - do: Keyword.drop(opts, ~w[add_annotations put_annotations put_annotation_properties]a) + defp annotation_statements({subject, predications}, opts), + do: Description.new(subject, Keyword.put(opts, :init, predications)) + + defp annotation_statements(statements, _), do: statements + + defp deletions(original, input) do + diff = + original + |> take(Map.keys(input.descriptions)) + |> RDF.Diff.diff(input) + + diff.deletions + end @doc """ Adds RDF-star annotations to the given set of statements. diff --git a/test/unit/star/graph_test.exs b/test/unit/star/graph_test.exs index 0c1ae9d..44d3e56 100644 --- a/test/unit/star/graph_test.exs +++ b/test/unit/star/graph_test.exs @@ -614,11 +614,11 @@ defmodule RDF.Star.Graph.Test do expected_graph end - describe "put/3 with delete_annotations option" do + describe "put/3 with delete_annotations_on_deleted option" do test "no annotations of overwritten statements are removed when delete_annotations is false (default)" do assert graph() |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: [{EX.AP, EX.AO}]) - |> Graph.put({EX.S1, EX.P2, EX.O2}, delete_annotations: false) == + |> Graph.put({EX.S1, EX.P2, EX.O2}, delete_annotations_on_deleted: false) == graph() |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO}) |> Graph.add({EX.S1, EX.P2, EX.O2}) @@ -634,7 +634,7 @@ defmodule RDF.Star.Graph.Test do test "all annotations of overwritten statements are removed when delete_annotations is true" do assert graph() |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: [{EX.AP, EX.AO}]) - |> Graph.put({EX.S1, EX.P2, EX.O2}, delete_annotations: true) == + |> Graph.put({EX.S1, EX.P2, EX.O2}, delete_annotations_on_deleted: true) == graph() |> Graph.add({EX.S1, EX.P2, EX.O2}) @@ -648,7 +648,7 @@ defmodule RDF.Star.Graph.Test do ) |> Graph.put({EX.S1, EX.P3, EX.O3}, add_annotations: [{EX.AP3, EX.AO3}], - delete_annotations: true + delete_annotations_on_deleted: true ) == graph() |> Graph.add([ @@ -665,13 +665,47 @@ defmodule RDF.Star.Graph.Test do |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: [{EX.AP1, EX.AO1}, {EX.AP2, EX.AO2}] ) - |> Graph.put({EX.S1, EX.P2, EX.O2}, delete_annotations: EX.AP1) == + |> Graph.put({EX.S1, EX.P2, EX.O2}, delete_annotations_on_deleted: EX.AP1) == graph() |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) |> Graph.add({EX.S1, EX.P2, EX.O2}) end end + test "put/3 with add_annotations_on_deleted option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.put({EX.S1, EX.P2, EX.O2}, add_annotations_on_deleted: {EX.AP1, EX.AO2}) == + graph() + |> Graph.add({EX.S1, EX.P2, EX.O2}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO2}) + end + + test "put/3 with put_annotations_on_deleted option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.put({EX.S1, EX.P2, EX.O2}, put_annotations_on_deleted: {EX.AP2, EX.AO2}) == + graph() + |> Graph.add({EX.S1, EX.P2, EX.O2}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) + end + + test "put/3 with put_annotation_properties_on_deleted option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.put({EX.S1, EX.P2, EX.O2}, + put_annotation_properties_on_deleted: [ + {EX.AP1, EX.AO12}, + {EX.AP2, EX.AO2} + ] + ) == + graph() + |> Graph.add({EX.S1, EX.P2, EX.O2}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO12}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) + end + test "put_properties/3" do graph = graph() @@ -925,11 +959,11 @@ defmodule RDF.Star.Graph.Test do expected_graph end - describe "put_properties/3 with delete_annotations option" do + describe "put_properties/3 with delete_annotations_on_deleted option" do test "no annotations of overwritten statements are removed when delete_annotations is false (default)" do assert graph() |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: [{EX.AP, EX.AO}]) - |> Graph.put_properties({EX.S1, EX.P1, EX.O2}, delete_annotations: false) == + |> Graph.put_properties({EX.S1, EX.P1, EX.O2}, delete_annotations_on_deleted: false) == graph() |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO}) |> Graph.add({EX.S1, EX.P1, EX.O2}) @@ -945,7 +979,7 @@ defmodule RDF.Star.Graph.Test do test "all annotations of overwritten statements are removed when delete_annotations is true" do assert graph() |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: [{EX.AP, EX.AO}]) - |> Graph.put_properties({EX.S1, EX.P1, EX.O2}, delete_annotations: true) == + |> Graph.put_properties({EX.S1, EX.P1, EX.O2}, delete_annotations_on_deleted: true) == graph() |> Graph.add({EX.S1, EX.P1, EX.O2}) @@ -959,7 +993,7 @@ defmodule RDF.Star.Graph.Test do ) |> Graph.put_properties({EX.S1, EX.P1, EX.O3}, add_annotations: [{EX.AP3, EX.AO3}], - delete_annotations: true + delete_annotations_on_deleted: true ) == graph() |> Graph.add([ @@ -976,13 +1010,51 @@ defmodule RDF.Star.Graph.Test do |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: [{EX.AP1, EX.AO1}, {EX.AP2, EX.AO2}] ) - |> Graph.put_properties({EX.S1, EX.P1, EX.O2}, delete_annotations: EX.AP1) == + |> Graph.put_properties({EX.S1, EX.P1, EX.O2}, delete_annotations_on_deleted: EX.AP1) == graph() |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) |> Graph.add({EX.S1, EX.P1, EX.O2}) end end + test "put_properties/3 with add_annotations_on_deleted option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.put_properties({EX.S1, EX.P1, EX.O2}, + add_annotations_on_deleted: {EX.AP1, EX.AO2} + ) == + graph() + |> Graph.add({EX.S1, EX.P1, EX.O2}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO2}) + end + + test "put_properties/3 with put_annotations_on_deleted option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.put_properties({EX.S1, EX.P1, EX.O2}, + put_annotations_on_deleted: {EX.AP2, EX.AO2} + ) == + graph() + |> Graph.add({EX.S1, EX.P1, EX.O2}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) + end + + test "put_properties/3 with put_annotation_properties_on_deleted option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.put_properties({EX.S1, EX.P1, EX.O2}, + put_annotation_properties_on_deleted: [ + {EX.AP1, EX.AO12}, + {EX.AP2, EX.AO2} + ] + ) == + graph() + |> Graph.add({EX.S1, EX.P1, EX.O2}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO12}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) + end + test "delete/3" do assert graph_with_annotation() |> Graph.delete(star_statement()) == graph() end @@ -1047,6 +1119,41 @@ defmodule RDF.Star.Graph.Test do end end + test "delete/3 with add_annotations option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.delete({EX.S1, EX.P1, EX.O1}, + add_annotations: {EX.AP1, EX.AO2} + ) == + graph() + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO2}) + end + + test "delete/3 with put_annotations option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.delete({EX.S1, EX.P1, EX.O1}, + put_annotations: {EX.AP2, EX.AO2} + ) == + graph() + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) + end + + test "delete/3 with put_annotation_properties option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.delete({EX.S1, EX.P1, EX.O1}, + put_annotation_properties: [ + {EX.AP1, EX.AO12}, + {EX.AP2, EX.AO2} + ] + ) == + graph() + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO12}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) + end + test "delete_descriptions/3" do assert graph_with_annotation() |> Graph.delete_descriptions(statement()) == graph() end @@ -1091,6 +1198,37 @@ defmodule RDF.Star.Graph.Test do end end + test "delete_descriptions/3 with add_annotations option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.delete_descriptions(EX.S1, add_annotations: {EX.AP1, EX.AO2}) == + graph() + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO2}) + end + + test "delete_descriptions/3 with put_annotations option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.delete_descriptions(EX.S1, put_annotations: {EX.AP2, EX.AO2}) == + graph() + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) + end + + test "delete_descriptions/3 with put_annotation_properties option" do + assert graph() + |> Graph.add({EX.S1, EX.P1, EX.O1}, add_annotations: {EX.AP1, EX.AO1}) + |> Graph.delete_descriptions(EX.S1, + put_annotation_properties: [ + {EX.AP1, EX.AO12}, + {EX.AP2, EX.AO2} + ] + ) == + graph() + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO12}) + |> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2}) + end + describe "add_annotations/3" do test "various statement forms annotated with a predicate-object pair" do assert Graph.add_annotations(graph(), statement(), {EX.AP, EX.AO}) ==