Skip to content

Commit d2bfefa

Browse files
viiryamatthewmturner
authored andcommitted
Implement array_agg aggregate function (apache#1300)
* Implement array_agg aggregate function. * Avoid copying. * Fix clippy. * For review comment. * Add e2e tests. * Add assert and order by.
1 parent 05e7cc5 commit d2bfefa

7 files changed

Lines changed: 333 additions & 5 deletions

File tree

ballista/rust/core/proto/ballista.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ enum AggregateFunction {
168168
AVG = 3;
169169
COUNT = 4;
170170
APPROX_DISTINCT = 5;
171+
ARRAY_AGG = 6;
171172
}
172173

173174
message AggregateExprNode {

ballista/rust/core/src/serde/logical_plan/to_proto.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,7 @@ impl TryInto<protobuf::LogicalExprNode> for &Expr {
11241124
AggregateFunction::ApproxDistinct => {
11251125
protobuf::AggregateFunction::ApproxDistinct
11261126
}
1127+
AggregateFunction::ArrayAgg => protobuf::AggregateFunction::ArrayAgg,
11271128
AggregateFunction::Min => protobuf::AggregateFunction::Min,
11281129
AggregateFunction::Max => protobuf::AggregateFunction::Max,
11291130
AggregateFunction::Sum => protobuf::AggregateFunction::Sum,
@@ -1358,6 +1359,7 @@ impl From<&AggregateFunction> for protobuf::AggregateFunction {
13581359
AggregateFunction::Avg => Self::Avg,
13591360
AggregateFunction::Count => Self::Count,
13601361
AggregateFunction::ApproxDistinct => Self::ApproxDistinct,
1362+
AggregateFunction::ArrayAgg => Self::ArrayAgg,
13611363
}
13621364
}
13631365
}

ballista/rust/core/src/serde/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ impl From<protobuf::AggregateFunction> for AggregateFunction {
117117
protobuf::AggregateFunction::ApproxDistinct => {
118118
AggregateFunction::ApproxDistinct
119119
}
120+
protobuf::AggregateFunction::ArrayAgg => AggregateFunction::ArrayAgg,
120121
}
121122
}
122123
}

