Skip to content

Commit 4792d1c

Browse files
authored
Refactor equality as per spec. eqg and add testing equality ops (#537)
1 parent 59dbbcc commit 4792d1c

File tree

10 files changed

+183
-56
lines changed

10 files changed

+183
-56
lines changed

partiql-conformance-tests/tests/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,9 @@ pub(crate) fn pass_eval(
210210
expected: &TestValue,
211211
) {
212212
match eval(statement, mode, env) {
213-
Ok(v) => assert_eq!(v.result, expected.value),
213+
Ok(v) => {
214+
assert_eq!(&TestValue::from(v), expected)
215+
},
214216
Err(TestError::Parse(_)) => {
215217
panic!("When evaluating (mode = {mode:#?}) `{statement}`, unexpected parse error")
216218
}

partiql-conformance-tests/tests/test_value.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,40 @@
1-
use partiql_value::Value;
1+
use partiql_eval::eval::Evaluated;
2+
use partiql_value::{EqualityValue, NullableEq, Value};
23

34
use partiql_extension_ion::decode::{IonDecoderBuilder, IonDecoderConfig};
45
use partiql_extension_ion::Encoding;
56

67
#[allow(dead_code)]
8+
#[derive(Debug, Ord, PartialOrd)]
79
pub(crate) struct TestValue {
810
pub value: Value,
911
}
1012

13+
impl Eq for TestValue {}
14+
15+
impl PartialEq for TestValue {
16+
fn eq(&self, other: &Self) -> bool {
17+
// When testing, we need NaN == NaN and NULL == NULL in order to assert test success properly
18+
let wrap_value = EqualityValue::<'_, true, true, Value>;
19+
NullableEq::eq(&wrap_value(&self.value), &wrap_value(&other.value)) == Value::Boolean(true)
20+
}
21+
}
22+
23+
impl From<Value> for TestValue {
24+
fn from(value: Value) -> Self {
25+
TestValue { value }
26+
}
27+
}
28+
29+
impl From<Evaluated> for TestValue {
30+
fn from(value: Evaluated) -> Self {
31+
value.result.into()
32+
}
33+
}
34+
1135
impl From<&str> for TestValue {
1236
fn from(contents: &str) -> Self {
13-
TestValue {
14-
value: parse_test_value_str(contents),
15-
}
37+
parse_test_value_str(contents).into()
1638
}
1739
}
1840

partiql-eval/src/eval/expr/operators.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,11 @@ impl BindEvalExpr for EvalOpBinary {
249249
EvalOpBinary::And => logical!(AndCheck, partiql_value::BinaryAnd::and),
250250
EvalOpBinary::Or => logical!(OrCheck, partiql_value::BinaryOr::or),
251251
EvalOpBinary::Eq => equality!(|lhs, rhs| {
252-
let wrap = EqualityValue::<false, Value>;
252+
let wrap = EqualityValue::<false, false, Value>;
253253
NullableEq::eq(&wrap(lhs), &wrap(rhs))
254254
}),
255255
EvalOpBinary::Neq => equality!(|lhs, rhs| {
256-
let wrap = EqualityValue::<false, Value>;
256+
let wrap = EqualityValue::<false, false, Value>;
257257
NullableEq::neq(&wrap(lhs), &wrap(rhs))
258258
}),
259259
EvalOpBinary::Gt => comparison!(NullableOrd::gt),

partiql-value/src/bag.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,16 +178,38 @@ impl Debug for Bag {
178178

179179
impl PartialEq for Bag {
180180
fn eq(&self, other: &Self) -> bool {
181-
if self.len() != other.len() {
182-
return false;
181+
let wrap = EqualityValue::<true, false, _>;
182+
NullableEq::eq(&wrap(self), &wrap(other)) == Value::Boolean(true)
183+
}
184+
}
185+
186+
impl<const NULLS_EQUAL: bool, const NAN_EQUAL: bool> NullableEq
187+
for EqualityValue<'_, NULLS_EQUAL, NAN_EQUAL, Bag>
188+
{
189+
#[inline(always)]
190+
fn eq(&self, other: &Self) -> Value {
191+
let ord_wrap = NullSortedValue::<'_, false, _>;
192+
let (l, r) = (self.0, other.0);
193+
if l.len() != r.len() {
194+
return Value::Boolean(false);
183195
}
184-
for (v1, v2) in self.0.iter().sorted().zip(other.0.iter().sorted()) {
185-
let wrap = EqualityValue::<true, Value>;
186-
if NullableEq::eq(&wrap(v1), &wrap(v2)) != Value::Boolean(true) {
187-
return false;
196+
197+
let li = l.iter().map(ord_wrap).sorted().map(|nsv| nsv.0);
198+
let ri = r.iter().map(ord_wrap).sorted().map(|nsv| nsv.0);
199+
200+
for (v1, v2) in li.zip(ri) {
201+
let wrap = EqualityValue::<{ NULLS_EQUAL }, { NAN_EQUAL }, Value>;
202+
if NullableEq::eqg(&wrap(v1), &wrap(v2)) != Value::Boolean(true) {
203+
return Value::Boolean(false);
188204
}
189205
}
190-
true
206+
Value::Boolean(true)
207+
}
208+
209+
#[inline(always)]
210+
fn eqg(&self, rhs: &Self) -> Value {
211+
let wrap = EqualityValue::<'_, true, { NAN_EQUAL }, _>;
212+
NullableEq::eq(&wrap(self.0), &wrap(rhs.0))
191213
}
192214
}
193215

partiql-value/src/comparison.rs

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::util;
21
use crate::Value;
2+
use crate::{util, Bag, List, Tuple};
33

44
pub trait Comparable {
55
fn is_comparable_to(&self, rhs: &Self) -> bool;
@@ -16,6 +16,7 @@ impl Comparable for Value {
1616
| (Value::Boolean(_), Value::Boolean(_))
1717
| (Value::String(_), Value::String(_))
1818
| (Value::Blob(_), Value::Blob(_))
19+
| (Value::DateTime(_), Value::DateTime(_))
1920
| (Value::List(_), Value::List(_))
2021
| (Value::Bag(_), Value::Bag(_))
2122
| (Value::Tuple(_), Value::Tuple(_))
@@ -31,19 +32,43 @@ impl Comparable for Value {
3132

3233
// `Value` `eq` and `neq` with Missing and Null propagation
3334
pub trait NullableEq {
34-
type Output;
35-
fn eq(&self, rhs: &Self) -> Self::Output;
36-
fn neq(&self, rhs: &Self) -> Self::Output;
37-
}
35+
fn eq(&self, rhs: &Self) -> Value;
3836

39-
/// A wrapper on [`T`] that specifies if missing and null values should be equal.
40-
#[derive(Eq, PartialEq)]
41-
pub struct EqualityValue<'a, const NULLS_EQUAL: bool, T>(pub &'a T);
37+
fn neq(&self, rhs: &Self) -> Value {
38+
let eq_result = NullableEq::eq(self, rhs);
39+
match eq_result {
40+
Value::Boolean(_) | Value::Null => !eq_result,
41+
_ => Value::Missing,
42+
}
43+
}
4244

43-
impl<const GROUP_NULLS: bool> NullableEq for EqualityValue<'_, GROUP_NULLS, Value> {
44-
type Output = Value;
45+
/// `PartiQL's `eqg` is used to compare the internals of Lists, Bags, and Tuples.
46+
///
47+
/// > The eqg, unlike the =, returns true when a NULL is compared to a NULL or a MISSING
48+
/// > to a MISSING
49+
fn eqg(&self, rhs: &Self) -> Value;
4550

46-
fn eq(&self, rhs: &Self) -> Self::Output {
51+
fn neqg(&self, rhs: &Self) -> Value {
52+
let eqg_result = NullableEq::eqg(self, rhs);
53+
match eqg_result {
54+
Value::Boolean(_) | Value::Null => !eqg_result,
55+
_ => Value::Missing,
56+
}
57+
}
58+
}
59+
60+
/// A wrapper on [`T`] that specifies equality outcome for missing and null, and `NaN` values.
61+
#[derive(Eq, PartialEq, Debug)]
62+
pub struct EqualityValue<'a, const NULLS_EQUAL: bool, const NAN_EQUAL: bool, T>(pub &'a T);
63+
64+
impl<const GROUP_NULLS: bool, const NAN_EQUAL: bool> NullableEq
65+
for EqualityValue<'_, GROUP_NULLS, NAN_EQUAL, Value>
66+
{
67+
#[inline(always)]
68+
fn eq(&self, rhs: &Self) -> Value {
69+
let wrap_list = EqualityValue::<'_, { GROUP_NULLS }, { NAN_EQUAL }, List>;
70+
let wrap_bag = EqualityValue::<'_, { GROUP_NULLS }, { NAN_EQUAL }, Bag>;
71+
let wrap_tuple = EqualityValue::<'_, { GROUP_NULLS }, { NAN_EQUAL }, Tuple>;
4772
if GROUP_NULLS {
4873
if let (Value::Missing | Value::Null, Value::Missing | Value::Null) = (self.0, rhs.0) {
4974
return Value::Boolean(true);
@@ -73,16 +98,23 @@ impl<const GROUP_NULLS: bool> NullableEq for EqualityValue<'_, GROUP_NULLS, Valu
7398
(Value::Decimal(_), Value::Real(_)) => {
7499
Value::from(self.0 == &util::coerce_int_or_real_to_decimal(rhs.0))
75100
}
101+
(Value::Real(l), Value::Real(r)) => {
102+
if NAN_EQUAL && l.is_nan() && r.is_nan() {
103+
return Value::Boolean(true);
104+
}
105+
Value::from(l == r)
106+
}
107+
(Value::List(l), Value::List(r)) => NullableEq::eq(&wrap_list(l), &wrap_list(r)),
108+
(Value::Bag(l), Value::Bag(r)) => NullableEq::eq(&wrap_bag(l), &wrap_bag(r)),
109+
(Value::Tuple(l), Value::Tuple(r)) => NullableEq::eq(&wrap_tuple(l), &wrap_tuple(r)),
76110
(_, _) => Value::from(self.0 == rhs.0),
77111
}
78112
}
79113

80-
fn neq(&self, rhs: &Self) -> Self::Output {
81-
let eq_result = NullableEq::eq(self, rhs);
82-
match eq_result {
83-
Value::Boolean(_) | Value::Null => !eq_result,
84-
_ => Value::Missing,
85-
}
114+
#[inline(always)]
115+
fn eqg(&self, rhs: &Self) -> Value {
116+
let wrap = EqualityValue::<'_, true, { NAN_EQUAL }, _>;
117+
NullableEq::eq(&wrap(self.0), &wrap(rhs.0))
86118
}
87119
}
88120

partiql-value/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ mod tests {
8585

8686
#[test]
8787
fn iterators() {
88-
let bag: Bag = [1, 10, 3, 4].iter().collect();
88+
let bag: Bag = [1, 10, 3, 4].into_iter().collect();
8989
assert_eq!(bag.len(), 4);
9090
let max = bag
9191
.iter()
9292
.fold(Value::Integer(0), |x, y| if y > &x { y.clone() } else { x });
9393
assert_eq!(max, Value::Integer(10));
9494
let _bref = Value::from(bag).as_bag_ref();
9595

96-
let list: List = [1, 2, 3, -4].iter().collect();
96+
let list: List = [1, 2, 3, -4].into_iter().collect();
9797
assert_eq!(list.len(), 4);
9898
let max = list
9999
.iter()
@@ -442,14 +442,14 @@ mod tests {
442442
// tests
443443

444444
fn nullable_eq(lhs: Value, rhs: Value) -> Value {
445-
let wrap = EqualityValue::<false, Value>;
445+
let wrap = EqualityValue::<false, false, Value>;
446446
let lhs = wrap(&lhs);
447447
let rhs = wrap(&rhs);
448448
NullableEq::eq(&lhs, &rhs)
449449
}
450450

451451
fn nullable_neq(lhs: Value, rhs: Value) -> Value {
452-
let wrap = EqualityValue::<false, Value>;
452+
let wrap = EqualityValue::<false, false, Value>;
453453
let lhs = wrap(&lhs);
454454
let rhs = wrap(&rhs);
455455
NullableEq::neq(&lhs, &rhs)

partiql-value/src/list.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,32 @@ impl Debug for List {
175175

176176
impl PartialEq for List {
177177
fn eq(&self, other: &Self) -> bool {
178-
if self.len() != other.len() {
179-
return false;
178+
let wrap = EqualityValue::<true, false, _>;
179+
NullableEq::eq(&wrap(self), &wrap(other)) == Value::Boolean(true)
180+
}
181+
}
182+
183+
impl<const NULLS_EQUAL: bool, const NAN_EQUAL: bool> NullableEq
184+
for EqualityValue<'_, NULLS_EQUAL, NAN_EQUAL, List>
185+
{
186+
#[inline(always)]
187+
fn eq(&self, other: &Self) -> Value {
188+
if self.0.len() != other.0.len() {
189+
return Value::Boolean(false);
180190
}
181191
for (v1, v2) in self.0.iter().zip(other.0.iter()) {
182-
let wrap = EqualityValue::<true, Value>;
183-
if NullableEq::eq(&wrap(v1), &wrap(v2)) != Value::Boolean(true) {
184-
return false;
192+
let wrap = EqualityValue::<{ NULLS_EQUAL }, { NAN_EQUAL }, Value>;
193+
if NullableEq::eqg(&wrap(v1), &wrap(v2)) != Value::Boolean(true) {
194+
return Value::Boolean(false);
185195
}
186196
}
187-
true
197+
Value::Boolean(true)
198+
}
199+
200+
#[inline(always)]
201+
fn eqg(&self, rhs: &Self) -> Value {
202+
let wrap = EqualityValue::<'_, true, { NAN_EQUAL }, _>;
203+
NullableEq::eq(&wrap(self.0), &wrap(rhs.0))
188204
}
189205
}
190206

partiql-value/src/sort.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ where
1818

1919
impl<const NULLS_FIRST: bool> Ord for NullSortedValue<'_, NULLS_FIRST, Value> {
2020
fn cmp(&self, other: &Self) -> Ordering {
21-
let wrap_list = NullSortedValue::<{ NULLS_FIRST }, List>;
22-
let wrap_tuple = NullSortedValue::<{ NULLS_FIRST }, Tuple>;
23-
let wrap_bag = NullSortedValue::<{ NULLS_FIRST }, Bag>;
21+
let wrap_list = NullSortedValue::<'_, { NULLS_FIRST }, List>;
22+
let wrap_tuple = NullSortedValue::<'_, { NULLS_FIRST }, Tuple>;
23+
let wrap_bag = NullSortedValue::<'_, { NULLS_FIRST }, Bag>;
2424
let null_cond = |order: Ordering| {
2525
if NULLS_FIRST {
2626
order

partiql-value/src/tuple.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,35 @@ impl Iterator for Tuple {
213213

214214
impl PartialEq for Tuple {
215215
fn eq(&self, other: &Self) -> bool {
216-
if self.vals.len() != other.vals.len() {
217-
return false;
216+
let wrap = EqualityValue::<true, false, _>;
217+
NullableEq::eq(&wrap(self), &wrap(other)) == Value::Boolean(true)
218+
}
219+
}
220+
221+
impl<const NULLS_EQUAL: bool, const NAN_EQUAL: bool> NullableEq
222+
for EqualityValue<'_, NULLS_EQUAL, NAN_EQUAL, Tuple>
223+
{
224+
#[inline(always)]
225+
fn eq(&self, other: &Self) -> Value {
226+
if self.0.vals.len() != other.0.vals.len() {
227+
return Value::Boolean(false);
218228
}
219-
for ((ls, lv), (rs, rv)) in self.pairs().sorted().zip(other.pairs().sorted()) {
229+
for ((ls, lv), (rs, rv)) in self.0.pairs().sorted().zip(other.0.pairs().sorted()) {
220230
if ls != rs {
221-
return false;
231+
return Value::Boolean(false);
222232
}
223-
let wrap = EqualityValue::<true, Value>;
224-
if NullableEq::eq(&wrap(lv), &wrap(rv)) != Value::Boolean(true) {
225-
return false;
233+
let wrap = EqualityValue::<{ NULLS_EQUAL }, { NAN_EQUAL }, Value>;
234+
if NullableEq::eqg(&wrap(lv), &wrap(rv)) != Value::Boolean(true) {
235+
return Value::Boolean(false);
226236
}
227237
}
228-
true
238+
Value::Boolean(true)
239+
}
240+
241+
#[inline(always)]
242+
fn eqg(&self, rhs: &Self) -> Value {
243+
let wrap = EqualityValue::<'_, true, { NAN_EQUAL }, _>;
244+
NullableEq::eq(&wrap(self.0), &wrap(rhs.0))
229245
}
230246
}
231247

partiql-value/src/value.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,13 @@ impl From<&str> for Value {
378378
}
379379
}
380380

381+
impl From<i128> for Value {
382+
#[inline]
383+
fn from(n: i128) -> Self {
384+
Value::from(RustDecimal::from(n))
385+
}
386+
}
387+
381388
impl From<i64> for Value {
382389
#[inline]
383390
fn from(n: i64) -> Self {
@@ -409,8 +416,11 @@ impl From<i8> for Value {
409416
impl From<usize> for Value {
410417
#[inline]
411418
fn from(n: usize) -> Self {
412-
// TODO overflow to bigint/decimal
413-
Value::Integer(n as i64)
419+
if n > i64::MAX as usize {
420+
Value::from(RustDecimal::from(n))
421+
} else {
422+
Value::Integer(n as i64)
423+
}
414424
}
415425
}
416426

@@ -445,14 +455,21 @@ impl From<u64> for Value {
445455
impl From<u128> for Value {
446456
#[inline]
447457
fn from(n: u128) -> Self {
448-
(n as usize).into()
458+
Value::from(RustDecimal::from(n))
449459
}
450460
}
451461

452462
impl From<f64> for Value {
453463
#[inline]
454464
fn from(f: f64) -> Self {
455-
Value::Real(OrderedFloat(f))
465+
Value::from(OrderedFloat(f))
466+
}
467+
}
468+
469+
impl From<OrderedFloat<f64>> for Value {
470+
#[inline]
471+
fn from(f: OrderedFloat<f64>) -> Self {
472+
Value::Real(f)
456473
}
457474
}
458475

0 commit comments

Comments
 (0)