diff --git a/vortex-array/src/arrays/constant/compute/between.rs b/vortex-array/src/arrays/constant/compute/between.rs index d79dff2fc17..25101b3a9b6 100644 --- a/vortex-array/src/arrays/constant/compute/between.rs +++ b/vortex-array/src/arrays/constant/compute/between.rs @@ -8,6 +8,7 @@ use crate::IntoArray; use crate::array::ArrayView; use crate::arrays::Constant; use crate::arrays::ConstantArray; +use crate::dtype::DType; use crate::scalar::Scalar; use crate::scalar_fn::fns::between::BetweenOptions; use crate::scalar_fn::fns::between::BetweenReduce; @@ -24,6 +25,19 @@ impl BetweenReduce for Constant { .zip(lower.as_constant()) .zip(upper.as_constant()) { + let nullability = array.dtype().nullability() + | lower.dtype().nullability() + | upper.dtype().nullability(); + + // `Scalar` orders null below every value, so comparing a null constant would give a + // non-null false. The result is null instead. + if constant.is_null() { + return Ok(Some( + ConstantArray::new(Scalar::null(DType::Bool(nullability)), array.len()) + .into_array(), + )); + } + let BetweenOptions { lower_strict, upper_strict, @@ -43,12 +57,7 @@ impl BetweenReduce for Constant { let result = lower_result && upper_result; - let scalar = Scalar::bool( - result, - array.dtype().nullability() - | lower.dtype().nullability() - | upper.dtype().nullability(), - ); + let scalar = Scalar::bool(result, nullability); return Ok(Some(ConstantArray::new(scalar, array.len()).into_array())); } diff --git a/vortex-array/src/scalar_fn/fns/between/mod.rs b/vortex-array/src/scalar_fn/fns/between/mod.rs index adc269b40fc..b7727edb6c4 100644 --- a/vortex-array/src/scalar_fn/fns/between/mod.rs +++ b/vortex-array/src/scalar_fn/fns/between/mod.rs @@ -618,6 +618,36 @@ mod tests { Ok(()) } + /// Compression stores an all-null chunk as a [`ConstantArray`]. Its rows must come out null + /// rather than false, since `NOT` turns a false row into a match. + #[rstest] + #[case::f64(Scalar::null_native::(), Scalar::from(-0.0f64), Scalar::from(0.0f64))] + #[case::i32(Scalar::null_native::(), Scalar::from(0i32), Scalar::from(0i32))] + fn constant_null_array( + #[case] null: Scalar, + #[case] lower: Scalar, + #[case] upper: Scalar, + ) -> VortexResult<()> { + let ctx = &mut SESSION.create_execution_ctx(); + let array = ConstantArray::new(null, 3).into_array(); + let lower = ConstantArray::new(lower, 3).into_array(); + let upper = ConstantArray::new(upper, 3).into_array(); + + let between = array.between(lower, upper, NON_STRICT)?; + let not_between = between.not()?; + + assert_eq!( + between.execute::(ctx)?.opt_bool_vec(ctx), + [None, None, None] + ); + assert_eq!( + not_between.execute::(ctx)?.opt_bool_vec(ctx), + [None, None, None] + ); + + Ok(()) + } + #[test] fn test_between_decimal() { let ctx = &mut SESSION.create_execution_ctx();