Handle derived datatypes on RDF.Literal.Datatype.equal_value?/2 properly

This commit is contained in:
Marcel Otto 2020-05-25 23:10:50 +02:00
parent d247e1bf4f
commit 162e82ed47
15 changed files with 288 additions and 140 deletions

View file

@ -24,9 +24,9 @@ defmodule RDF.Literal.Datatype do
@callback new!(any, Keyword.t()) :: Literal.t()
@doc """
Casts a datatype literal of one type into a datatype literal of another type.
Callback for datatype specific castings.
This function is called by the auto-generated `cast/1` function on the implementations, which already deals with the basic cases.
This callback is called by the auto-generated `cast/1` function on the implementations, which already deals with the basic cases.
So, implementations can assume the passed argument is a valid `RDF.Literal.Datatype` struct,
a `RDF.IRI` or a `RDF.BlankNode`.
@ -98,7 +98,10 @@ defmodule RDF.Literal.Datatype do
@callback valid?(Literal.t() | literal | any) :: boolean
@doc """
Checks if two datatype literals are equal in terms of the values of their value space.
Callback for datatype specific `equal_value?/2` comparisons when the given literals have the same or derived datatypes.
This callback is called by auto-generated `equal_value?/2` function when the given literals have
the same datatype or one is derived from the other.
Should return `nil` when the given arguments are not comparable as literals of this
datatype. This behaviour is particularly important for SPARQL.ex where this
@ -106,10 +109,25 @@ defmodule RDF.Literal.Datatype do
terms are treated as errors and immediately leads to a rejection of a possible
match.
This function is called by auto-generated `equal_value?/2` function on the
implementations, which already deals with basic cases and coercion.
See also `c:do_equal_value_different_datatypes?/2`.
"""
@callback do_equal_value?(literal, literal) :: boolean | nil
@callback do_equal_value_same_or_derived_datatypes?(literal, literal) :: boolean | nil
@doc """
Callback for datatype specific `equal_value?/2` comparisons when the given literals have different datatypes.
This callback is called by auto-generated `equal_value?/2` function when the given literals have
different datatypes and are not derived from each other.
Should return `nil` when the given arguments are not comparable as literals of this
datatype. This behaviour is particularly important for SPARQL.ex where this
function is used for the `=` operator, where comparisons between incomparable
terms are treated as errors and immediately leads to a rejection of a possible
match.
See also `c:do_equal_value_same_or_derived_datatypes?/2`.
"""
@callback do_equal_value_different_datatypes?(literal, literal) :: boolean | nil
@doc """
Compares two `RDF.Literal`s.
@ -266,7 +284,11 @@ defmodule RDF.Literal.Datatype do
Returns `nil` when the given arguments are not comparable as literals of this
datatype.
Implementations define this equivalence relation via the `c:do_equal_value?/2` callback.
Invalid literals are only considered equal in this relation when both have the exact same
datatype and the same attributes (lexical form, language etc.).
Implementations can customize this equivalence relation via the `c:do_equal_value_different_datatypes?/2`
and `c:do_equal_value_different_datatypes?/2` callbacks.
"""
def equal_value?(left, right)
def equal_value?(left, %Literal{literal: right}), do: equal_value?(left, right)
@ -275,24 +297,58 @@ defmodule RDF.Literal.Datatype do
def equal_value?(_, nil), do: nil
def equal_value?(left, right) do
cond do
not Literal.datatype?(right) and not RDF.term?(right) -> equal_value?(left, Literal.coerce(right))
not Literal.datatype?(left) and not RDF.term?(left) -> equal_value?(Literal.coerce(left), right)
true -> do_equal_value?(left, right)
not Literal.datatype?(right) and not resource?(right) -> equal_value?(left, Literal.coerce(right))
not Literal.datatype?(left) and not resource?(left) -> equal_value?(Literal.coerce(left), right)
true ->
left_datatype = left.__struct__
right_datatype = right.__struct__
left_valid = resource?(left) or left_datatype.valid?(left)
right_valid = resource?(right) or right_datatype.valid?(right)
cond do
not left_valid and not right_valid ->
left == right
left_valid and right_valid ->
case equality_path(left_datatype, right_datatype) do
{:same_or_derived, datatype} ->
datatype.do_equal_value_same_or_derived_datatypes?(left, right)
{:different, datatype} ->
datatype.do_equal_value_different_datatypes?(left, right)
end
# one of the given literals is invalid
true ->
if left_datatype == right_datatype do
false
end
end
end
end
# RDF.XSD.Datatypes offers another default implementation, but since it is
# RDF.XSD.Datatype offers another default implementation, but since it is
# still in a macro implementation defoverridable doesn't work
unless RDF.XSD.Datatype in @behaviour do
@impl unquote(__MODULE__)
def do_equal_value?(left, right)
def do_equal_value?(%__MODULE__{} = left, %__MODULE__{} = right), do: left == right
def do_equal_value?(_, _), do: nil
def do_equal_value_same_or_derived_datatypes?(left, right), do: left == right
defoverridable do_equal_value?: 2
@impl unquote(__MODULE__)
def do_equal_value_different_datatypes?(left, right), do: nil
defoverridable do_equal_value_same_or_derived_datatypes?: 2,
do_equal_value_different_datatypes?: 2
end
@impl unquote(__MODULE__)
defp equality_path(left_datatype, right_datatype)
defp equality_path(datatype, datatype), do: {:same_or_derived, datatype}
defp equality_path(datatype, _), do: {:different, datatype}
# as opposed to RDF.resource? this does not try to resolve atoms
defp resource?(%RDF.IRI{}), do: true
defp resource?(%RDF.BlankNode{}), do: true
defp resource?(_), do: false
@impl unquote(__MODULE__)
def update(literal, fun, opts \\ [])
def update(%Literal{literal: literal}, fun, opts), do: update(literal, fun, opts)
def update(%__MODULE__{} = literal, fun, opts) do
@ -314,6 +370,7 @@ defmodule RDF.Literal.Datatype do
cast: 1,
do_cast: 1,
equal_value?: 2,
equality_path: 2,
update: 2,
update: 3

View file

@ -85,9 +85,11 @@ defmodule RDF.Literal.Generic do
def do_cast(_), do: nil
@impl Datatype
def do_equal_value?(%__MODULE__{datatype: datatype} = left,
%__MODULE__{datatype: datatype} = right), do: left == right
def do_equal_value?(_, _), do: nil
def do_equal_value_same_or_derived_datatypes?(
%{datatype: datatype} = left,
%{datatype: datatype} = right
), do: left == right
def do_equal_value_same_or_derived_datatypes?(_, _), do: nil
@impl Datatype
def compare(left, %Literal{literal: right}), do: compare(left, right)

View file

@ -77,6 +77,18 @@ defmodule RDF.XSD.Datatype do
defdelegate get(id), to: RDF.Literal.Datatype.Registry, as: :xsd_datatype
@doc false
def most_specific(left, right)
def most_specific(datatype, datatype), do: datatype
def most_specific(left, right) do
cond do
left.datatype?(right) -> right
right.datatype?(left) -> left
true -> nil
end
end
defmacro __using__(opts) do
quote do
defstruct [:value, :uncanonical_lexical]
@ -245,6 +257,21 @@ defmodule RDF.XSD.Datatype do
do: datatype?(datatype) and datatype.valid?(literal)
def valid?(_), do: false
@doc false
defp equality_path(left_datatype, right_datatype)
defp equality_path(datatype, datatype), do: {:same_or_derived, datatype}
defp equality_path(left_datatype, right_datatype) do
if RDF.XSD.datatype?(left_datatype) and RDF.XSD.datatype?(right_datatype) do
if datatype = RDF.XSD.Datatype.most_specific(left_datatype, right_datatype) do
{:same_or_derived, datatype}
else
{:different, left_datatype}
end
else
{:different, left_datatype}
end
end
defimpl Inspect do
"Elixir.Inspect." <> datatype_name = to_string(__MODULE__)
@datatype_name datatype_name

View file

@ -49,21 +49,12 @@ defmodule RDF.XSD.Datatype.Primitive do
end
@impl RDF.Literal.Datatype
def do_equal_value?(left, right)
def do_equal_value?(
%datatype{uncanonical_lexical: lexical1, value: nil},
%datatype{uncanonical_lexical: lexical2, value: nil}
) do
lexical1 == lexical2
def do_equal_value_same_or_derived_datatypes?(%left_datatype{} = left, %right_datatype{} = right) do
left_datatype.value(left) == right_datatype.value(right)
end
def do_equal_value?(%datatype{} = literal1, %datatype{} = literal2) do
literal1 |> datatype.canonical() |> datatype.value() ==
literal2 |> datatype.canonical() |> datatype.value()
end
def do_equal_value?(_, _), do: nil
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right), do: nil
@impl RDF.Literal.Datatype
def compare(left, right)
@ -88,7 +79,8 @@ defmodule RDF.XSD.Datatype.Primitive do
do_cast: 1,
init_valid_lexical: 3,
init_invalid_lexical: 2,
do_equal_value?: 2,
do_equal_value_same_or_derived_datatypes?: 2,
do_equal_value_different_datatypes?: 2,
compare: 2
@before_compile unquote(__MODULE__)

View file

@ -71,12 +71,19 @@ defmodule RDF.XSD.Datatype.Restriction do
end
end
# TODO: This makes it impossible to define do_equal_value definitions on derivations,
# but we need to overwrite this to reach for example the XSD.Numeric delegation.
def equal_value?(literal1, literal2), do: @base.equal_value?(literal1, literal2)
def equal_value?(literal1, literal2) do
base_primitive().equal_value?(literal1, literal2)
end
@impl RDF.Literal.Datatype
def do_equal_value?(left, right), do: nil # unused; see comment on equal_value?/2
def do_equal_value_same_or_derived_datatypes?(left, right) do
@base.do_equal_value_same_or_derived_datatypes?(left, right)
end
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right) do
@base.do_equal_value_different_datatypes?(left, right)
end
@impl RDF.Literal.Datatype
def compare(left, right), do: @base.compare(left, right)
@ -84,6 +91,8 @@ defmodule RDF.XSD.Datatype.Restriction do
defoverridable canonical_mapping: 1,
do_cast: 1,
equal_value?: 2,
do_equal_value_same_or_derived_datatypes?: 2,
do_equal_value_different_datatypes?: 2,
compare: 2
Module.register_attribute(__MODULE__, :facets, accumulate: true)

View file

@ -65,23 +65,24 @@ defmodule RDF.XSD.AnyURI do
def do_cast(value), do: super(value)
@impl RDF.Literal.Datatype
def do_equal_value?(literal1, literal2)
def do_equal_value_different_datatypes?(left, right)
def do_equal_value?(%IRI{} = iri, %__MODULE__{} = any_uri),
do: do_equal_value?(any_uri, iri)
def do_equal_value_different_datatypes?(%IRI{} = iri, any_uri),
do: do_equal_value_different_datatypes?(any_uri, iri)
def do_equal_value?(%__MODULE__{} = any_uri, %IRI{value: iri}),
def do_equal_value_different_datatypes?(any_uri, %IRI{value: iri}),
do: lexical(any_uri) == iri
def do_equal_value?(left, %__MODULE__{} = right) when maybe_ns_term(left),
do: equal_value?(right, left)
def do_equal_value_different_datatypes?(left, right) when maybe_ns_term(left),
do: do_equal_value_different_datatypes?(right, left)
def do_equal_value?(%__MODULE__{} = left, right) when maybe_ns_term(right) do
def do_equal_value_different_datatypes?(left, right) when maybe_ns_term(right) do
case RDF.Namespace.resolve_term(right) do
{:ok, iri} -> equal_value?(left, iri)
{:ok, iri} -> do_equal_value_different_datatypes?(left, iri)
_ -> nil
end
end
def do_equal_value?(literal1, literal2), do: super(literal1, literal2)
def do_equal_value_different_datatypes?(literal1, literal2),
do: super(literal1, literal2)
end

View file

@ -166,30 +166,21 @@ defmodule RDF.XSD.Date do
@impl RDF.Literal.Datatype
def do_equal_value?(literal1, literal2)
def do_equal_value?(
%__MODULE__{value: nil, uncanonical_lexical: lexical1},
%__MODULE__{value: nil, uncanonical_lexical: lexical2}
) do
lexical1 == lexical2
end
def do_equal_value?(%__MODULE__{value: value1}, %__MODULE__{value: value2})
when is_nil(value1) or is_nil(value2),
do: false
def do_equal_value?(%__MODULE__{value: value1}, %__MODULE__{value: value2}) do
def do_equal_value_same_or_derived_datatypes?(left, right) do
XSD.DateTime.equal_value?(
comparison_normalization(value1),
comparison_normalization(value2)
comparison_normalization(left.value),
comparison_normalization(right.value)
)
end
def do_equal_value?(%__MODULE__{}, %XSD.DateTime{}), do: false
def do_equal_value?(%XSD.DateTime{}, %__MODULE__{}), do: false
def do_equal_value?(_, _), do: nil
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right) do
if XSD.DateTime.datatype?(left) or XSD.DateTime.datatype?(right) do
false
else
super(left, right)
end
end
@impl RDF.Literal.Datatype
def compare(left, right)

View file

@ -155,27 +155,19 @@ defmodule RDF.XSD.DateTime do
end
@impl RDF.Literal.Datatype
def do_equal_value?(literal1, literal2)
def do_equal_value?(
%__MODULE__{value: %type{} = value1},
%__MODULE__{value: %type{} = value2}
def do_equal_value_same_or_derived_datatypes?(
%{value: %type{} = left_value},
%{value: %type{} = right_value}
) do
type.compare(value1, value2) == :eq
end
def do_equal_value?(
%__MODULE__{value: nil, uncanonical_lexical: lexical1},
%__MODULE__{value: nil, uncanonical_lexical: lexical2}
) do
lexical1 == lexical2
type.compare(left_value, right_value) == :eq
end
# This is another quirk for the open-world test date-2 from the SPARQL 1.0 test suite:
# comparisons between one date with tz and another one without a tz are incomparable
# when the unequal, but comparable and returning false when equal.
# What's the reasoning behind this madness?
def do_equal_value?(%__MODULE__{} = literal1, %__MODULE__{} = literal2) do
case compare(literal1, literal2) do
def do_equal_value_same_or_derived_datatypes?(left_literal, right_literal) do
case compare(left_literal, right_literal) do
:lt -> false
:gt -> false
# This actually can't/shouldn't happen.
@ -184,12 +176,16 @@ defmodule RDF.XSD.DateTime do
end
end
def do_equal_value?(%__MODULE__{}, %XSD.Date{}), do: false
def do_equal_value?(%XSD.Date{}, %__MODULE__{}), do: false
def do_equal_value?(_, _), do: nil
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right) do
if XSD.Date.datatype?(left) or XSD.Date.datatype?(right) do
false
else
super(left, right)
end
end
@impl RDF.Literal.Datatype
def compare(left, right)
def compare(left, %RDF.Literal{literal: right}), do: compare(left, right)
def compare(%RDF.Literal{literal: left}, right), do: compare(left, right)

View file

@ -152,7 +152,12 @@ defmodule RDF.XSD.Decimal do
end
def equal_value?(left, right), do: XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right), do: XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right), do: XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def compare(left, right), do: XSD.Numeric.compare(left, right)

View file

@ -160,7 +160,12 @@ defmodule RDF.XSD.Double do
end
def equal_value?(left, right), do: XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right), do: XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right), do: XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def compare(left, right), do: XSD.Numeric.compare(left, right)

View file

@ -99,7 +99,11 @@ defmodule RDF.XSD.Integer do
end
end
def equal_value?(left, right), do: XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right), do: XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right), do: XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def compare(left, right), do: XSD.Numeric.compare(left, right)

View file

@ -12,7 +12,7 @@ defmodule RDF.XSD.Numeric do
defdelegate datatype?(value), to: Literal.Datatype.Registry, as: :numeric_datatype?
@doc """
@doc !"""
Tests for numeric value equality of two numeric XSD datatyped literals.
see:
@ -22,30 +22,21 @@ defmodule RDF.XSD.Numeric do
"""
@spec equal_value?(t() | any, t() | any) :: boolean
def equal_value?(left, right)
def equal_value?(left, %Literal{literal: right}), do: equal_value?(left, right)
def equal_value?(%Literal{literal: left}, right), do: equal_value?(left, right)
def equal_value?(nil, _), do: nil
def equal_value?(_, nil), do: nil
def equal_value?(
%datatype{value: nil, uncanonical_lexical: lexical1},
%datatype{value: nil, uncanonical_lexical: lexical2}
) do
lexical1 == lexical2
end
def equal_value?(%left_datatype{value: left}, %right_datatype{value: right})
when left_datatype == XSD.Decimal or right_datatype == XSD.Decimal,
do: not is_nil(left) and not is_nil(right) and equal_decimal_value?(left, right)
def equal_value?(%left_datatype{value: left}, %right_datatype{value: right}) do
if datatype?(left_datatype) and datatype?(right_datatype) do
left != :nan and right != :nan and left == right
cond do
XSD.Decimal.datatype?(left_datatype) or XSD.Decimal.datatype?(right_datatype) ->
equal_decimal_value?(left, right)
datatype?(left_datatype) and datatype?(right_datatype) ->
left != :nan and right != :nan and left == right
true ->
nil
end
end
def equal_value?(left, right),
do: equal_value?(Literal.coerce(left), Literal.coerce(right))
def equal_value?(_, _), do: nil
defp equal_decimal_value?(%D{} = left, %D{} = right), do: D.equal?(left, right)

