Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions vortex-array/src/arrays/decimal/compute/between.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
use vortex_buffer::BitBuffer;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;

use crate::ArrayRef;
use crate::ExecutionCtx;
Expand Down Expand Up @@ -57,20 +56,18 @@ fn between_unpack<T: NativeDecimalType>(
.decimal_value()
.and_then(|v| v.cast::<T>())
else {
vortex_bail!(
"invalid lower bound Scalar: {lower}, expected {:?}",
T::DECIMAL_TYPE
)
// The bound's backing integer value doesn't fit in T (the array's storage type). This
// can happen when the bound scalar uses a wider decimal storage type than the array,
// even though both share the same precision and scale. Signal to the caller to fall
// back to the generic Arrow-based comparison, which handles mixed storage types.
return Ok(None);
};
let Some(upper_value) = upper
.as_decimal()
.decimal_value()
.and_then(|v| v.cast::<T>())
else {
vortex_bail!(
"invalid upper bound Scalar: {upper}, expected {:?}",
T::DECIMAL_TYPE
)
return Ok(None);
};

let lower_op = match options.lower_strict {
Expand Down
68 changes: 68 additions & 0 deletions vortex-array/src/scalar_fn/fns/between/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ mod tests {
use rstest::rstest;
use vortex_buffer::buffer;

use vortex_error::VortexResult;

use super::*;
use crate::IntoArray;
use crate::VortexSessionExecute;
Expand Down Expand Up @@ -545,4 +547,70 @@ mod tests {
BoolArray::from_iter([true, true, true, false])
);
}

/// Regression test: between on a decimal array whose storage type is narrower than the
/// bound scalars' backing integer type must not crash.
///
/// The fuzzer discovered that a DecimalArray with `values_type = I16` (i16 backing) can
/// receive bound scalars stored as `DecimalValue::I32` when both share the same precision
/// and scale. The old code called `vortex_bail!` when the cast from i32 to i16 failed,
/// propagating an error that the fuzz harness turned into a panic.
#[test]
fn test_between_decimal_mismatched_storage_types() -> VortexResult<()> {
// Array backed by i16, with precision=5 and a negative scale.
// DecimalArray allows narrower-than-canonical backing when all values fit.
let values = buffer![100i16, 200i16, 300i16, 400i16];
let decimal_type = DecimalDType::new(5, -3);
let array = DecimalArray::new(values, decimal_type, Validity::NonNullable).into_array();

// Lower bound stored as I32(100) – fits in i16, but the backing type differs.
let lower = ConstantArray::new(
Scalar::decimal(DecimalValue::I32(100i32), decimal_type, Nullability::NonNullable),
array.len(),
)
.into_array();

// Upper bound stored as I32(82246) – does NOT fit in i16 (> i16::MAX = 32767).
// All array values are <= 32767 < 82246, so every element satisfies the upper bound.
let upper = ConstantArray::new(
Scalar::decimal(DecimalValue::I32(82246i32), decimal_type, Nullability::NonNullable),
array.len(),
)
.into_array();

let result = between_canonical(
&array,
&lower,
&upper,
&BetweenOptions {
lower_strict: StrictComparison::NonStrict,
upper_strict: StrictComparison::NonStrict,
},
&mut SESSION.create_execution_ctx(),
)?;
// 100 <= [100,200,300,400] <= 82246 → all true
assert_arrays_eq!(result, BoolArray::from_iter([true, true, true, true]));

// Verify that values outside the lower bound are correctly excluded.
let lower_high = ConstantArray::new(
Scalar::decimal(DecimalValue::I32(250i32), decimal_type, Nullability::NonNullable),
array.len(),
)
.into_array();

let result2 = between_canonical(
&array,
&lower_high,
&upper,
&BetweenOptions {
lower_strict: StrictComparison::NonStrict,
upper_strict: StrictComparison::NonStrict,
},
&mut SESSION.create_execution_ctx(),
)?;
// 250 <= [100,200,300,400] <= 82246 → [false, false, true, true]
assert_arrays_eq!(result2, BoolArray::from_iter([false, false, true, true]));

Ok(())
}
}
Loading