diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a35cef..22395809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixes variable resolution lookup order and excessive lookups - Fixes variable resolution of some ORDER BY variables - Fixes nested list/bag/tuple type ordering for when `ASC NULLS LAST` and `DESC NULLS FIRST` are specified +- partiql-value fix deep equality of list, bags, and tuples ## [0.5.0] - 2023-06-06 ### Changed diff --git a/partiql-eval/src/eval/expr/operators.rs b/partiql-eval/src/eval/expr/operators.rs index 3e1289d2..aeaa0398 100644 --- a/partiql-eval/src/eval/expr/operators.rs +++ b/partiql-eval/src/eval/expr/operators.rs @@ -11,7 +11,7 @@ use partiql_types::{ ArrayType, BagType, PartiqlType, StructType, TypeKind, TYPE_ANY, TYPE_BOOL, TYPE_NUMERIC_TYPES, }; use partiql_value::Value::{Boolean, Missing, Null}; -use partiql_value::{BinaryAnd, BinaryOr, NullableEq, NullableOrd, Value}; +use partiql_value::{BinaryAnd, BinaryOr, EqualityValue, NullableEq, NullableOrd, Value}; use std::borrow::{Borrow, Cow}; use std::fmt::Debug; @@ -170,8 +170,14 @@ impl BindEvalExpr for EvalOpBinary { match self { EvalOpBinary::And => logical!(AndCheck, |lhs, rhs| lhs.and(rhs)), EvalOpBinary::Or => logical!(OrCheck, |lhs, rhs| lhs.or(rhs)), - EvalOpBinary::Eq => equality!(NullableEq::eq), - EvalOpBinary::Neq => equality!(NullableEq::neq), + EvalOpBinary::Eq => equality!(|lhs, rhs| { + let wrap = EqualityValue::; + NullableEq::eq(&wrap(lhs), &wrap(rhs)) + }), + EvalOpBinary::Neq => equality!(|lhs, rhs| { + let wrap = EqualityValue::; + NullableEq::neq(&wrap(lhs), &wrap(rhs)) + }), EvalOpBinary::Gt => equality!(NullableOrd::gt), EvalOpBinary::Gteq => equality!(NullableOrd::gteq), EvalOpBinary::Lt => equality!(NullableOrd::lt), diff --git a/partiql-value/src/bag.rs b/partiql-value/src/bag.rs index 0e947f1c..993a7929 100644 --- a/partiql-value/src/bag.rs +++ b/partiql-value/src/bag.rs @@ -8,7 +8,7 @@ use std::hash::{Hash, Hasher}; use std::{slice, vec}; -use crate::{List, NullSortedValue, NullableEq, Value}; +use crate::{EqualityValue, List, NullSortedValue, NullableEq, Value}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -164,13 +164,9 @@ impl PartialEq for Bag { return false; } for (v1, v2) in self.0.iter().sorted().zip(other.0.iter().sorted()) { - match (v1, v2) { - (Value::Missing, Value::Missing) | (Value::Null, Value::Null) => continue, - (v1, v2) => { - if NullableEq::eq(v1, v2) != Value::Boolean(true) { - return false; - } - } + let wrap = EqualityValue::; + if NullableEq::eq(&wrap(v1), &wrap(v2)) != Value::Boolean(true) { + return false; } } true diff --git a/partiql-value/src/lib.rs b/partiql-value/src/lib.rs index b422c62c..4cd9245f 100644 --- a/partiql-value/src/lib.rs +++ b/partiql-value/src/lib.rs @@ -349,10 +349,8 @@ impl Comparable for Value { // `Value` `eq` and `neq` with Missing and Null propagation pub trait NullableEq { - type Output; - - fn eq(&self, rhs: &Self) -> Self::Output; - fn neq(&self, rhs: &Self) -> Self::Output; + fn eq(&self, rhs: &Self) -> Value; + fn neq(&self, rhs: &Self) -> Value; } // `Value` comparison with Missing and Null propagation @@ -365,54 +363,57 @@ pub trait NullableOrd { fn gteq(&self, rhs: &Self) -> Self::Output; } -impl NullableEq for Value { - type Output = Self; +/// A wrapper on [`T`] that specifies if a missing and null values should be equal. +#[derive(Eq, PartialEq)] +pub struct EqualityValue<'a, const NULLS_EQUAL: bool, T>(pub &'a T); + +impl<'a, const GROUP_NULLS: bool> NullableEq for EqualityValue<'a, GROUP_NULLS, Value> { + fn eq(&self, rhs: &Self) -> Value { + match GROUP_NULLS { + true => match (self.0, rhs.0) { + (Value::Missing, Value::Missing) + | (Value::Null, Value::Null) + | (Value::Missing, Value::Null) + | (Value::Null, Value::Missing) => return Value::Boolean(true), + _ => {} + }, + false => match (self.0, rhs.0) { + (Value::Missing, _) => return Value::Missing, + (_, Value::Missing) => return Value::Missing, + (Value::Null, _) => return Value::Null, + (_, Value::Null) => return Value::Null, + _ => {} + }, + }; - fn eq(&self, rhs: &Self) -> Self::Output { - match (self, rhs) { - (Value::Missing, _) => Value::Missing, - (_, Value::Missing) => Value::Missing, - (Value::Null, _) => Value::Null, - (_, Value::Null) => Value::Null, - (Value::Integer(_), Value::Real(_)) => Value::from(&coerce_int_to_real(self) == rhs), + match (self.0, rhs.0) { + (Value::Integer(_), Value::Real(_)) => { + Value::from(&coerce_int_to_real(self.0) == rhs.0) + } (Value::Integer(_), Value::Decimal(_)) => { - Value::from(&coerce_int_or_real_to_decimal(self) == rhs) + Value::from(&coerce_int_or_real_to_decimal(self.0) == rhs.0) } (Value::Real(_), Value::Decimal(_)) => { - Value::from(&coerce_int_or_real_to_decimal(self) == rhs) + Value::from(&coerce_int_or_real_to_decimal(self.0) == rhs.0) + } + (Value::Real(_), Value::Integer(_)) => { + Value::from(self.0 == &coerce_int_to_real(rhs.0)) } - (Value::Real(_), Value::Integer(_)) => Value::from(self == &coerce_int_to_real(rhs)), (Value::Decimal(_), Value::Integer(_)) => { - Value::from(self == &coerce_int_or_real_to_decimal(rhs)) + Value::from(self.0 == &coerce_int_or_real_to_decimal(rhs.0)) } (Value::Decimal(_), Value::Real(_)) => { - Value::from(self == &coerce_int_or_real_to_decimal(rhs)) + Value::from(self.0 == &coerce_int_or_real_to_decimal(rhs.0)) } - (_, _) => Value::from(self == rhs), + (_, _) => Value::from(self.0 == rhs.0), } } - fn neq(&self, rhs: &Self) -> Self::Output { - match (self, rhs) { - (Value::Missing, _) => Value::Missing, - (_, Value::Missing) => Value::Missing, - (Value::Null, _) => Value::Null, - (_, Value::Null) => Value::Null, - (Value::Integer(_), Value::Real(_)) => Value::from(&coerce_int_to_real(self) != rhs), - (Value::Integer(_), Value::Decimal(_)) => { - Value::from(&coerce_int_or_real_to_decimal(self) != rhs) - } - (Value::Real(_), Value::Decimal(_)) => { - Value::from(&coerce_int_or_real_to_decimal(self) != rhs) - } - (Value::Real(_), Value::Integer(_)) => Value::from(self != &coerce_int_to_real(rhs)), - (Value::Decimal(_), Value::Integer(_)) => { - Value::from(self != &coerce_int_or_real_to_decimal(rhs)) - } - (Value::Decimal(_), Value::Real(_)) => { - Value::from(self != &coerce_int_or_real_to_decimal(rhs)) - } - (_, _) => Value::from(self != rhs), + fn neq(&self, rhs: &Self) -> Value { + let eq_result = NullableEq::eq(self, rhs); + match eq_result { + Value::Boolean(_) | Value::Null => !eq_result, + _ => Value::Missing, } } } @@ -1395,301 +1396,310 @@ mod tests { fn partiql_value_equality() { // TODO: many equality tests missing. Can use conformance tests to fill the gap or some other // tests + + fn nullable_eq(lhs: Value, rhs: Value) -> Value { + let wrap = EqualityValue::; + let lhs = wrap(&lhs); + let rhs = wrap(&rhs); + NullableEq::eq(&lhs, &rhs) + } + + fn nullable_neq(lhs: Value, rhs: Value) -> Value { + let wrap = EqualityValue::; + let lhs = wrap(&lhs); + let rhs = wrap(&rhs); + NullableEq::neq(&lhs, &rhs) + } + // Eq assert_eq!( Value::from(true), - NullableEq::eq(&Value::from(true), &Value::from(true)) + nullable_eq(Value::from(true), Value::from(true)) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(true), &Value::from(false)) + nullable_eq(Value::from(true), Value::from(false)) ); // Container examples from spec section 7.1.1 https://partiql.org/assets/PartiQL-Specification.pdf#subsubsection.7.1.1 assert_eq!( Value::from(true), - NullableEq::eq( - &Value::from(bag![3, 2, 4, 2]), - &Value::from(bag![2, 2, 3, 4]) - ) + nullable_eq(Value::from(bag![3, 2, 4, 2]), Value::from(bag![2, 2, 3, 4])) ); assert_eq!( Value::from(true), - NullableEq::eq( - &Value::from(tuple![("a", 1), ("b", 2)]), - &Value::from(tuple![("a", 1), ("b", 2)]) + nullable_eq( + Value::from(tuple![("a", 1), ("b", 2)]), + Value::from(tuple![("a", 1), ("b", 2)]) ) ); assert_eq!( Value::from(true), - NullableEq::eq( - &Value::from(tuple![("a", list![0, 1]), ("b", 2)]), - &Value::from(tuple![("a", list![0, 1]), ("b", 2)]) + nullable_eq( + Value::from(tuple![("a", list![0, 1]), ("b", 2)]), + Value::from(tuple![("a", list![0, 1]), ("b", 2)]) ) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(bag![3, 4, 2]), &Value::from(bag![2, 2, 3, 4])) + nullable_eq(Value::from(bag![3, 4, 2]), Value::from(bag![2, 2, 3, 4])) ); assert_eq!( Value::from(true), - NullableEq::eq(&Value::from(list![1, 2]), &Value::from(list![1e0, 2.0])) + nullable_eq(Value::from(list![1, 2]), Value::from(list![1e0, 2.0])) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(list![1, 2]), &Value::from(list![2.0, 1e0])) + nullable_eq(Value::from(list![1, 2]), Value::from(list![2.0, 1e0])) ); assert_eq!( Value::from(true), - NullableEq::eq(&Value::from(bag![1, 2]), &Value::from(bag![2.0, 1e0])) + nullable_eq(Value::from(bag![1, 2]), Value::from(bag![2.0, 1e0])) ); assert_eq!( Value::from(false), - NullableEq::eq( - &Value::from(tuple![("a", 1), ("b", 2)]), - &Value::from(tuple![("a", 1)]) + nullable_eq( + Value::from(tuple![("a", 1), ("b", 2)]), + Value::from(tuple![("a", 1)]) ) ); assert_eq!( Value::from(false), - NullableEq::eq( - &Value::from(tuple![("a", list![0, 1]), ("b", 2)]), - &Value::from(tuple![("a", list![0, 1, 2]), ("b", 2)]) + nullable_eq( + Value::from(tuple![("a", list![0, 1]), ("b", 2)]), + Value::from(tuple![("a", list![0, 1, 2]), ("b", 2)]) ) ); assert_eq!( Value::from(false), - NullableEq::eq( - &Value::from(tuple![("a", 1), ("b", 2)]), - &Value::from(tuple![("a", 1), ("b", Value::Null)]) + nullable_eq( + Value::from(tuple![("a", 1), ("b", 2)]), + Value::from(tuple![("a", 1), ("b", Value::Null)]) ) ); assert_eq!( Value::from(false), - NullableEq::eq( - &Value::from(tuple![("a", list![0, 1]), ("b", 2)]), - &Value::from(tuple![("a", list![Value::Null, 1]), ("b", 2)]) + nullable_eq( + Value::from(tuple![("a", list![0, 1]), ("b", 2)]), + Value::from(tuple![("a", list![Value::Null, 1]), ("b", 2)]) ) ); - assert_eq!( - Value::Null, - NullableEq::eq(&Value::from(true), &Value::Null) - ); - assert_eq!( - Value::Null, - NullableEq::eq(&Value::Null, &Value::from(true)) - ); + assert_eq!(Value::Null, nullable_eq(Value::from(true), Value::Null)); + assert_eq!(Value::Null, nullable_eq(Value::Null, Value::from(true))); assert_eq!( Value::Missing, - NullableEq::eq(&Value::from(true), &Value::Missing) + nullable_eq(Value::from(true), Value::Missing) ); assert_eq!( Value::Missing, - NullableEq::eq(&Value::Missing, &Value::from(true)) + nullable_eq(Value::Missing, Value::from(true)) ); // different, comparable types result in boolean true assert_eq!( Value::from(true), - NullableEq::eq(&Value::from(1), &Value::from(1.0)) + nullable_eq(Value::from(1), Value::from(1.0)) ); assert_eq!( Value::from(true), - NullableEq::eq(&Value::from(1.0), &Value::from(1)) + nullable_eq(Value::from(1.0), Value::from(1)) ); assert_eq!( Value::from(true), - NullableEq::eq(&Value::from(1), &Value::from(dec!(1.0))) + nullable_eq(Value::from(1), Value::from(dec!(1.0))) ); assert_eq!( Value::from(true), - NullableEq::eq(&Value::from(dec!(1.0)), &Value::from(1)) + nullable_eq(Value::from(dec!(1.0)), Value::from(1)) ); assert_eq!( Value::from(true), - NullableEq::eq(&Value::from(1.0), &Value::from(dec!(1.0))) + nullable_eq(Value::from(1.0), Value::from(dec!(1.0))) ); assert_eq!( Value::from(true), - NullableEq::eq(&Value::from(dec!(1.0)), &Value::from(1.0)) + nullable_eq(Value::from(dec!(1.0)), Value::from(1.0)) ); // different, comparable types result in boolean false assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(1), &Value::from(2.0)) + nullable_eq(Value::from(1), Value::from(2.0)) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(1.0), &Value::from(2)) + nullable_eq(Value::from(1.0), Value::from(2)) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(1), &Value::from(dec!(2.0))) + nullable_eq(Value::from(1), Value::from(dec!(2.0))) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(dec!(1.0)), &Value::from(2)) + nullable_eq(Value::from(dec!(1.0)), Value::from(2)) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(1.0), &Value::from(dec!(2.0))) + nullable_eq(Value::from(1.0), Value::from(dec!(2.0))) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(dec!(1.0)), &Value::from(2.0)) + nullable_eq(Value::from(dec!(1.0)), Value::from(2.0)) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(1), &Value::from(f64::NEG_INFINITY)) + nullable_eq(Value::from(1), Value::from(f64::NEG_INFINITY)) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(f64::NEG_INFINITY), &Value::from(1)) + nullable_eq(Value::from(f64::NEG_INFINITY), Value::from(1)) ); // different, non-comparable types result in boolean true assert_eq!( Value::from(false), - NullableEq::eq(&Value::from(true), &Value::from("abc")) + nullable_eq(Value::from(true), Value::from("abc")) ); assert_eq!( Value::from(false), - NullableEq::eq(&Value::from("abc"), &Value::from(true)) + nullable_eq(Value::from("abc"), Value::from(true)) ); // Neq assert_eq!( Value::from(false), - Value::from(true).neq(&Value::from(true)) + nullable_neq(Value::from(true), Value::from(true)) ); assert_eq!( Value::from(true), - Value::from(true).neq(&Value::from(false)) + nullable_neq(Value::from(true), Value::from(false)) ); // Container examples from spec section 7.1.1 https://partiql.org/assets/PartiQL-Specification.pdf#subsubsection.7.1.1 // (opposite result of eq cases) assert_eq!( Value::from(false), - NullableEq::neq( - &Value::from(bag![3, 2, 4, 2]), - &Value::from(bag![2, 2, 3, 4]) - ) + nullable_neq(Value::from(bag![3, 2, 4, 2]), Value::from(bag![2, 2, 3, 4])) ); assert_eq!( Value::from(false), - NullableEq::neq( - &Value::from(tuple![("a", 1), ("b", 2)]), - &Value::from(tuple![("a", 1), ("b", 2)]) + nullable_neq( + Value::from(tuple![("a", 1), ("b", 2)]), + Value::from(tuple![("a", 1), ("b", 2)]) ) ); assert_eq!( Value::from(false), - NullableEq::neq( - &Value::from(tuple![("a", list![0, 1]), ("b", 2)]), - &Value::from(tuple![("a", list![0, 1]), ("b", 2)]) + nullable_neq( + Value::from(tuple![("a", list![0, 1]), ("b", 2)]), + Value::from(tuple![("a", list![0, 1]), ("b", 2)]) ) ); assert_eq!( Value::from(true), - NullableEq::neq(&Value::from(bag![3, 4, 2]), &Value::from(bag![2, 2, 3, 4])) + nullable_neq(Value::from(bag![3, 4, 2]), Value::from(bag![2, 2, 3, 4])) ); assert_eq!( Value::from(true), - NullableEq::neq( - &Value::from(tuple![("a", 1), ("b", 2)]), - &Value::from(tuple![("a", 1)]) + nullable_neq( + Value::from(tuple![("a", 1), ("b", 2)]), + Value::from(tuple![("a", 1)]) ) ); assert_eq!( Value::from(true), - NullableEq::neq( - &Value::from(tuple![("a", list![0, 1]), ("b", 2)]), - &Value::from(tuple![("a", list![0, 1, 2]), ("b", 2)]) + nullable_neq( + Value::from(tuple![("a", list![0, 1]), ("b", 2)]), + Value::from(tuple![("a", list![0, 1, 2]), ("b", 2)]) ) ); assert_eq!( Value::from(true), - NullableEq::neq( - &Value::from(tuple![("a", 1), ("b", 2)]), - &Value::from(tuple![("a", 1), ("b", Value::Null)]) + nullable_neq( + Value::from(tuple![("a", 1), ("b", 2)]), + Value::from(tuple![("a", 1), ("b", Value::Null)]) ) ); assert_eq!( Value::from(true), - NullableEq::neq( - &Value::from(tuple![("a", list![0, 1]), ("b", 2)]), - &Value::from(tuple![("a", list![Value::Null, 1]), ("b", 2)]) + nullable_neq( + Value::from(tuple![("a", list![0, 1]), ("b", 2)]), + Value::from(tuple![("a", list![Value::Null, 1]), ("b", 2)]) ) ); - assert_eq!(Value::Null, Value::from(true).neq(&Value::Null)); - assert_eq!(Value::Null, Value::Null.neq(&Value::from(true))); - assert_eq!(Value::Missing, Value::from(true).neq(&Value::Missing)); - assert_eq!(Value::Missing, Value::Missing.neq(&Value::from(true))); + assert_eq!(Value::Null, nullable_neq(Value::from(true), Value::Null)); + assert_eq!(Value::Null, nullable_neq(Value::Null, Value::from(true))); + assert_eq!( + Value::Missing, + nullable_neq(Value::from(true), Value::Missing) + ); + assert_eq!( + Value::Missing, + nullable_neq(Value::Missing, Value::from(true)) + ); // different, comparable types result in boolean true assert_eq!( Value::from(true), - NullableEq::neq(&Value::from(1), &Value::from(2.0)) + nullable_neq(Value::from(1), Value::from(2.0)) ); assert_eq!( Value::from(true), - NullableEq::neq(&Value::from(1.0), &Value::from(2)) + nullable_neq(Value::from(1.0), Value::from(2)) ); assert_eq!( Value::from(true), - NullableEq::neq(&Value::from(1), &Value::from(dec!(2.0))) + nullable_neq(Value::from(1), Value::from(dec!(2.0))) ); assert_eq!( Value::from(true), - NullableEq::neq(&Value::from(dec!(1.0)), &Value::from(2)) + nullable_neq(Value::from(dec!(1.0)), Value::from(2)) ); assert_eq!( Value::from(true), - NullableEq::neq(&Value::from(1.0), &Value::from(dec!(2.0))) + nullable_neq(Value::from(1.0), Value::from(dec!(2.0))) ); assert_eq!( Value::from(true), - NullableEq::neq(&Value::from(dec!(1.0)), &Value::from(2.0)) + nullable_neq(Value::from(dec!(1.0)), Value::from(2.0)) ); assert_eq!( Value::from(true), - NullableEq::neq(&Value::from(1), &Value::from(f64::NEG_INFINITY)) + nullable_neq(Value::from(1), Value::from(f64::NEG_INFINITY)) ); assert_eq!( Value::from(true), - NullableEq::neq(&Value::from(f64::NEG_INFINITY), &Value::from(1)) + nullable_neq(Value::from(f64::NEG_INFINITY), Value::from(1)) ); // different, comparable types result in boolean false assert_eq!( Value::from(false), - NullableEq::neq(&Value::from(1), &Value::from(1.0)) + nullable_neq(Value::from(1), Value::from(1.0)) ); assert_eq!( Value::from(false), - NullableEq::neq(&Value::from(1.0), &Value::from(1)) + nullable_neq(Value::from(1.0), Value::from(1)) ); assert_eq!( Value::from(false), - NullableEq::neq(&Value::from(1), &Value::from(dec!(1.0))) + nullable_neq(Value::from(1), Value::from(dec!(1.0))) ); assert_eq!( Value::from(false), - NullableEq::neq(&Value::from(dec!(1.0)), &Value::from(1)) + nullable_neq(Value::from(dec!(1.0)), Value::from(1)) ); assert_eq!( Value::from(false), - NullableEq::neq(&Value::from(1.0), &Value::from(dec!(1.0))) + nullable_neq(Value::from(1.0), Value::from(dec!(1.0))) ); assert_eq!( Value::from(false), - NullableEq::neq(&Value::from(dec!(1.0)), &Value::from(1.0)) + nullable_neq(Value::from(dec!(1.0)), Value::from(1.0)) ); // different, non-comparable types result in boolean true assert_eq!( Value::from(true), - Value::from(true).neq(&Value::from("abc")) + nullable_neq(Value::from(true), Value::from("abc")) ); assert_eq!( Value::from(true), - Value::from("abc").neq(&Value::from(true)) + nullable_neq(Value::from("abc"), Value::from(true)) ); } diff --git a/partiql-value/src/list.rs b/partiql-value/src/list.rs index fb01b342..33ef3c09 100644 --- a/partiql-value/src/list.rs +++ b/partiql-value/src/list.rs @@ -5,7 +5,7 @@ use std::hash::{Hash, Hasher}; use std::{slice, vec}; -use crate::{Bag, NullSortedValue, NullableEq, Value}; +use crate::{Bag, EqualityValue, NullSortedValue, NullableEq, Value}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -154,14 +154,10 @@ impl PartialEq for List { if self.len() != other.len() { return false; } - for (v1, v2) in self.iter().zip(other.iter()) { - match (v1, v2) { - (Value::Missing, Value::Missing) | (Value::Null, Value::Null) => continue, - (v1, v2) => { - if NullableEq::eq(v1, v2) != Value::Boolean(true) { - return false; - } - } + for (v1, v2) in self.0.iter().zip(other.0.iter()) { + let wrap = EqualityValue::; + if NullableEq::eq(&wrap(v1), &wrap(v2)) != Value::Boolean(true) { + return false; } } true diff --git a/partiql-value/src/tuple.rs b/partiql-value/src/tuple.rs index b84447bc..dc12554a 100644 --- a/partiql-value/src/tuple.rs +++ b/partiql-value/src/tuple.rs @@ -9,7 +9,7 @@ use std::vec; use unicase::UniCase; -use crate::{BindingsName, NullSortedValue, Value}; +use crate::{BindingsName, EqualityValue, NullSortedValue, NullableEq, Value}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -170,7 +170,19 @@ impl Iterator for Tuple { impl PartialEq for Tuple { fn eq(&self, other: &Self) -> bool { - self.pairs().sorted().eq(other.pairs().sorted()) + if self.vals.len() != other.vals.len() { + return false; + } + for ((ls, lv), (rs, rv)) in self.pairs().sorted().zip(other.pairs().sorted()) { + if ls != rs { + return false; + } + let wrap = EqualityValue::; + if NullableEq::eq(&wrap(lv), &wrap(rv)) != Value::Boolean(true) { + return false; + } + } + true } }