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
18 changes: 10 additions & 8 deletions vortex-array/src/aggregate_fn/fns/min_max/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ where
.validity()?
.execute_mask(array.as_ref().len(), ctx)?
{
Mask::AllTrue(_) => compute_min_max(array.buffer::<D>().iter(), array.decimal_dtype()),
Mask::AllTrue(_) => compute_min_max(array.buffer::<D>().iter(), array.decimal_dtype())?,
Mask::AllFalse(_) => None,
Mask::Values(v) => compute_min_max(
array
Expand All @@ -53,30 +53,32 @@ where
.zip(v.bit_buffer().iter())
.filter_map(|(v, m)| m.then_some(v)),
array.decimal_dtype(),
),
)?,
},
)
}

fn compute_min_max<'a, T>(
iter: impl Iterator<Item = &'a T>,
decimal_dtype: DecimalDType,
) -> Option<MinMaxResult>
) -> VortexResult<Option<MinMaxResult>>
where
T: Into<DecimalValue> + NativeDecimalType + Ord + Copy + 'a,
{
match iter.minmax_by(|a, b| a.cmp(b)) {
// The stored values are not guaranteed to fit the precision (for example, when read from a
// file), so return an error instead of panicking.
Ok(match iter.minmax_by(|a, b| a.cmp(b)) {
itertools::MinMaxResult::NoElements => None,
itertools::MinMaxResult::OneElement(&x) => {
let scalar = Scalar::decimal(x.into(), decimal_dtype, NonNullable);
let scalar = Scalar::try_decimal(x.into(), decimal_dtype, NonNullable)?;
Some(MinMaxResult {
min: scalar.clone(),
max: scalar,
})
}
itertools::MinMaxResult::MinMax(&min, &max) => Some(MinMaxResult {
min: Scalar::decimal(min.into(), decimal_dtype, NonNullable),
max: Scalar::decimal(max.into(), decimal_dtype, NonNullable),
min: Scalar::try_decimal(min.into(), decimal_dtype, NonNullable)?,
max: Scalar::try_decimal(max.into(), decimal_dtype, NonNullable)?,
}),
}
})
}
17 changes: 17 additions & 0 deletions vortex-array/src/aggregate_fn/fns/min_max/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,23 @@ mod tests {
Ok(())
}

#[test]
fn test_decimal_value_outside_precision_errors() {
// 100 does not fit in decimal(2, 0), and `DecimalArray::new` does not check the values.
for values in [buffer![100i8], buffer![1i8, 100i8]] {
let decimal = DecimalArray::new(values, DecimalDType::new(2, 0), Validity::NonNullable);
let mut ctx = SESSION.create_execution_ctx();
assert!(
min_max(
&decimal.into_array(),
&mut ctx,
NumericalAggregateOpts::default(),
)
.is_err()
);
}
}

#[test]
fn list_and_fixed_size_list_return_dtype() {
let element_dtype = DType::Primitive(PType::I32, Nullability::Nullable);
Expand Down
23 changes: 20 additions & 3 deletions vortex-array/src/arrays/decimal/vtable/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ impl OperationsVTable<Decimal> for Decimal {
index: usize,
_ctx: &mut ExecutionCtx,
) -> VortexResult<Scalar> {
Ok(match_each_decimal_value_type!(array.values_type(), |D| {
Scalar::decimal(
// The stored values are not guaranteed to fit the precision (for example, when read from a
// file), so return an error instead of panicking.
match_each_decimal_value_type!(array.values_type(), |D| {
Scalar::try_decimal(
DecimalValue::from(array.buffer::<D>()[index]),
array.decimal_dtype(),
array.dtype().nullability(),
)
}))
})
}
}

Expand Down Expand Up @@ -102,4 +104,19 @@ mod tests {
)
);
}

#[test]
fn test_scalar_at_value_outside_precision_errors() {
let array = DecimalArray::new(
buffer![100i8],
DecimalDType::new(2, 0),
Validity::NonNullable,
);

assert!(
array
.execute_scalar(0, &mut SESSION.create_execution_ctx())
.is_err()
);
}
}
21 changes: 20 additions & 1 deletion vortex-array/src/scalar/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,35 @@ impl Scalar {
}

/// Creates a new decimal scalar with the given value, precision, scale, and nullability.
///
/// # Panics
///
/// Panics if `value` does not fit in the precision of `decimal_type`. Use
/// [`Scalar::try_decimal`] when the value comes from data that has not been validated.
pub fn decimal(
value: DecimalValue,
decimal_type: DecimalDType,
nullability: Nullability,
) -> Self {
Self::try_decimal(value, decimal_type, nullability)
.vortex_expect("unable to construct a decimal `Scalar`")
}

/// Attempts to create a new decimal scalar with the given value, precision, scale, and
/// nullability.
///
/// # Errors
///
/// Returns an error if `value` does not fit in the precision of `decimal_type`.
pub fn try_decimal(
value: DecimalValue,
decimal_type: DecimalDType,
nullability: Nullability,
) -> VortexResult<Self> {
Self::try_new(
DType::Decimal(decimal_type, nullability),
Some(ScalarValue::Decimal(value)),
)
.vortex_expect("unable to construct a decimal `Scalar`")
}

/// Creates a new UTF-8 scalar from a string-like value.
Expand Down