From 287839740c81756cded294426dbaf712730a5df3 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Sat, 18 Apr 2020 00:31:03 +0200 Subject: [PATCH] Add comparable?/2 functions to RDF.Literal and RDF.Literal.Datatypes --- lib/rdf/literal.ex | 7 +++++++ lib/rdf/literal/datatype.ex | 9 +++++++++ lib/rdf/literal/datatypes/generic.ex | 8 ++++++++ lib/rdf/literal/datatypes/lang_string.ex | 6 ++++++ lib/rdf/literal/datatypes/xsd.ex | 8 ++++++++ test/unit/datatypes/generic_test.exs | 16 ++++++++++++++++ test/unit/datatypes/lang_string_test.exs | 16 ++++++++++++++++ test/unit/datatypes/xsd_test.exs | 9 +++++++++ 8 files changed, 79 insertions(+) diff --git a/lib/rdf/literal.ex b/lib/rdf/literal.ex index 058ccf4..813e5d8 100644 --- a/lib/rdf/literal.ex +++ b/lib/rdf/literal.ex @@ -202,6 +202,13 @@ defmodule RDF.Literal do def equal_value?(_, _), do: false + @spec comparable?(t, t) :: boolean + def comparable?(%__MODULE__{literal: %datatype{} = left}, right) do + Datatype.Registry.rdf_datatype(datatype).comparable?(left, right) + end + + def comparable?(_, _), do: false + @spec compare(t, t) :: Datatype.comparison_result | :indeterminate | nil def compare(%__MODULE__{literal: %datatype{} = left}, right) do Datatype.Registry.rdf_datatype(datatype).compare(left, right) diff --git a/lib/rdf/literal/datatype.ex b/lib/rdf/literal/datatype.ex index d9fdca7..928d0b8 100644 --- a/lib/rdf/literal/datatype.ex +++ b/lib/rdf/literal/datatype.ex @@ -86,6 +86,15 @@ defmodule RDF.Literal.Datatype do """ @callback compare(Literal.t() | literal, Literal.t() | literal) :: comparison_result | :indeterminate | nil + @doc """ + Checks if two datatype literals are comparable. + + This basically mimics the comparability check in terms of the `=` operator of the SPARQL + which handles equality comparisons between some datatypes as errors, i.e. considering + them as incompatible. + """ + @callback comparable?(Literal.t() | literal, Literal.t() | literal) :: boolean + @callback update(Literal.t() | literal, fun()) :: Literal.t @callback update(Literal.t() | literal, fun(), keyword) :: Literal.t end diff --git a/lib/rdf/literal/datatypes/generic.ex b/lib/rdf/literal/datatypes/generic.ex index 28b6653..da23dee 100644 --- a/lib/rdf/literal/datatypes/generic.ex +++ b/lib/rdf/literal/datatypes/generic.ex @@ -89,6 +89,14 @@ defmodule RDF.Literal.Generic do def equal_value?(%Literal{literal: left}, right), do: equal_value?(left, right) def equal_value?(%__MODULE__{} = left, right), do: left == right + @impl Datatype + def comparable?(left, %Literal{literal: right}), do: comparable?(left, right) + def comparable?(%Literal{literal: left}, right), do: comparable?(left, right) + def comparable?(%__MODULE__{datatype: left_datatype}, + %__MODULE__{datatype: right_datatype}), + do: left_datatype == right_datatype + def comparable?(_, _), do: false + @impl Datatype def compare(left, %Literal{literal: right}), do: compare(left, right) def compare(%Literal{literal: left}, right), do: compare(left, right) diff --git a/lib/rdf/literal/datatypes/lang_string.ex b/lib/rdf/literal/datatypes/lang_string.ex index 64a6ffb..9d7a90e 100644 --- a/lib/rdf/literal/datatypes/lang_string.ex +++ b/lib/rdf/literal/datatypes/lang_string.ex @@ -95,6 +95,12 @@ defmodule RDF.LangString do def equal_value?(%__MODULE__{} = left, right), do: left == right def equal_value?(_, _), do: false + @impl Datatype + def comparable?(left, %Literal{literal: right}), do: comparable?(left, right) + def comparable?(%Literal{literal: left}, right), do: comparable?(left, right) + def comparable?(%__MODULE__{}, %__MODULE__{}), do: true + def comparable?(_, _), do: false + @impl Datatype def compare(left, %Literal{literal: right}), do: compare(left, right) def compare(%Literal{literal: left}, right), do: compare(left, right) diff --git a/lib/rdf/literal/datatypes/xsd.ex b/lib/rdf/literal/datatypes/xsd.ex index ffd1875..0c0ee02 100644 --- a/lib/rdf/literal/datatypes/xsd.ex +++ b/lib/rdf/literal/datatypes/xsd.ex @@ -92,6 +92,14 @@ defmodule RDF.Literal.XSD do end def equal_value?(_, _), do: false + @impl RDF.Literal.Datatype + def comparable?(left, %Literal{literal: right}), do: comparable?(left, right) + def comparable?(%Literal{literal: left}, right), do: comparable?(left, right) + def comparable?(%unquote(xsd_datatype){} = left, right) do + XSD.Literal.comparable?(left, right) + end + def comparable?(_, _), do: false + @impl RDF.Literal.Datatype @dialyzer {:nowarn_function, compare: 2} # TODO: Why is this warning raised def compare(left, %Literal{literal: right}), do: compare(left, right) diff --git a/test/unit/datatypes/generic_test.exs b/test/unit/datatypes/generic_test.exs index 8a0bd53..e809759 100644 --- a/test/unit/datatypes/generic_test.exs +++ b/test/unit/datatypes/generic_test.exs @@ -165,6 +165,22 @@ defmodule RDF.Literal.GenericTest do assert Generic.equal_value?(Generic.new("foo", datatype: "foo"), RDF.XSD.String.new("foo")) == false end + test "comparable?/2" do + Enum.each @valid, fn {input, {_, datatype}} -> + assert Generic.comparable?( + Generic.new(input, datatype: datatype), + Generic.new(input, datatype: datatype)) == true + end + + assert Generic.comparable?( + Generic.new("foo", datatype: "http://example.com/foo"), + Generic.new("foo", datatype: "http://example.com/bar")) == false + + assert Generic.comparable?( + Generic.new("foo", datatype: "http://example.com/foo"), + RDF.string("foo")) == false + end + test "compare/2" do Enum.each @valid, fn {input, {_, datatype}} -> assert Generic.compare( diff --git a/test/unit/datatypes/lang_string_test.exs b/test/unit/datatypes/lang_string_test.exs index 6f67b70..a5534ba 100644 --- a/test/unit/datatypes/lang_string_test.exs +++ b/test/unit/datatypes/lang_string_test.exs @@ -198,6 +198,22 @@ defmodule RDF.LangStringTest do assert LangString.equal_value?(RDF.XSD.String.new("foo"), LangString.new("foo", [])) == false end + test "comparable?/2" do + Enum.each @valid, fn {input, {_, language}} -> + assert LangString.comparable?( + LangString.new(input, language: language), + LangString.new(input, language: language)) == true + end + + assert LangString.comparable?( + LangString.new("foo", language: "en"), + LangString.new("foo", language: "de")) == true # TODO: This is an assumption. Couldn't find anything in the specs yet. + + assert LangString.comparable?( + LangString.new("foo", language: "de"), + RDF.string("foo")) == false + end + test "compare/2" do Enum.each @valid, fn {input, {_, language}} -> assert LangString.compare( diff --git a/test/unit/datatypes/xsd_test.exs b/test/unit/datatypes/xsd_test.exs index 6b6816e..bd06e4d 100644 --- a/test/unit/datatypes/xsd_test.exs +++ b/test/unit/datatypes/xsd_test.exs @@ -140,6 +140,15 @@ defmodule RDF.Literal.XSDTest do end end) + Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} -> + value = value_type |> value() |> List.first() + @tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value + test "#{rdf_datatype}.comparable?/2 (#{inspect value})", %{rdf_datatype: rdf_datatype, value: value} do + literal = rdf_datatype.new(value) + assert rdf_datatype.comparable?(literal, literal) == true + end + end) + describe "cast/1" do test "when given a literal with the same datatype" do assert RDF.XSD.String.new("foo") |> RDF.XSD.String.cast() == RDF.XSD.String.new("foo")