use std::{str::FromStr, sync::Arc};
use arrow_array::{Array, ArrayRef, BooleanArray, Float32Array, Float64Array};
use arrow_ord::cmp;
use arrow_schema::FieldRef;
use reifydb_core::value::column::factory;
use reifydb_evaluate::expression::compare::{
CompareOp, Equal, GreaterThan, GreaterThanEqual, LessThan, LessThanEqual, NotEqual, compare_columns,
};
use reifydb_value::{
Result,
error::Diagnostic,
fragment::Fragment,
value::{
column_view::{ColumnView, ViewData},
constraint::{precision::Precision, scale::Scale},
date::Date,
datetime::DateTime,
decimal::Decimal,
duration::Duration,
uuid::parse::parse_uuid7,
value_type::{
ValueType,
field::{FieldType, named},
},
},
};
fn compare<Op: CompareOp>(left: (FieldRef, ArrayRef), right: (FieldRef, ArrayRef)) -> (FieldRef, ArrayRef) {
compare_columns::<Op>(
&factory::rename(left, "left"),
&factory::rename(right, "right"),
Fragment::testing_empty(),
|_, l: ValueType, r: ValueType| -> Diagnostic { panic!("no comparison between {l:?} and {r:?}") },
)
.unwrap()
}
fn try_compare<Op: CompareOp>(left: (FieldRef, ArrayRef), right: (FieldRef, ArrayRef)) -> Result<(FieldRef, ArrayRef)> {
compare_columns::<Op>(
&factory::rename(left, "left"),
&factory::rename(right, "right"),
Fragment::testing_empty(),
|_, l: ValueType, r: ValueType| -> Diagnostic {
Diagnostic {
code: "TEST".to_string(),
message: format!("no comparison between {l:?} and {r:?}"),
..Default::default()
}
},
)
}
fn bools(column: (FieldRef, ArrayRef)) -> Vec<Option<bool>> {
let view = ColumnView::try_from(&column).unwrap();
let ViewData::Bool(a) = &view.data else {
panic!("comparison must return a bool column, got {:?}", view.get_type())
};
(0..a.len())
.map(|i| {
if a.is_null(i) {
None
} else {
Some(a.value(i))
}
})
.collect()
}
#[test]
fn nan_equals_nan() {
let result = compare::<Equal>(factory::float8("", [f64::NAN]), factory::float8("", [f64::NAN]));
assert_eq!(bools(result), vec![Some(true)]);
}
#[test]
fn nan_above_every_number() {
let result = compare::<GreaterThan>(factory::float8("", [f64::NAN]), factory::float8("", [1.0]));
assert_eq!(bools(result), vec![Some(true)]);
}
#[test]
fn negative_zero_equals_zero() {
let result = compare::<Equal>(factory::float8("", [-0.0]), factory::float8("", [0.0]));
assert_eq!(bools(result), vec![Some(true)]);
}
#[test]
fn float4_nan_equals_float8_nan() {
let result = compare::<Equal>(factory::float4("", [f32::NAN]), factory::float8("", [f64::NAN]));
assert_eq!(bools(result), vec![Some(true)]);
}
#[test]
fn false_below_true() {
let result = compare::<LessThan>(factory::bool("", [false, true]), factory::bool("", [true, true]));
assert_eq!(bools(result), vec![Some(true), Some(false)]);
}
#[test]
fn int4_column_vs_one_row_int1() {
let result = compare::<Equal>(factory::int4("", [4, 5, 6]), factory::int1("", [5]));
assert_eq!(bools(result), vec![Some(false), Some(true), Some(false)]);
}
#[test]
fn one_row_left_side() {
let result = compare::<LessThan>(factory::int1("", [5]), factory::int4("", [4, 5, 6]));
assert_eq!(bools(result), vec![Some(false), Some(false), Some(true)]);
}
#[test]
fn signed_vs_unsigned_never_wraps() {
let result = compare::<LessThan>(factory::int8("", [-1]), factory::uint8("", [u64::MAX]));
assert_eq!(bools(result), vec![Some(true)]);
}
#[test]
fn i128_vs_u128_extremes() {
let result = compare::<LessThan>(
factory::int16("", [i128::MIN, i128::MAX]),
factory::uint16("", [u128::MAX, u128::MAX]),
);
assert_eq!(bools(result), vec![Some(true), Some(true)]);
}
#[test]
fn uuid7_order() {
let earlier = parse_uuid7(Fragment::internal("01890a5d-ac96-774b-bcce-b302099a8057")).unwrap();
let later = parse_uuid7(Fragment::internal("01890a5d-ac97-774b-bcce-b302099a8057")).unwrap();
let result = compare::<LessThan>(factory::uuid7("", [earlier]), factory::uuid7("", [later]));
assert_eq!(bools(result), vec![Some(true)]);
}
#[test]
fn duration_months_before_days() {
let result = compare::<GreaterThan>(
factory::duration("", [Duration::from_months(1).unwrap()]),
factory::duration("", [Duration::from_days(31).unwrap()]),
);
assert_eq!(bools(result), vec![Some(true)]);
}
#[test]
fn none_on_either_side() {
let result =
compare::<Equal>(factory::int4_with_bitvec("", [1, 2], vec![true, false]), factory::int4("", [1, 2]));
assert_eq!(bools(result), vec![Some(true), None]);
}
#[test]
fn untyped_none() {
let result = compare::<Equal>(factory::none_typed("", ValueType::Any, 2), factory::int4("", [1, 2]));
assert_eq!(bools(result), vec![None, None]);
}
#[test]
fn decimal_vs_int4() {
let result = compare::<GreaterThan>(
factory::decimal("", Precision::new(2), Scale::new(1), [Decimal::from_str("1.5").unwrap()]),
factory::int4("", [1]),
);
assert_eq!(bools(result), vec![Some(true)]);
}
#[test]
fn date_vs_datetime() {
let result = try_compare::<LessThan>(
factory::date("", vec![Date::new(2024, 1, 1).unwrap()]),
factory::datetime("", vec![DateTime::from_nanos(0)]),
);
assert!(result.is_err(), "date vs datetime must be a type error, got {result:?}");
}
#[test]
fn length_mismatch() {
let result = try_compare::<Equal>(factory::int4("", [1, 2]), factory::int4("", [1, 2, 3]));
assert!(result.is_err(), "length mismatch must be an error, got {result:?}");
}
const NEGATIVE_NAN: f64 = f64::from_bits(0xfff8_0000_0000_0000);
fn floats<Op: CompareOp>(left: &[f64], right: &[f64]) -> Vec<Option<bool>> {
bools(compare::<Op>(factory::float8("", left.to_vec()), factory::float8("", right.to_vec())))
}
#[test]
fn every_operator_places_nan_above_every_number() {
let left = [f64::NAN, f64::NAN, 1.0, f64::INFINITY];
let right = [f64::NAN, 1.0, f64::NAN, f64::NAN];
assert_eq!(floats::<Equal>(&left, &right), vec![Some(true), Some(false), Some(false), Some(false)]);
assert_eq!(floats::<NotEqual>(&left, &right), vec![Some(false), Some(true), Some(true), Some(true)]);
assert_eq!(floats::<LessThan>(&left, &right), vec![Some(false), Some(false), Some(true), Some(true)]);
assert_eq!(floats::<LessThanEqual>(&left, &right), vec![Some(true), Some(false), Some(true), Some(true)]);
assert_eq!(floats::<GreaterThan>(&left, &right), vec![Some(false), Some(true), Some(false), Some(false)]);
assert_eq!(floats::<GreaterThanEqual>(&left, &right), vec![Some(true), Some(true), Some(false), Some(false)]);
}
#[test]
fn a_nan_with_the_sign_bit_set_is_still_the_largest_value() {
let left = [NEGATIVE_NAN, NEGATIVE_NAN, NEGATIVE_NAN];
let right = [f64::NAN, f64::NEG_INFINITY, f64::MAX];
assert_eq!(floats::<Equal>(&left, &right), vec![Some(true), Some(false), Some(false)]);
assert_eq!(floats::<GreaterThan>(&left, &right), vec![Some(false), Some(true), Some(true)]);
assert_eq!(floats::<LessThan>(&left, &right), vec![Some(false), Some(false), Some(false)]);
}
#[test]
fn negative_zero_is_neither_below_nor_above_zero() {
let left = [-0.0, 0.0];
let right = [0.0, -0.0];
assert_eq!(floats::<LessThan>(&left, &right), vec![Some(false), Some(false)]);
assert_eq!(floats::<GreaterThan>(&left, &right), vec![Some(false), Some(false)]);
assert_eq!(floats::<LessThanEqual>(&left, &right), vec![Some(true), Some(true)]);
assert_eq!(floats::<NotEqual>(&left, &right), vec![Some(false), Some(false)]);
}
#[test]
fn a_one_row_float_side_is_compared_against_every_row() {
let column = || factory::float8("", [1.0, f64::NAN, 3.0]);
let one = || factory::float8("", [f64::NAN]);
assert_eq!(bools(compare::<Equal>(column(), one())), vec![Some(false), Some(true), Some(false)]);
assert_eq!(bools(compare::<LessThan>(one(), column())), vec![Some(false), Some(false), Some(false)]);
assert_eq!(bools(compare::<LessThan>(column(), one())), vec![Some(true), Some(false), Some(true)]);
}
#[test]
fn a_none_float_row_gives_none_on_either_side_and_with_a_one_row_side() {
let with_none = || factory::float8_with_bitvec("", [1.0, f64::NAN], vec![true, false]);
let result = compare::<Equal>(with_none(), factory::float8("", [1.0, f64::NAN]));
assert_eq!(bools(result), vec![Some(true), None]);
let result = compare::<Equal>(factory::float8("", [1.0, f64::NAN]), with_none());
assert_eq!(bools(result), vec![Some(true), None]);
let result = compare::<Equal>(with_none(), factory::float8("", [1.0]));
assert_eq!(bools(result), vec![Some(true), None]);
let result = compare::<Equal>(factory::float8("", [1.0]), with_none());
assert_eq!(bools(result), vec![Some(true), None]);
}
#[test]
fn float4_columns_follow_the_same_rules_without_a_cast() {
let left = factory::float4("", [-0.0, f32::NAN, 1.0]);
let right = factory::float4("", [0.0, f32::NAN, f32::NAN]);
assert_eq!(bools(compare::<Equal>(left.clone(), right.clone())), vec![Some(true), Some(true), Some(false)]);
assert_eq!(bools(compare::<LessThan>(left, right)), vec![Some(false), Some(false), Some(true)]);
}
#[test]
fn an_int_against_a_float_uses_the_float_rules() {
let result = compare::<LessThan>(factory::int4("", [1, 2]), factory::float8("", [f64::NAN, 1.5]));
assert_eq!(bools(result), vec![Some(true), Some(false)]);
}
const NEGATIVE_NAN_F32: f32 = f32::from_bits(0xffc0_0000);
fn raw(ty: ValueType, array: ArrayRef) -> (FieldRef, ArrayRef) {
named("", FieldType::from(ty), array)
}
fn kernel(result: BooleanArray) -> Vec<Option<bool>> {
result.iter().collect()
}
fn assert_matches_kernel(ty: ValueType, left: ArrayRef, right: ArrayRef) {
let l = || raw(ty.clone(), left.clone());
let r = || raw(ty.clone(), right.clone());
assert_eq!(bools(compare::<Equal>(l(), r())), kernel(cmp::eq(&left, &right).unwrap()));
assert_eq!(bools(compare::<NotEqual>(l(), r())), kernel(cmp::neq(&left, &right).unwrap()));
assert_eq!(bools(compare::<LessThan>(l(), r())), kernel(cmp::lt(&left, &right).unwrap()));
assert_eq!(bools(compare::<LessThanEqual>(l(), r())), kernel(cmp::lt_eq(&left, &right).unwrap()));
assert_eq!(bools(compare::<GreaterThan>(l(), r())), kernel(cmp::gt(&left, &right).unwrap()));
assert_eq!(bools(compare::<GreaterThanEqual>(l(), r())), kernel(cmp::gt_eq(&left, &right).unwrap()));
}
#[test]
fn raw_float8_columns_compare_exactly_like_the_arrow_kernel() {
let left: ArrayRef =
Arc::new(Float64Array::from(vec![-0.0, 0.0, NEGATIVE_NAN, NEGATIVE_NAN, -0.0, NEGATIVE_NAN]));
let right: ArrayRef =
Arc::new(Float64Array::from(vec![0.0, -0.0, f64::NAN, f64::NEG_INFINITY, -0.0, NEGATIVE_NAN]));
assert_matches_kernel(ValueType::Float8, left, right);
}
#[test]
fn raw_float4_columns_compare_exactly_like_the_arrow_kernel() {
let left: ArrayRef = Arc::new(Float32Array::from(vec![
-0.0,
0.0,
NEGATIVE_NAN_F32,
NEGATIVE_NAN_F32,
-0.0,
NEGATIVE_NAN_F32,
]));
let right: ArrayRef =
Arc::new(Float32Array::from(vec![0.0, -0.0, f32::NAN, f32::NEG_INFINITY, -0.0, NEGATIVE_NAN_F32]));
assert_matches_kernel(ValueType::Float4, left, right);
}
#[test]
fn a_raw_float4_negative_nan_equals_a_float8_nan() {
let float4 = raw(ValueType::Float4, Arc::new(Float32Array::from(vec![NEGATIVE_NAN_F32])));
let result = compare::<Equal>(float4, factory::float8("", [f64::NAN]));
assert_eq!(bools(result), vec![Some(true)]);
}