From 145b0b0c8fab2a669419be59cec054716a82bb84 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Mon, 7 Aug 2023 17:46:45 -0700 Subject: [PATCH 1/3] Fix list/bag deep equality --- partiql-value/src/bag.rs | 17 ++++++++++++----- partiql-value/src/lib.rs | 12 ++++++++++++ partiql-value/src/list.rs | 23 +++++++++++++++++++++-- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/partiql-value/src/bag.rs b/partiql-value/src/bag.rs index e84b2c84..0e947f1c 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, Value}; +use crate::{List, NullSortedValue, NullableEq, Value}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -163,10 +163,17 @@ impl PartialEq for Bag { if self.len() != other.len() { return false; } - - let lhs = self.0.iter().sorted(); - let rhs = other.0.iter().sorted(); - lhs.zip(rhs).all(|(l, r)| l == r) + 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; + } + } + } + } + true } } diff --git a/partiql-value/src/lib.rs b/partiql-value/src/lib.rs index 0a283120..b422c62c 100644 --- a/partiql-value/src/lib.rs +++ b/partiql-value/src/lib.rs @@ -1430,6 +1430,18 @@ mod tests { Value::from(false), NullableEq::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])) + ); + assert_eq!( + Value::from(false), + NullableEq::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])) + ); assert_eq!( Value::from(false), NullableEq::eq( diff --git a/partiql-value/src/list.rs b/partiql-value/src/list.rs index adda9a7d..d973de78 100644 --- a/partiql-value/src/list.rs +++ b/partiql-value/src/list.rs @@ -5,11 +5,11 @@ use std::hash::Hash; use std::{slice, vec}; -use crate::{Bag, NullSortedValue, Value}; +use crate::{Bag, NullSortedValue, NullableEq, Value}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -#[derive(Default, Hash, PartialEq, Eq, Clone)] +#[derive(Default, Hash, Eq, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// Represents a PartiQL List value, e.g. [1, 2, 'one'] pub struct List(Vec); @@ -149,6 +149,25 @@ impl Debug for List { } } +impl PartialEq for List { + fn eq(&self, other: &Self) -> bool { + 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; + } + } + } + } + true + } +} + impl PartialOrd for List { fn partial_cmp(&self, other: &Self) -> Option { let mut l = self.0.iter(); From 2798b6321997ab60b41e424316da7f65a663735d Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Mon, 7 Aug 2023 18:15:11 -0700 Subject: [PATCH 2/3] Resolve clippy hash warning --- partiql-value/src/list.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/partiql-value/src/list.rs b/partiql-value/src/list.rs index d973de78..fb01b342 100644 --- a/partiql-value/src/list.rs +++ b/partiql-value/src/list.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; -use std::hash::Hash; +use std::hash::{Hash, Hasher}; use std::{slice, vec}; @@ -9,7 +9,7 @@ use crate::{Bag, NullSortedValue, NullableEq, Value}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -#[derive(Default, Hash, Eq, Clone)] +#[derive(Default, Eq, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// Represents a PartiQL List value, e.g. [1, 2, 'one'] pub struct List(Vec); @@ -230,3 +230,11 @@ impl Ord for List { } } } + +impl Hash for List { + fn hash(&self, state: &mut H) { + for v in self.0.iter() { + v.hash(state); + } + } +} From d63cf7f1e0dede2c3e35e5397f4e2ef69aa62605 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Thu, 10 Aug 2023 14:10:14 -0700 Subject: [PATCH 3/3] Fix bug with null/missing equivalence, refactor neq, refactor eq/eqg logic --- CHANGELOG.md | 1 + partiql-eval/src/eval/expr/operators.rs | 12 +- partiql-value/src/bag.rs | 12 +- partiql-value/src/lib.rs | 281 +++++++++++++----------- partiql-value/src/list.rs | 14 +- partiql-value/src/tuple.rs | 16 +- 6 files changed, 180 insertions(+), 156 deletions(-) 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..c5fb6c46 100644 --- a/partiql-value/src/lib.rs +++ b/partiql-value/src/lib.rs @@ -350,7 +350,6 @@ 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; } @@ -365,54 +364,59 @@ pub trait NullableOrd { fn gteq(&self, rhs: &Self) -> Self::Output; } -impl NullableEq for Value { - type Output = Self; +/// A wrapper on [`T`] that specifies if 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> { + type Output = Value; 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 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, + _ => {} + }, + }; + + 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), + let eq_result = NullableEq::eq(self, rhs); + match eq_result { + Value::Boolean(_) | Value::Null => !eq_result, + _ => Value::Missing, } } } @@ -1395,301 +1399,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 } }