datafusion/src/physical_plan/aggregates.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use super::{
3434
use crate::error::{DataFusionError, Result};
3535
use crate::physical_plan::distinct_expressions;
3636
use crate::physical_plan::expressions;
37-
use arrow::datatypes::{DataType, Schema, TimeUnit};
37+
use arrow::datatypes::{DataType, Field, Schema, TimeUnit};
3838
use expressions::{avg_return_type, sum_return_type};
3939
use std::{fmt, str::FromStr, sync::Arc};
4040
/// the implementation of an aggregate function
@@ -46,7 +46,7 @@ pub type AccumulatorFunctionImplementation =
4646
pub type StateTypeFunction =
4747
Arc<dyn Fn(&DataType) -> Result<Arc<Vec<DataType>>> + Send + Sync>;
4848

49-
/// Enum of all built-in scalar functions
49+
/// Enum of all built-in aggregate functions
5050
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
5151
pub enum AggregateFunction {
5252
/// count
@@ -61,6 +61,8 @@ pub enum AggregateFunction {
6161
Avg,
6262
/// Approximate aggregate function
6363
ApproxDistinct,
64+
/// array_agg
65+
ArrayAgg,
6466
}
6567

6668
impl fmt::Display for AggregateFunction {
@@ -80,6 +82,7 @@ impl FromStr for AggregateFunction {
8082
"avg" => AggregateFunction::Avg,
8183
"sum" => AggregateFunction::Sum,
8284
"approx_distinct" => AggregateFunction::ApproxDistinct,
85+
"array_agg" => AggregateFunction::ArrayAgg,
8386
_ => {
8487
return Err(DataFusionError::Plan(format!(
8588
"There is no built-in function named {}",
@@ -105,6 +108,11 @@ pub fn return_type(fun: &AggregateFunction, arg_types: &[DataType]) -> Result<Da
105108
AggregateFunction::Max | AggregateFunction::Min => Ok(arg_types[0].clone()),
106109
AggregateFunction::Sum => sum_return_type(&arg_types[0]),
107110
AggregateFunction::Avg => avg_return_type(&arg_types[0]),
111+
AggregateFunction::ArrayAgg => Ok(DataType::List(Box::new(Field::new(
112+
"item",
113+
arg_types[0].clone(),
114+
true,
115+
)))),
108116
}
109117
}
110118

@@ -157,6 +165,9 @@ pub fn create_aggregate_expr(
157165
(AggregateFunction::ApproxDistinct, _) => Arc::new(
158166
expressions::ApproxDistinct::new(arg, name, arg_types[0].clone()),
159167
),
168+
(AggregateFunction::ArrayAgg, _) => {
169+
Arc::new(expressions::ArrayAgg::new(arg, name, arg_types[0].clone()))
170+
}
160171
(AggregateFunction::Min, _) => {
161172
Arc::new(expressions::Min::new(arg, name, return_type))
162173
}
@@ -202,9 +213,9 @@ static DATES: &[DataType] = &[DataType::Date32, DataType::Date64];
202213
pub fn signature(fun: &AggregateFunction) -> Signature {
203214
// note: the physical expression must accept the type returned by this function or the execution panics.
204215
match fun {
205-
AggregateFunction::Count | AggregateFunction::ApproxDistinct => {
206-
Signature::any(1, Volatility::Immutable)
207-
}
216+
AggregateFunction::Count
217+
| AggregateFunction::ApproxDistinct
218+
| AggregateFunction::ArrayAgg => Signature::any(1, Volatility::Immutable),
208219
AggregateFunction::Min | AggregateFunction::Max => {
209220
let valid = STRINGS
210221
.iter()
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//! Defines physical expressions that can evaluated at runtime during query execution
19+
20+
use super::format_state_name;
21+
use crate::error::Result;
22+
use crate::physical_plan::{Accumulator, AggregateExpr, PhysicalExpr};
23+
use crate::scalar::ScalarValue;
24+
use arrow::datatypes::{DataType, Field};
25+
use std::any::Any;
26+
use std::sync::Arc;
27+
28+
/// ARRAY_AGG aggregate expression
29+
#[derive(Debug)]
30+
pub struct ArrayAgg {
31+
name: String,
32+
input_data_type: DataType,
33+
expr: Arc<dyn PhysicalExpr>,
34+
}
35+
36+
impl ArrayAgg {
37+
/// Create a new ArrayAgg aggregate function
38+
pub fn new(
39+
expr: Arc<dyn PhysicalExpr>,
40+
name: impl Into<String>,
41+
data_type: DataType,
42+
) -> Self {
43+
Self {
44+
name: name.into(),
45+
expr,
46+
input_data_type: data_type,
47+
}
48+
}
49+
}
50+
51+
impl AggregateExpr for ArrayAgg {
52+
fn as_any(&self) -> &dyn Any {
53+
self
54+
}
55+
56+
fn field(&self) -> Result<Field> {
57+
Ok(Field::new(
58+
&self.name,
59+
DataType::List(Box::new(Field::new(
60+
"item",
61+
self.input_data_type.clone(),
62+
true,
63+
))),
64+
false,
65+
))
66+
}
67+
68+
fn create_accumulator(&self) -> Result<Box<dyn Accumulator>> {
69+
Ok(Box::new(ArrayAggAccumulator::try_new(
70+
&self.input_data_type,
71+
)?))
72+
}
73+
74+
fn state_fields(&self) -> Result<Vec<Field>> {
75+
Ok(vec![Field::new(
76+
&format_state_name(&self.name, "array_agg"),
77+
DataType::List(Box::new(Field::new(
78+
"item",
79+
self.input_data_type.clone(),
80+
true,
81+
))),
82+
false,
83+
)])
84+
}
85+
86+
fn expressions(&self) -> Vec<Arc<dyn PhysicalExpr>> {
87+
vec![self.expr.clone()]
88+
}
89+
}
90+
91+
#[derive(Debug)]
92+
pub(crate) struct ArrayAggAccumulator {
93+
array: Vec<ScalarValue>,
94+
datatype: DataType,
95+
}
96+
97+
impl ArrayAggAccumulator {
98+
/// new array_agg accumulator based on given item data type
99+
pub fn try_new(datatype: &DataType) -> Result<Self> {
100+
Ok(Self {
101+
array: vec![],
102+
datatype: datatype.clone(),
103+
})
104+
}
105+
}
106+
107+
impl Accumulator for ArrayAggAccumulator {
108+
fn state(&self) -> Result<Vec<ScalarValue>> {
109+
Ok(vec![ScalarValue::List(
110+
Some(Box::new(self.array.clone())),
111+
Box::new(self.datatype.clone()),
112+
)])
113+
}
114+
115+
fn update(&mut self, values: &[ScalarValue]) -> Result<()> {
116+
let value = &values[0];
117+
self.array.push(value.clone());
118+
119+
Ok(())
120+
}
121+
122+
fn merge(&mut self, states: &[ScalarValue]) -> Result<()> {
123+
if states.is_empty() {
124+
return Ok(());
125+
};
126+
127+
assert!(states.len() == 1, "states length should be 1!");
128+
match &states[0] {
129+
ScalarValue::List(Some(array), _) => {
130+
self.array.extend((&**array).clone());
131+
}
132+
_ => unreachable!(),
133+
}
134+
Ok(())
135+
}
136+
137+
fn evaluate(&self) -> Result<ScalarValue> {
138+
Ok(ScalarValue::List(
139+
Some(Box::new(self.array.clone())),
140+
Box::new(self.datatype.clone()),
141+
))
142+
}
143+
}
144+
145+
#[cfg(test)]
146+
mod tests {
147+
use super::*;
148+
use crate::physical_plan::expressions::col;
149+
use crate::physical_plan::expressions::tests::aggregate;
150+
use crate::{error::Result, generic_test_op};
151+
use arrow::array::ArrayRef;
152+
use arrow::array::Int32Array;
153+
use arrow::datatypes::*;
154+
use arrow::record_batch::RecordBatch;
155+
156+
#[test]
157+
fn array_agg_i32() -> Result<()> {
158+
let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5]));
159+
160+
let list = ScalarValue::List(
161+
Some(Box::new(vec![
162+
ScalarValue::Int32(Some(1)),
163+
ScalarValue::Int32(Some(2)),
164+
ScalarValue::Int32(Some(3)),
165+
ScalarValue::Int32(Some(4)),
166+
ScalarValue::Int32(Some(5)),
167+
])),
168+
Box::new(DataType::Int32),
169+
);
170+
171+
generic_test_op!(a, DataType::Int32, ArrayAgg, list, DataType::Int32)
172+
}
173+
174+
#[test]
175+
fn array_agg_nested() -> Result<()> {
176+
let l1 = ScalarValue::List(
177+
Some(Box::new(vec![
178+
ScalarValue::List(
179+
Some(Box::new(vec![
180+
ScalarValue::from(1i32),
181+
ScalarValue::from(2i32),
182+
ScalarValue::from(3i32),
183+
])),
184+
Box::new(DataType::Int32),
185+
),
186+
ScalarValue::List(
187+
Some(Box::new(vec![
188+
ScalarValue::from(4i32),
189+
ScalarValue::from(5i32),
190+
])),
191+
Box::new(DataType::Int32),
192+
),
193+
])),
194+
Box::new(DataType::List(Box::new(Field::new(
195+
"item",
196+
DataType::Int32,
197+
true,
198+
)))),
199+
);
200+
201+
let l2 = ScalarValue::List(
202+
Some(Box::new(vec![
203+
ScalarValue::List(
204+
Some(Box::new(vec![ScalarValue::from(6i32)])),
205+
Box::new(DataType::Int32),
206+
),
207+
ScalarValue::List(
208+
Some(Box::new(vec![
209+
ScalarValue::from(7i32),
210+
ScalarValue::from(8i32),
211+
])),
212+
Box::new(DataType::Int32),
213+
),
214+
])),
215+
Box::new(DataType::List(Box::new(Field::new(
216+
"item",
217+
DataType::Int32,
218+
true,
219+
)))),
220+
);
221+
222+
let l3 = ScalarValue::List(
223+
Some(Box::new(vec![ScalarValue::List(
224+
Some(Box::new(vec![ScalarValue::from(9i32)])),
225+
Box::new(DataType::Int32),
226+
)])),
227+
Box::new(DataType::List(Box::new(Field::new(
228+
"item",
229+
DataType::Int32,
230+
true,
231+
)))),
232+
);
233+
234+
let list = ScalarValue::List(
235+
Some(Box::new(vec![l1.clone(), l2.clone(), l3.clone()])),
236+
Box::new(DataType::List(Box::new(Field::new(
237+
"item",
238+
DataType::Int32,
239+
true,
240+
)))),
241+
);
242+
243+
let array = ScalarValue::iter_to_array(vec![l1, l2, l3]).unwrap();
244+
245+
generic_test_op!(
246+
array,
247+
DataType::List(Box::new(Field::new(
248+
"item",
249+
DataType::List(Box::new(Field::new("item", DataType::Int32, true,))),
250+
true,
251+
))),
252+
ArrayAgg,
253+
list,
254+
DataType::List(Box::new(Field::new("item", DataType::Int32, true,)))
255+
)
256+
}
257+
}

datafusion/src/physical_plan/expressions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use arrow::compute::kernels::sort::{SortColumn, SortOptions};
2626
use arrow::record_batch::RecordBatch;
2727

2828
mod approx_distinct;
29+
mod array_agg;
2930
mod average;
3031
#[macro_use]
3132
mod binary;
@@ -58,6 +59,7 @@ pub mod helpers {
5859
}
5960

6061
pub use approx_distinct::ApproxDistinct;
62+
pub use array_agg::ArrayAgg;
6163
pub use average::{avg_return_type, Avg, AvgAccumulator};
6264
pub use binary::{binary, binary_operator_data_type, BinaryExpr};
6365
pub use case::{case, CaseExpr};

0 commit comments

Comments
 (0)