Skip to content

Commit

Permalink
Handle precision overflow when casting from integer to decimal (apach…
Browse files Browse the repository at this point in the history
…e#3996)

* Handle overflow precision when casting from integer to decimal

* fix clippy

* Update test

* Update test
  • Loading branch information
viirya authored Apr 1, 2023
1 parent 8bac91d commit 4e7bb45
Showing 1 changed file with 63 additions and 10 deletions.
73 changes: 63 additions & 10 deletions arrow-cast/src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,29 @@ where

let array = if scale < 0 {
match cast_options.safe {
true => array.unary_opt::<_, D>(|v| v.as_().div_checked(scale_factor).ok()),
false => array.try_unary::<_, D, _>(|v| v.as_().div_checked(scale_factor))?,
true => array.unary_opt::<_, D>(|v| {
v.as_().div_checked(scale_factor).ok().and_then(|v| {
(D::validate_decimal_precision(v, precision).is_ok()).then_some(v)
})
}),
false => array.try_unary::<_, D, _>(|v| {
v.as_()
.div_checked(scale_factor)
.and_then(|v| D::validate_decimal_precision(v, precision).map(|_| v))
})?,
}
} else {
match cast_options.safe {
true => array.unary_opt::<_, D>(|v| v.as_().mul_checked(scale_factor).ok()),
false => array.try_unary::<_, D, _>(|v| v.as_().mul_checked(scale_factor))?,
true => array.unary_opt::<_, D>(|v| {
v.as_().mul_checked(scale_factor).ok().and_then(|v| {
(D::validate_decimal_precision(v, precision).is_ok()).then_some(v)
})
}),
false => array.try_unary::<_, D, _>(|v| {
v.as_()
.mul_checked(scale_factor)
.and_then(|v| D::validate_decimal_precision(v, precision).map(|_| v))
})?,
}
};

Expand Down Expand Up @@ -4375,8 +4391,7 @@ mod tests {
assert!(casted_array.is_ok());
let array = casted_array.unwrap();
let array: &Decimal128Array = array.as_primitive();
let err = array.validate_decimal_precision(3);
assert_eq!("Invalid argument error: 1000 is too large to store in a Decimal128 of precision 3. Max is 999", err.unwrap_err().to_string());
assert!(array.is_null(4));

// test i8 to decimal type with overflow the result type
// the 100 will be converted to 1000_i128, but it is out of range for max value in the precision 3.
Expand All @@ -4385,8 +4400,7 @@ mod tests {
assert!(casted_array.is_ok());
let array = casted_array.unwrap();
let array: &Decimal128Array = array.as_primitive();
let err = array.validate_decimal_precision(3);
assert_eq!("Invalid argument error: 1000 is too large to store in a Decimal128 of precision 3. Max is 999", err.unwrap_err().to_string());
assert!(array.is_null(4));

// test f32 to decimal type
let array = Float32Array::from(vec![
Expand Down Expand Up @@ -4544,8 +4558,7 @@ mod tests {
assert!(casted_array.is_ok());
let array = casted_array.unwrap();
let array: &Decimal256Array = array.as_primitive();
let err = array.validate_decimal_precision(3);
assert_eq!("Invalid argument error: 1000 is too large to store in a Decimal256 of precision 3. Max is 999", err.unwrap_err().to_string());
assert!(array.is_null(4));

// test f32 to decimal type
let array = Float32Array::from(vec![
Expand Down Expand Up @@ -8132,4 +8145,44 @@ mod tests {
.unwrap();
assert_eq!(1672531200000000000, c.value(0));
}

#[test]
fn test_cast_numeric_to_decimal128_precision_overflow() {
let array = Int64Array::from(vec![1234567]);
let array = Arc::new(array) as ArrayRef;
let casted_array = cast_with_options(
&array,
&DataType::Decimal128(7, 3),
&CastOptions { safe: true },
);
assert!(casted_array.is_ok());
assert!(casted_array.unwrap().is_null(0));

let err = cast_with_options(
&array,
&DataType::Decimal128(7, 3),
&CastOptions { safe: false },
);
assert_eq!("Invalid argument error: 1234567000 is too large to store in a Decimal128 of precision 7. Max is 9999999", err.unwrap_err().to_string());
}

#[test]
fn test_cast_numeric_to_decimal256_precision_overflow() {
let array = Int64Array::from(vec![1234567]);
let array = Arc::new(array) as ArrayRef;
let casted_array = cast_with_options(
&array,
&DataType::Decimal256(7, 3),
&CastOptions { safe: true },
);
assert!(casted_array.is_ok());
assert!(casted_array.unwrap().is_null(0));

let err = cast_with_options(
&array,
&DataType::Decimal256(7, 3),
&CastOptions { safe: false },
);
assert_eq!("Invalid argument error: 1234567000 is too large to store in a Decimal256 of precision 7. Max is 9999999", err.unwrap_err().to_string());
}
}

0 comments on commit 4e7bb45

Please sign in to comment.