-
Notifications
You must be signed in to change notification settings - Fork 2.4k
More decimal 32/64 support - type coercsion and misc gaps #17808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -357,6 +357,14 @@ fn math_decimal_coercion( | |
| | (Decimal256(_, _), Decimal256(_, _)) => { | ||
| Some((lhs_type.clone(), rhs_type.clone())) | ||
| } | ||
| // Cross-variant decimal coercion - choose larger variant with appropriate precision/scale | ||
| (Decimal32(_, _), Decimal64(_, _) | Decimal128(_, _) | Decimal256(_, _)) | ||
| | (Decimal64(_, _), Decimal32(_, _) | Decimal128(_, _) | Decimal256(_, _)) | ||
| | (Decimal128(_, _), Decimal32(_, _) | Decimal64(_, _) | Decimal256(_, _)) | ||
| | (Decimal256(_, _), Decimal32(_, _) | Decimal64(_, _) | Decimal128(_, _)) => { | ||
| let coerced_type = get_wider_decimal_type_cross_variant(lhs_type, rhs_type)?; | ||
| Some((coerced_type.clone(), coerced_type)) | ||
| } | ||
| // Unlike with comparison we don't coerce to a decimal in the case of floating point | ||
| // numbers, instead falling back to floating point arithmetic instead | ||
| ( | ||
|
|
@@ -955,28 +963,106 @@ pub fn decimal_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option<Data | |
|
|
||
| match (lhs_type, rhs_type) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be cleaner like so: /// Decimal coercion rules.
pub fn decimal_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option<DataType> {
use arrow::datatypes::DataType::*;
// Prefer decimal data type over floating point for comparison operation
match (lhs_type, rhs_type) {
// Same decimal types
(lhs_type, rhs_type)
if std::mem::discriminant(lhs_type) == std::mem::discriminant(rhs_type) =>
{
get_wider_decimal_type(lhs_type, rhs_type)
}
// Mismatched decimal types
(lhs_type, rhs_type)
if is_decimal(lhs_type)
&& is_decimal(rhs_type)
&& std::mem::discriminant(lhs_type)
!= std::mem::discriminant(rhs_type) =>
{
get_wider_decimal_type_cross_variant(lhs_type, rhs_type)
}
// Decimal + non-decimal types
(Decimal32(_, _) | Decimal64(_, _) | Decimal128(_, _) | Decimal256(_, _), _)
| (_, Decimal32(_, _) | Decimal64(_, _) | Decimal128(_, _) | Decimal256(_, _)) => {
get_common_decimal_type(lhs_type, rhs_type)
}
(_, _) => None,
}
}Following what was done above
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops forgot the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah I've added them locally :) should have something soon
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done as part of 4145a04 |
||
| // Prefer decimal data type over floating point for comparison operation | ||
| (Decimal32(_, _), Decimal32(_, _)) => get_wider_decimal_type(lhs_type, rhs_type), | ||
| (Decimal32(_, _), Decimal64(_, _) | Decimal128(_, _) | Decimal256(_, _)) => { | ||
| get_wider_decimal_type_cross_variant(lhs_type, rhs_type) | ||
| } | ||
| (Decimal32(_, _), _) => get_common_decimal_type(lhs_type, rhs_type), | ||
| (Decimal64(_, _), Decimal64(_, _)) => get_wider_decimal_type(lhs_type, rhs_type), | ||
| (Decimal64(_, _), Decimal32(_, _) | Decimal128(_, _) | Decimal256(_, _)) => { | ||
| get_wider_decimal_type_cross_variant(lhs_type, rhs_type) | ||
| } | ||
| (Decimal64(_, _), _) => get_common_decimal_type(lhs_type, rhs_type), | ||
| (Decimal128(_, _), Decimal128(_, _)) => { | ||
| get_wider_decimal_type(lhs_type, rhs_type) | ||
| } | ||
| (Decimal128(_, _), Decimal32(_, _) | Decimal64(_, _) | Decimal256(_, _)) => { | ||
| get_wider_decimal_type_cross_variant(lhs_type, rhs_type) | ||
| } | ||
| (Decimal128(_, _), _) => get_common_decimal_type(lhs_type, rhs_type), | ||
| (_, Decimal128(_, _)) => get_common_decimal_type(rhs_type, lhs_type), | ||
| (Decimal256(_, _), Decimal256(_, _)) => { | ||
| get_wider_decimal_type(lhs_type, rhs_type) | ||
| } | ||
| (Decimal256(_, _), Decimal32(_, _) | Decimal64(_, _) | Decimal128(_, _)) => { | ||
| get_wider_decimal_type_cross_variant(lhs_type, rhs_type) | ||
| } | ||
| (Decimal256(_, _), _) => get_common_decimal_type(lhs_type, rhs_type), | ||
| (_, Decimal32(_, _)) => get_common_decimal_type(rhs_type, lhs_type), | ||
| (_, Decimal64(_, _)) => get_common_decimal_type(rhs_type, lhs_type), | ||
| (_, Decimal128(_, _)) => get_common_decimal_type(rhs_type, lhs_type), | ||
| (_, Decimal256(_, _)) => get_common_decimal_type(rhs_type, lhs_type), | ||
| (_, _) => None, | ||
| } | ||
| } | ||
|
|
||
| /// Handle cross-variant decimal widening by choosing the larger variant | ||
| fn get_wider_decimal_type_cross_variant( | ||
| lhs_type: &DataType, | ||
| rhs_type: &DataType, | ||
| ) -> Option<DataType> { | ||
| use arrow::datatypes::DataType::*; | ||
|
|
||
| let (p1, s1) = match lhs_type { | ||
| Decimal32(p, s) => (*p, *s), | ||
| Decimal64(p, s) => (*p, *s), | ||
| Decimal128(p, s) => (*p, *s), | ||
| Decimal256(p, s) => (*p, *s), | ||
| _ => return None, | ||
| }; | ||
|
|
||
| let (p2, s2) = match rhs_type { | ||
| Decimal32(p, s) => (*p, *s), | ||
| Decimal64(p, s) => (*p, *s), | ||
| Decimal128(p, s) => (*p, *s), | ||
| Decimal256(p, s) => (*p, *s), | ||
| _ => return None, | ||
| }; | ||
|
|
||
| // max(s1, s2) + max(p1-s1, p2-s2), max(s1, s2) | ||
| let s = s1.max(s2); | ||
| let range = (p1 as i8 - s1).max(p2 as i8 - s2); | ||
| let required_precision = (range + s) as u8; | ||
|
Comment on lines
+1029
to
+1031
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if we have: Decimal256 with precision 76 (max) and scale 0, and Decimal128 with precision 38 (max) with scale 1; So Is this a valid case?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think an overflow is valid, I'll have to think about it and maybe look into solutions in other systems.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked around a bit, and what I could find is:
I'm not sure what's the desired behavior regarding precision loss (should it be configurable? Is there currently an accepted desired behavior?), I think for this PR it should be fine to just return
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think returning
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done as part of fd1f043
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cheers; left another minor comment related to the check below. Also would be nice if we had a test for this edge case. |
||
|
|
||
| // Choose the larger variant between the two input types | ||
| match (lhs_type, rhs_type) { | ||
| (Decimal32(_, _), Decimal64(_, _)) | (Decimal64(_, _), Decimal32(_, _)) => { | ||
| Some(Decimal64(required_precision, s)) | ||
| } | ||
| (Decimal32(_, _), Decimal128(_, _)) | (Decimal128(_, _), Decimal32(_, _)) => { | ||
| Some(Decimal128(required_precision, s)) | ||
| } | ||
| (Decimal32(_, _), Decimal256(_, _)) | (Decimal256(_, _), Decimal32(_, _)) => { | ||
| Some(Decimal256(required_precision, s)) | ||
| } | ||
| (Decimal64(_, _), Decimal128(_, _)) | (Decimal128(_, _), Decimal64(_, _)) => { | ||
| Some(Decimal128(required_precision, s)) | ||
| } | ||
| (Decimal64(_, _), Decimal256(_, _)) | (Decimal256(_, _), Decimal64(_, _)) => { | ||
| Some(Decimal256(required_precision, s)) | ||
| } | ||
| (Decimal128(_, _), Decimal256(_, _)) | (Decimal256(_, _), Decimal128(_, _)) => { | ||
| Some(Decimal256(required_precision, s)) | ||
| } | ||
| _ => None, | ||
| } | ||
| } | ||
|
|
||
| /// Coerce `lhs_type` and `rhs_type` to a common type. | ||
| fn get_common_decimal_type( | ||
| decimal_type: &DataType, | ||
| other_type: &DataType, | ||
| ) -> Option<DataType> { | ||
| use arrow::datatypes::DataType::*; | ||
| match decimal_type { | ||
| Decimal32(_, _) | Decimal64(_, _) | Decimal128(_, _) => { | ||
| Decimal32(_, _) => { | ||
| let other_decimal_type = coerce_numeric_type_to_decimal32(other_type)?; | ||
| get_wider_decimal_type(decimal_type, &other_decimal_type) | ||
| } | ||
| Decimal64(_, _) => { | ||
| let other_decimal_type = coerce_numeric_type_to_decimal64(other_type)?; | ||
| get_wider_decimal_type(decimal_type, &other_decimal_type) | ||
| } | ||
| Decimal128(_, _) => { | ||
| let other_decimal_type = coerce_numeric_type_to_decimal128(other_type)?; | ||
| get_wider_decimal_type(decimal_type, &other_decimal_type) | ||
| } | ||
|
|
@@ -988,7 +1074,7 @@ fn get_common_decimal_type( | |
| } | ||
| } | ||
|
|
||
| /// Returns a `DataType::Decimal128` that can store any value from either | ||
| /// Returns a decimal [`DataType`] variant that can store any value from either | ||
| /// `lhs_decimal_type` and `rhs_decimal_type` | ||
| /// | ||
| /// The result decimal type is `(max(s1, s2) + max(p1-s1, p2-s2), max(s1, s2))`. | ||
|
|
@@ -1209,14 +1295,14 @@ fn numerical_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option<DataTy | |
| } | ||
|
|
||
| fn create_decimal32_type(precision: u8, scale: i8) -> DataType { | ||
| DataType::Decimal128( | ||
| DataType::Decimal32( | ||
| DECIMAL32_MAX_PRECISION.min(precision), | ||
| DECIMAL32_MAX_SCALE.min(scale), | ||
| ) | ||
| } | ||
|
|
||
| fn create_decimal64_type(precision: u8, scale: i8) -> DataType { | ||
| DataType::Decimal128( | ||
| DataType::Decimal64( | ||
| DECIMAL64_MAX_PRECISION.min(precision), | ||
| DECIMAL64_MAX_SCALE.min(scale), | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1922,6 +1922,8 @@ fn compare_join_arrays( | |
| DataType::BinaryView => compare_value!(BinaryViewArray), | ||
| DataType::FixedSizeBinary(_) => compare_value!(FixedSizeBinaryArray), | ||
| DataType::LargeBinary => compare_value!(LargeBinaryArray), | ||
| DataType::Decimal32(..) => compare_value!(Decimal32Array), | ||
| DataType::Decimal64(..) => compare_value!(Decimal64Array), | ||
| DataType::Decimal128(..) => compare_value!(Decimal128Array), | ||
| DataType::Timestamp(time_unit, None) => match time_unit { | ||
| TimeUnit::Second => compare_value!(TimestampSecondArray), | ||
|
|
@@ -1994,6 +1996,8 @@ fn is_join_arrays_equal( | |
| DataType::BinaryView => compare_value!(BinaryViewArray), | ||
| DataType::FixedSizeBinary(_) => compare_value!(FixedSizeBinaryArray), | ||
| DataType::LargeBinary => compare_value!(LargeBinaryArray), | ||
| DataType::Decimal32(..) => compare_value!(Decimal32Array), | ||
| DataType::Decimal64(..) => compare_value!(Decimal64Array), | ||
|
Comment on lines
+1999
to
+2000
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I am curious why 256 is omitted? Perhaps we can add it in if there's no compiler error doing so?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I'll add it. Through this work I've noticed a few places where it was omitted, but I've yet to find a reason or even a failing test that would explain it, usually its just a matter of adding a branch to a match statement like here. |
||
| DataType::Decimal128(..) => compare_value!(Decimal128Array), | ||
| DataType::Timestamp(time_unit, None) => match time_unit { | ||
| TimeUnit::Second => compare_value!(TimestampSecondArray), | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.