View file

@ -189,19 +189,11 @@ defmodule RDF.XSD.Time do
end
end
@impl RDF.Literal.Datatype
def do_equal_value?(literal1, literal2)
def do_equal_value?(%__MODULE__{value: %_{}}, %__MODULE__{value: tz_tuple})
when is_tuple(tz_tuple),
do: nil
def do_equal_value?(%__MODULE__{value: tz_tuple}, %__MODULE__{value: %_{}})
when is_tuple(tz_tuple),
do: nil
def do_equal_value?(left, right), do: super(left, right)
def do_equal_value_same_or_derived_datatypes?(left, right)
def do_equal_value_same_or_derived_datatypes?(%{value: %{}}, %{value: tz_tuple}) when is_tuple(tz_tuple), do: nil
def do_equal_value_same_or_derived_datatypes?(%{value: tz_tuple}, %{value: %{}}) when is_tuple(tz_tuple), do: nil
def do_equal_value_same_or_derived_datatypes?(left, right), do: super(left, right)
@doc """
Extracts the timezone string from a `RDF.XSD.Time` value.

View file

@ -1,6 +1,9 @@
defmodule RDF.EqualityTest do
use RDF.Test.Case
alias RDF.TestDatatypes.{Initials, CustomTime, DateWithoutTz, DateTimeWithTz, Age,
DecimalUnitInterval, DoubleUnitInterval, FloatUnitInterval}
describe "RDF.IRI and XSD.AnyURI" do
@term_equal_iris [
{RDF.iri("http://example.com/"), RDF.iri("http://example.com/")},
@ -66,11 +69,13 @@ defmodule RDF.EqualityTest do
@term_equal_strings [
{XSD.string("foo"), XSD.string("foo")},
{RDF.lang_string("foo", language: "de"), RDF.lang_string("foo", language: "de")},
{Initials.new("FO"), Initials.new("FO")}
]
@value_equal_strings []
@unequal_strings [
{XSD.string("foo"), XSD.string("bar")},
{RDF.lang_string("foo", language: "de"), RDF.lang_string("bar", language: "de")},
{XSD.string("fo"), Initials.new("FO")}
]
@equal_strings_by_coercion [
{XSD.string("foo"), "foo"}
@ -152,7 +157,9 @@ defmodule RDF.EqualityTest do
{XSD.float("1.0"), XSD.float(1.0)},
{XSD.decimal("1.0"), XSD.decimal(1.0)},
{XSD.decimal("-42.0"), XSD.decimal(-42.0)},
{XSD.decimal("1.0"), XSD.decimal(1.0)}
{XSD.decimal("1.0"), XSD.decimal(1.0)},
{Age.new("42"), Age.new("42")},
{DecimalUnitInterval.new("0.1"), DecimalUnitInterval.new("0.1")},
]
@value_equal_numerics [
{XSD.integer("42"), XSD.non_negative_integer("42")},
@ -169,6 +176,11 @@ defmodule RDF.EqualityTest do
{XSD.integer(42), XSD.decimal(42.0)},
{XSD.integer(42), XSD.double(42.0)},
{XSD.integer(42), XSD.float(42.0)},
{XSD.integer(42), Age.new(42)},
{XSD.float(0.1), DecimalUnitInterval.new(0.1)},
{XSD.decimal(0.1), DoubleUnitInterval.new(0.1)},
{XSD.double(0.1), FloatUnitInterval.new(0.1)},
{DecimalUnitInterval.new(0.1), DoubleUnitInterval.new(0.1)},
{XSD.non_negative_integer(42), XSD.decimal(42.0)},
{XSD.non_negative_integer(42), XSD.double(42.0)},
{XSD.positive_integer(42), XSD.decimal(42.0)},
@ -187,7 +199,8 @@ defmodule RDF.EqualityTest do
@unequal_numerics [
{XSD.integer(1), XSD.integer(2)},
{XSD.integer("1"), XSD.double("1.1")},
{XSD.integer("1"), XSD.decimal("1.1")}
{XSD.integer("1"), XSD.decimal("1.1")},
{DecimalUnitInterval.new(0.1), DoubleUnitInterval.new(0.2)},
]
@equal_numerics_by_coercion [
{XSD.integer(42), 42},
@ -214,7 +227,8 @@ defmodule RDF.EqualityTest do
{XSD.double("foo"), XSD.double("foo")},
{XSD.float("foo"), XSD.float("foo")},
{XSD.non_negative_integer("foo"), XSD.non_negative_integer("foo")},
{XSD.positive_integer("foo"), XSD.positive_integer("foo")}
{XSD.positive_integer("foo"), XSD.positive_integer("foo")},
{DecimalUnitInterval.new(1.1), DecimalUnitInterval.new(1.1)},
]
@unequal_invalid_numerics [
{XSD.integer("foo"), XSD.integer("bar")},
@ -224,7 +238,8 @@ defmodule RDF.EqualityTest do
{XSD.double("foo"), XSD.double("bar")},
{XSD.float("foo"), XSD.float("bar")},
{XSD.non_negative_integer("foo"), XSD.non_negative_integer("bar")},
{XSD.positive_integer("foo"), XSD.positive_integer("bar")}
{XSD.positive_integer("foo"), XSD.positive_integer("bar")},
{DecimalUnitInterval.new(1.1), DoubleUnitInterval.new(1.2)},
]
@incomparable_numerics [
{XSD.integer("42"), nil},
@ -250,7 +265,8 @@ defmodule RDF.EqualityTest do
describe "XSD.DateTime" do
@term_equal_datetimes [
{XSD.datetime("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T12:00:00-01:00")},
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T12:00:00")}
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T12:00:00")},
{DateTimeWithTz.new("2002-04-02T12:00:00Z"), DateTimeWithTz.new("2002-04-02T12:00:00Z")},
]
@value_equal_datetimes [
{XSD.datetime("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T17:00:00+04:00")},
@ -261,11 +277,16 @@ defmodule RDF.EqualityTest do
{XSD.datetime("2010-01-01T00:00:00+00:00"), XSD.datetime("2010-01-01T00:00:00Z")},
{XSD.datetime("2002-04-02T23:00:00+00:00"), XSD.datetime("2002-04-02T23:00:00-00:00")},
{XSD.datetime("2010-01-01T00:00:00.0000Z"), XSD.datetime("2010-01-01T00:00:00Z")},
{XSD.datetime("2005-04-04T24:00:00"), XSD.datetime("2005-04-05T00:00:00")}
{XSD.datetime("2005-04-04T24:00:00"), XSD.datetime("2005-04-05T00:00:00")},
{DateTimeWithTz.new("2002-04-02T12:00:00-01:00"), DateTimeWithTz.new("2002-04-02T17:00:00+04:00")},
{DateTimeWithTz.new("2002-04-02T23:00:00Z"), XSD.datetime("2002-04-02T23:00:00+00:00")},
{XSD.datetime("2002-04-02T23:00:00+00:00"), DateTimeWithTz.new("2002-04-02T23:00:00-00:00")},
]
@unequal_datetimes [
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T17:00:00")},
{XSD.datetime("2005-04-04T24:00:00"), XSD.datetime("2005-04-04T00:00:00")}
{XSD.datetime("2005-04-04T24:00:00"), XSD.datetime("2005-04-04T00:00:00")},
{DateTimeWithTz.new("2005-04-04T24:00:00"), DateTimeWithTz.new("2005-04-04T00:00:00")}
]
@equal_datetimes_by_coercion [
{XSD.datetime("2002-04-02T12:00:00-01:00"),
@ -285,17 +306,21 @@ defmodule RDF.EqualityTest do
elem(DateTime.from_iso8601("2002-04-02T12:00:00+00:00"), 1)}
]
@equal_invalid_datetimes [
{XSD.datetime("foo"), XSD.datetime("foo")}
{XSD.datetime("foo"), XSD.datetime("foo")},
{DateTimeWithTz.new("foo"), DateTimeWithTz.new("foo")}
]
@unequal_invalid_datetimes [
{XSD.datetime("foo"), XSD.datetime("bar")}
{XSD.datetime("foo"), XSD.datetime("bar")},
{DateTimeWithTz.new("foo"), DateTimeWithTz.new("bar")},
{XSD.datetime("foo"), DateTimeWithTz.new("bar")}
]
@incomparable_datetimes [
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T12:00:00Z")},
{XSD.datetime("2010-01-01T00:00:00Z"), XSD.datetime("2010-01-01T00:00:00")},
{XSD.string("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T12:00:00-01:00")},
# These are incomparable because of indeterminacy due to missing timezone
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T23:00:00+00:00")}
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T23:00:00+00:00")},
{XSD.datetime("2002-04-02T12:00:00"), DateTimeWithTz.new("2002-04-02T12:00:00Z")},
]
test "term equality", do: assert_term_equal(@term_equal_datetimes)
@ -311,15 +336,18 @@ defmodule RDF.EqualityTest do
describe "XSD.Date" do
@term_equal_dates [
{XSD.date("2002-04-02-01:00"), XSD.date("2002-04-02-01:00")},
{XSD.date("2002-04-02"), XSD.date("2002-04-02")}
{XSD.date("2002-04-02"), XSD.date("2002-04-02")},
{DateWithoutTz.new("2002-04-02"), DateWithoutTz.new("2002-04-02")},
]
@value_equal_dates [
{XSD.date("2002-04-02-00:00"), XSD.date("2002-04-02+00:00")},
{XSD.date("2002-04-02Z"), XSD.date("2002-04-02+00:00")},
{XSD.date("2002-04-02Z"), XSD.date("2002-04-02-00:00")}
{XSD.date("2002-04-02Z"), XSD.date("2002-04-02-00:00")},
{XSD.date("2002-04-02"), DateWithoutTz.new("2002-04-02")},
]
@unequal_dates [
{XSD.date("2002-04-01"), XSD.date("2002-04-02")}
{XSD.date("2002-04-01"), XSD.date("2002-04-02")},
{DateWithoutTz.new("2002-04-02"), DateWithoutTz.new("2002-04-01")},
]
@equal_dates_by_coercion [
{XSD.date("2002-04-02"), Date.from_iso8601!("2002-04-02")}
@ -328,11 +356,14 @@ defmodule RDF.EqualityTest do
{XSD.date("2002-04-02"), Date.from_iso8601!("2002-04-03")}
]
@equal_invalid_dates [
{XSD.date("foo"), XSD.date("foo")}
{XSD.date("foo"), XSD.date("foo")},
{DateWithoutTz.new("foo"), DateWithoutTz.new("foo")},
]
@unequal_invalid_dates [
{XSD.date("2002.04.02"), XSD.date("2002-04-02")},
{XSD.date("foo"), XSD.date("bar")}
{XSD.date("foo"), XSD.date("bar")},
{DateWithoutTz.new("foo"), DateWithoutTz.new("bar")},
{XSD.date("foo"), DateWithoutTz.new("bar")},
]
@incomparable_dates [
{XSD.date("2002-04-02"), XSD.string("2002-04-02")},
@ -402,14 +433,18 @@ defmodule RDF.EqualityTest do
describe "XSD.Time" do
@term_equal_times [
{XSD.time("12:00:00+01:00"), XSD.time("12:00:00+01:00")},
{XSD.time("12:00:00"), XSD.time("12:00:00")}
{XSD.time("12:00:00"), XSD.time("12:00:00")},
{CustomTime.new("00:00:00Z"), CustomTime.new("00:00:00Z")},
]
@value_equal_times [
{XSD.time("00:00:00+00:00"), XSD.time("00:00:00Z")}
{XSD.time("00:00:00+00:00"), XSD.time("00:00:00Z")},
{XSD.time("00:00:00+00:00"), CustomTime.new("00:00:00Z")},
{CustomTime.new("00:00:00+00:00"), CustomTime.new("00:00:00Z")},
]
@unequal_times [
{XSD.time("12:00:00"), XSD.time("13:00:00")},
{XSD.time("00:00:00.0000Z"), XSD.time("00:00:00Z")}
{XSD.time("00:00:00.0000Z"), XSD.time("00:00:00Z")},
{XSD.time("00:00:00.0000Z"), CustomTime.new("00:00:00Z")},
]
@equal_times_by_coercion [
{XSD.time("12:00:00"), Time.from_iso8601!("12:00:00")}
@ -418,14 +453,17 @@ defmodule RDF.EqualityTest do
{XSD.time("12:00:00"), Time.from_iso8601!("13:00:00")}
]
@equal_invalid_times [
{XSD.time("foo"), XSD.time("foo")}
{XSD.time("foo"), XSD.time("foo")},
{CustomTime.new("foo"), CustomTime.new("foo")},
]
@unequal_invalid_times [
{XSD.time("foo"), XSD.time("bar")}
{XSD.time("foo"), XSD.time("bar")},
{XSD.time("foo"), CustomTime.new("bar")},
]
@incomparable_times [
{XSD.time("12:00:00"), XSD.string("12:00:00")},
{XSD.time("00:00:00"), XSD.time("00:00:00Z")},
{CustomTime.new("00:00:00"), CustomTime.new("00:00:00Z")},
{XSD.time("00:00:00.0000"), XSD.time("00:00:00Z")}
]

View file

@ -0,0 +1,38 @@
defmodule RDF.XSD.DatatypeTest do
use RDF.Test.Case
alias RDF.TestDatatypes.{CustomTime, Age}
describe "most_specific/2" do
test "when equal" do
assert XSD.Datatype.most_specific(XSD.Integer, XSD.Integer) == XSD.Integer
assert XSD.Datatype.most_specific(XSD.Byte, XSD.Byte) == XSD.Byte
assert XSD.Datatype.most_specific(CustomTime, CustomTime) == CustomTime
end
test "when one is derived from the other datatype" do
%{
XSD.Byte => {XSD.Integer, XSD.Byte},
XSD.UnsignedShort => {XSD.UnsignedInt, XSD.UnsignedShort},
Age => {XSD.Integer, Age},
CustomTime => {XSD.Time, CustomTime}
}
|> Enum.each(fn {most_specific, {left, right}} ->
assert XSD.Datatype.most_specific(left, right) == most_specific
assert XSD.Datatype.most_specific(right, left) == most_specific
end)
end
test "when independent" do
[
{XSD.Double, XSD.Byte},
{XSD.NegativeInteger, XSD.UnsignedShort},
{XSD.Date, CustomTime}
]
|> Enum.each(fn {left, right} ->
refute XSD.Datatype.most_specific(left, right)
refute XSD.Datatype.most_specific(right, left)
end)
end
end
end