From ca724d030068442664673d3b5ba17bff8d01dda2 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Thu, 31 Mar 2022 00:13:57 +0200 Subject: [PATCH] Fix graph addition of triples with an empty object list --- CHANGELOG.md | 6 ++++ lib/rdf/graph.ex | 54 +++++++++++++++------------------- test/unit/description_test.exs | 5 ++++ test/unit/graph_test.exs | 10 +++++++ 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94bed78..4141a52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ This project adheres to [Semantic Versioning](http://semver.org/) and - The inspect string of `RDF.Description` now includes the subject separately, so it can be seen also when the description is empty. +### Fixed + +- When triples with an empty object list where added to an `RDF.Graph`, it + included empty descriptions, which lead to inconsistent behaviour + (for example it would be counted in `RDF.Graph.subject_count/1`). + [Compare v0.11.0...HEAD](https://github.com/rdf-elixir/rdf-ex/compare/v0.11.0...HEAD) diff --git a/lib/rdf/graph.ex b/lib/rdf/graph.ex index c31e86f..03562f2 100644 --- a/lib/rdf/graph.ex +++ b/lib/rdf/graph.ex @@ -18,8 +18,6 @@ defmodule RDF.Graph do alias RDF.{Description, IRI, PrefixMap, PropertyMap} alias RDF.Star.Statement - import RDF.Utils - @type graph_description :: %{Statement.subject() => Description.t()} @type t :: %__MODULE__{ @@ -192,20 +190,21 @@ defmodule RDF.Graph do @spec add(t, input, keyword) :: t def add(graph, input, opts \\ []) - def add(%__MODULE__{} = graph, {subject, predications}, opts), - do: do_add(graph, RDF.coerce_subject(subject), predications, opts) - - def add(%__MODULE__{} = graph, {subject, _, _} = triple, opts), - do: do_add(graph, RDF.coerce_subject(subject), triple, opts) - - def add(graph, {subject, predicate, object, _}, opts), - do: add(graph, {subject, predicate, object}, opts) - - def add(%__MODULE__{} = graph, %Description{} = description, opts) do - if Description.count(description) > 0 do - do_add(graph, description.subject, description, opts) - else + def add(%__MODULE__{descriptions: descriptions} = graph, %Description{} = description, opts) do + if Enum.empty?(description) do graph + else + %__MODULE__{ + graph + | descriptions: + Map.update( + descriptions, + description.subject, + description, + &Description.add(&1, description, opts) + ) + } + |> RDF.Star.Graph.handle_addition_annotations(description, opts) end end @@ -234,26 +233,19 @@ defmodule RDF.Graph do |> Enum.reduce(graph, &add(&2, &1, opts)) end + def add(%__MODULE__{} = graph, {subject, predications}, opts), + do: add(graph, Description.new(subject, Keyword.put(opts, :init, predications)), opts) + + def add(%__MODULE__{} = graph, {subject, _, _} = triple, opts), + do: add(graph, Description.new(subject, Keyword.put(opts, :init, triple)), opts) + + def add(graph, {subject, predicate, object, _}, opts), + do: add(graph, {subject, predicate, object}, opts) + def add(graph, input, opts) when is_list(input) or (is_map(input) and not is_struct(input)) do Enum.reduce(input, graph, &add(&2, &1, opts)) end - defp do_add(%__MODULE__{descriptions: descriptions} = graph, subject, statements, opts) do - %__MODULE__{ - graph - | descriptions: - lazy_map_update( - descriptions, - subject, - # when new: create and initialize description with statements - fn -> Description.new(subject, Keyword.put(opts, :init, statements)) end, - # when update: merge statements description - fn description -> Description.add(description, statements, opts) end - ) - } - |> RDF.Star.Graph.handle_addition_annotations({subject, statements}, opts) - end - @doc """ Adds statements to a `RDF.Graph` overwriting existing statements with the subjects given in the `input` data. diff --git a/test/unit/description_test.exs b/test/unit/description_test.exs index 08e02ab..ce238d1 100644 --- a/test/unit/description_test.exs +++ b/test/unit/description_test.exs @@ -124,6 +124,11 @@ defmodule RDF.DescriptionTest do assert description_includes_predication(desc, {EX.p(), iri(EX.O2)}) end + test "with a predicate-object tuple and an empty list of objects" do + assert Description.add(description(), {EX.p(), []}) == + description() + end + test "with a list of predicate-object tuples" do desc = Description.add(description(), [ diff --git a/test/unit/graph_test.exs b/test/unit/graph_test.exs index 7c00748..585a3ac 100644 --- a/test/unit/graph_test.exs +++ b/test/unit/graph_test.exs @@ -74,6 +74,10 @@ defmodule RDF.GraphTest do assert graph_includes_statement?(g, {EX.Subject, EX.predicate(), EX.Object2}) end + test "initial triples with an empty object list" do + assert Graph.new({EX.Subject, EX.predicate(), []}) == Graph.new() + end + test "creating a named graph with an initial description" do g = Description.new(EX.Subject, init: {EX.predicate(), EX.Object}) @@ -336,6 +340,12 @@ defmodule RDF.GraphTest do assert graph_includes_statement?(g, {EX.S2, EX.P2, EX.O2}) end + test "empty object list" do + assert Graph.add(graph(), {EX.S, EX.P, []}) == graph() + graph = Graph.new({EX.S, EX.P, EX.O}) + assert Graph.add(graph, {EX.S, EX.P, []}) == graph + end + test "a mixed list" do g = Graph.new([{EX.S1, EX.p1(), EX.O1}, {EX.S2, EX.p2(), EX.O2}, {EX.S1, EX.p3(), EX.O3}])