-
Notifications
You must be signed in to change notification settings - Fork 2.2k
support sum/avg agg for decimal, change sum(float32) --> float64 #1408
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 5 commits
2095685
e3b005f
3c19c41
ff61c04
9675e30
f4b5655
2d247ce
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 |
|---|---|---|
|
|
@@ -38,11 +38,18 @@ use super::{format_state_name, sum}; | |
| pub struct Avg { | ||
| name: String, | ||
| expr: Arc<dyn PhysicalExpr>, | ||
| data_type: DataType, | ||
| } | ||
|
|
||
| /// function return type of an average | ||
| pub fn avg_return_type(arg_type: &DataType) -> Result<DataType> { | ||
| match arg_type { | ||
| DataType::Decimal(precision, scale) => { | ||
| // the new precision and scale for return type of avg function | ||
|
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. Can you please document document the rationale for the I also don't understand where the additional (arrow_dev) alamb@MacBook-Pro-2:~/Downloads$ psql
psql (14.1)
Type "help" for help.
alamb=# create table test(x decimal(10, 3));
CREATE TABLE
alamb=# insert into test values (1.02);
INSERT 0 1
alamb=# create table test2 as select avg(x) from test;
SELECT 1
alamb=# select table_name, column_name, numeric_precision, numeric_scale, data_type from information_schema.columns where table_name='test2';
table_name | column_name | numeric_precision | numeric_scale | data_type
------------+-------------+-------------------+---------------+-----------
test2 | avg | | | numeric
(1 row)
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.
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 following the spark behavior is reasonable, but it should be documented (aka that the constant
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. add comments to the code and issue #1461 to track this rule. |
||
| let new_precision = 38.min(*precision + 4); | ||
| let new_scale = 38.min(*scale + 4); | ||
| Ok(DataType::Decimal(new_precision, new_scale)) | ||
|
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. Could potentially use |
||
| } | ||
| DataType::Int8 | ||
| | DataType::Int16 | ||
| | DataType::Int32 | ||
|
|
@@ -73,6 +80,7 @@ pub(crate) fn is_avg_support_arg_type(arg_type: &DataType) -> bool { | |
| | DataType::Int64 | ||
| | DataType::Float32 | ||
| | DataType::Float64 | ||
| | DataType::Decimal(_, _) | ||
| ) | ||
| } | ||
|
|
||
|
|
@@ -86,11 +94,15 @@ impl Avg { | |
| // Average is always Float64, but Avg::new() has a data_type | ||
| // parameter to keep a consistent signature with the other | ||
| // Aggregate expressions. | ||
| assert_eq!(data_type, DataType::Float64); | ||
|
|
||
| Self { | ||
| name: name.into(), | ||
| expr, | ||
| match data_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. The comment above looks out of date -- I think it should simply be removed. And perhaps we can change this code so it doesn't use How about something like assert!(matches!(data_type, DataType::Float64 | DataType::Decimal(_, _)));Which I think might be easier to diagnose if anyone hits it
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. grate comments |
||
| DataType::Float64 | DataType::Decimal(_, _) => Self { | ||
| name: name.into(), | ||
| expr, | ||
| data_type, | ||
| }, | ||
| _ => { | ||
| unreachable!(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -102,7 +114,14 @@ impl AggregateExpr for Avg { | |
| } | ||
|
|
||
| fn field(&self) -> Result<Field> { | ||
| Ok(Field::new(&self.name, DataType::Float64, true)) | ||
| Ok(Field::new(&self.name, self.data_type.clone(), true)) | ||
| } | ||
|
|
||
| fn create_accumulator(&self) -> Result<Box<dyn Accumulator>> { | ||
| Ok(Box::new(AvgAccumulator::try_new( | ||
| // avg is f64 or decimal | ||
| &self.data_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. if a sum of I think handling overflow is probably fine to for a later date / PR, but it is strange to me that there is a discrepancy between the type for
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. The result type of phy expr(sum/avg) is same with each Accumulator, and it was decided by If the column is decimal(8,2), the avg of this column must be less than For the sum agg, we just should increase the precision part, and the rule of adding
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. add issue to track the overflow. |
||
| )?)) | ||
| } | ||
|
|
||
| fn state_fields(&self) -> Result<Vec<Field>> { | ||
|
|
@@ -114,19 +133,12 @@ impl AggregateExpr for Avg { | |
| ), | ||
| Field::new( | ||
| &format_state_name(&self.name, "sum"), | ||
| DataType::Float64, | ||
| self.data_type.clone(), | ||
| true, | ||
| ), | ||
| ]) | ||
| } | ||
|
|
||
| fn create_accumulator(&self) -> Result<Box<dyn Accumulator>> { | ||
| Ok(Box::new(AvgAccumulator::try_new( | ||
| // avg is f64 | ||
| &DataType::Float64, | ||
| )?)) | ||
| } | ||
|
|
||
| fn expressions(&self) -> Vec<Arc<dyn PhysicalExpr>> { | ||
| vec![self.expr.clone()] | ||
| } | ||
|
|
@@ -205,6 +217,17 @@ impl Accumulator for AvgAccumulator { | |
| ScalarValue::Float64(e) => { | ||
| Ok(ScalarValue::Float64(e.map(|f| f / self.count as f64))) | ||
| } | ||
| ScalarValue::Decimal128(value, precision, scale) => { | ||
| Ok(match value { | ||
| None => ScalarValue::Decimal128(None, precision, scale), | ||
| // TODO add the checker for overflow the precision | ||
|
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. 👍 |
||
| Some(v) => ScalarValue::Decimal128( | ||
| Some(v / self.count as i128), | ||
| precision, | ||
| scale, | ||
| ), | ||
| }) | ||
| } | ||
| _ => Err(DataFusionError::Internal( | ||
| "Sum should be f64 on average".to_string(), | ||
| )), | ||
|
|
@@ -220,6 +243,74 @@ mod tests { | |
| use arrow::record_batch::RecordBatch; | ||
| use arrow::{array::*, datatypes::*}; | ||
|
|
||
| #[test] | ||
| fn test_avg_return_data_type() -> Result<()> { | ||
| let data_type = DataType::Decimal(10, 5); | ||
| let result_type = avg_return_type(&data_type)?; | ||
| assert_eq!(DataType::Decimal(14, 9), result_type); | ||
|
|
||
| let data_type = DataType::Decimal(36, 10); | ||
| let result_type = avg_return_type(&data_type)?; | ||
| assert_eq!(DataType::Decimal(38, 14), result_type); | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[test] | ||
| fn avg_decimal() -> Result<()> { | ||
| // test agg | ||
| let mut decimal_builder = DecimalBuilder::new(6, 10, 0); | ||
| for i in 1..7 { | ||
| // the avg is 3.5, but we get the result of 3 | ||
|
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 don't understand this comment: the result is
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. This comment is out of data, I will remove this. |
||
| decimal_builder.append_value(i as i128)?; | ||
| } | ||
| let array: ArrayRef = Arc::new(decimal_builder.finish()); | ||
|
|
||
| generic_test_op!( | ||
| array, | ||
| DataType::Decimal(10, 0), | ||
| Avg, | ||
| ScalarValue::Decimal128(Some(35000), 14, 4), | ||
| DataType::Decimal(14, 4) | ||
| ) | ||
| } | ||
|
|
||
| #[test] | ||
| fn avg_decimal_with_nulls() -> Result<()> { | ||
| let mut decimal_builder = DecimalBuilder::new(5, 10, 0); | ||
| for i in 1..6 { | ||
| if i == 2 { | ||
| decimal_builder.append_null()?; | ||
| } else { | ||
| decimal_builder.append_value(i)?; | ||
| } | ||
| } | ||
| let array: ArrayRef = Arc::new(decimal_builder.finish()); | ||
| generic_test_op!( | ||
| array, | ||
| DataType::Decimal(10, 0), | ||
| Avg, | ||
| ScalarValue::Decimal128(Some(32500), 14, 4), | ||
| DataType::Decimal(14, 4) | ||
| ) | ||
| } | ||
|
|
||
| #[test] | ||
| fn avg_decimal_all_nulls() -> Result<()> { | ||
| // test agg | ||
| let mut decimal_builder = DecimalBuilder::new(5, 10, 0); | ||
| for _i in 1..6 { | ||
| decimal_builder.append_null()?; | ||
| } | ||
| let array: ArrayRef = Arc::new(decimal_builder.finish()); | ||
| generic_test_op!( | ||
| array, | ||
| DataType::Decimal(10, 0), | ||
| Avg, | ||
| ScalarValue::Decimal128(None, 14, 4), | ||
| DataType::Decimal(14, 4) | ||
| ) | ||
| } | ||
|
|
||
| #[test] | ||
| fn avg_i32() -> Result<()> { | ||
| let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5])); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