1use std::hash::Hash;
21use std::mem::size_of_val;
22use std::sync::Arc;
23
24use crate::array_agg::ArrayAgg;
25
26use arrow::array::{ArrayRef, AsArray, BooleanArray, LargeStringArray};
27use arrow::datatypes::{DataType, Field, FieldRef};
28use datafusion_common::cast::{as_generic_string_array, as_string_view_array};
29use datafusion_common::{
30 Result, ScalarValue, internal_datafusion_err, internal_err, not_impl_err,
31};
32use datafusion_expr::function::AccumulatorArgs;
33use datafusion_expr::utils::format_state_name;
34use datafusion_expr::{
35 Accumulator, AggregateUDFImpl, Documentation, EmitTo, GroupsAccumulator, Signature,
36 TypeSignature, Volatility,
37};
38use datafusion_functions_aggregate_common::accumulator::StateFieldsArgs;
39use datafusion_functions_aggregate_common::aggregate::groups_accumulator::nulls::apply_filter_as_nulls;
40use datafusion_macros::user_doc;
41use datafusion_physical_expr::expressions::Literal;
42
43make_udaf_expr_and_func!(
44 StringAgg,
45 string_agg,
46 expr delimiter,
47 "Concatenates the values of string expressions and places separator values between them",
48 string_agg_udaf
49);
50
51#[user_doc(
52 doc_section(label = "General Functions"),
53 description = "Concatenates the values of string expressions and places separator values between them. \
54If ordering is required, strings are concatenated in the specified order. \
55This aggregation function can only mix DISTINCT and ORDER BY if the ordering expression is exactly the same as the first argument expression.",
56 syntax_example = "string_agg([DISTINCT] expression, delimiter [ORDER BY expression])",
57 sql_example = r#"```sql
58> SELECT string_agg(name, ', ') AS names_list
59 FROM employee;
60+--------------------------+
61| names_list |
62+--------------------------+
63| Alice, Bob, Bob, Charlie |
64+--------------------------+
65> SELECT string_agg(name, ', ' ORDER BY name DESC) AS names_list
66 FROM employee;
67+--------------------------+
68| names_list |
69+--------------------------+
70| Charlie, Bob, Bob, Alice |
71+--------------------------+
72> SELECT string_agg(DISTINCT name, ', ' ORDER BY name DESC) AS names_list
73 FROM employee;
74+--------------------------+
75| names_list |
76+--------------------------+
77| Charlie, Bob, Alice |
78+--------------------------+
79```"#,
80 argument(
81 name = "expression",
82 description = "The string expression to concatenate. Can be a column or any valid string expression."
83 ),
84 argument(
85 name = "delimiter",
86 description = "A literal string used as a separator between the concatenated values."
87 )
88)]
89#[derive(Debug, PartialEq, Eq, Hash)]
91pub struct StringAgg {
92 signature: Signature,
93 array_agg: ArrayAgg,
94}
95
96impl StringAgg {
97 pub fn new() -> Self {
99 Self {
100 signature: Signature::one_of(
101 vec![
102 TypeSignature::Exact(vec![DataType::LargeUtf8, DataType::Utf8]),
103 TypeSignature::Exact(vec![DataType::LargeUtf8, DataType::LargeUtf8]),
104 TypeSignature::Exact(vec![DataType::LargeUtf8, DataType::Null]),
105 TypeSignature::Exact(vec![DataType::LargeUtf8, DataType::Utf8View]),
106 TypeSignature::Exact(vec![DataType::Utf8, DataType::Utf8]),
107 TypeSignature::Exact(vec![DataType::Utf8, DataType::LargeUtf8]),
108 TypeSignature::Exact(vec![DataType::Utf8, DataType::Null]),
109 TypeSignature::Exact(vec![DataType::Utf8, DataType::Utf8View]),
110 TypeSignature::Exact(vec![DataType::Utf8View, DataType::Utf8View]),
111 TypeSignature::Exact(vec![DataType::Utf8View, DataType::LargeUtf8]),
112 TypeSignature::Exact(vec![DataType::Utf8View, DataType::Null]),
113 TypeSignature::Exact(vec![DataType::Utf8View, DataType::Utf8]),
114 ],
115 Volatility::Immutable,
116 ),
117 array_agg: Default::default(),
118 }
119 }
120
121 fn extract_delimiter(args: &AccumulatorArgs) -> Result<String> {
123 let Some(lit) = args.exprs[1].downcast_ref::<Literal>() else {
124 return not_impl_err!("string_agg delimiter must be a string literal");
125 };
126
127 if lit.value().is_null() {
128 return Ok(String::new());
129 }
130
131 match lit.value().try_as_str() {
132 Some(s) => Ok(s.unwrap_or("").to_string()),
133 None => {
134 not_impl_err!(
135 "string_agg not supported for delimiter \"{}\"",
136 lit.value()
137 )
138 }
139 }
140 }
141}
142
143impl Default for StringAgg {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl AggregateUDFImpl for StringAgg {
154 fn name(&self) -> &str {
155 "string_agg"
156 }
157
158 fn signature(&self) -> &Signature {
159 &self.signature
160 }
161
162 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
163 Ok(DataType::LargeUtf8)
164 }
165
166 fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
167 if !args.is_distinct && args.ordering_fields.is_empty() {
168 Ok(vec![
169 Field::new(
170 format_state_name(args.name, "string_agg"),
171 DataType::LargeUtf8,
172 true,
173 )
174 .into(),
175 ])
176 } else {
177 self.array_agg.state_fields(args)
178 }
179 }
180
181 fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
182 let delimiter = Self::extract_delimiter(&acc_args)?;
183
184 if !acc_args.is_distinct && acc_args.order_bys.is_empty() {
185 Ok(Box::new(SimpleStringAggAccumulator::new(&delimiter)))
186 } else {
187 let array_agg_acc = self.array_agg.accumulator(AccumulatorArgs {
188 return_field: Field::new(
189 "f",
190 DataType::new_list(acc_args.return_field.data_type().clone(), true),
191 true,
192 )
193 .into(),
194 exprs: &filter_index(acc_args.exprs, 1),
195 expr_fields: &filter_index(acc_args.expr_fields, 1),
196 schema: acc_args.schema,
200 ignore_nulls: acc_args.ignore_nulls,
201 order_bys: acc_args.order_bys,
202 is_reversed: acc_args.is_reversed,
203 name: acc_args.name,
204 is_distinct: acc_args.is_distinct,
205 })?;
206
207 Ok(Box::new(StringAggAccumulator::new(
208 array_agg_acc,
209 &delimiter,
210 )))
211 }
212 }
213
214 fn reverse_expr(&self) -> datafusion_expr::ReversedUDAF {
215 datafusion_expr::ReversedUDAF::Reversed(string_agg_udaf())
216 }
217
218 fn groups_accumulator_supported(&self, args: AccumulatorArgs) -> bool {
219 !args.is_distinct && args.order_bys.is_empty()
220 }
221
222 fn create_groups_accumulator(
223 &self,
224 args: AccumulatorArgs,
225 ) -> Result<Box<dyn GroupsAccumulator>> {
226 let delimiter = Self::extract_delimiter(&args)?;
227 Ok(Box::new(StringAggGroupsAccumulator::new(delimiter)))
228 }
229
230 fn documentation(&self) -> Option<&Documentation> {
231 self.doc()
232 }
233}
234
235#[derive(Debug)]
237pub(crate) struct StringAggAccumulator {
238 array_agg_acc: Box<dyn Accumulator>,
239 delimiter: String,
240}
241
242impl StringAggAccumulator {
243 pub fn new(array_agg_acc: Box<dyn Accumulator>, delimiter: &str) -> Self {
244 Self {
245 array_agg_acc,
246 delimiter: delimiter.to_string(),
247 }
248 }
249}
250
251impl Accumulator for StringAggAccumulator {
252 fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
253 self.array_agg_acc.update_batch(&filter_index(values, 1))
254 }
255
256 fn evaluate(&mut self) -> Result<ScalarValue> {
257 let scalar = self.array_agg_acc.evaluate()?;
258
259 let ScalarValue::List(list) = scalar else {
260 return internal_err!(
261 "Expected a DataType::List while evaluating underlying ArrayAggAccumulator, but got {}",
262 scalar.data_type()
263 );
264 };
265
266 let string_arr: Vec<_> = match list.value_type() {
267 DataType::LargeUtf8 => as_generic_string_array::<i64>(list.values())?
268 .iter()
269 .flatten()
270 .collect(),
271 DataType::Utf8 => as_generic_string_array::<i32>(list.values())?
272 .iter()
273 .flatten()
274 .collect(),
275 DataType::Utf8View => as_string_view_array(list.values())?
276 .iter()
277 .flatten()
278 .collect(),
279 _ => {
280 return internal_err!(
281 "Expected elements to of type Utf8 or LargeUtf8, but got {}",
282 list.value_type()
283 );
284 }
285 };
286
287 if string_arr.is_empty() {
288 return Ok(ScalarValue::LargeUtf8(None));
289 }
290
291 Ok(ScalarValue::LargeUtf8(Some(
292 string_arr.join(&self.delimiter),
293 )))
294 }
295
296 fn size(&self) -> usize {
297 size_of_val(self) - size_of_val(&self.array_agg_acc)
298 + self.array_agg_acc.size()
299 + self.delimiter.capacity()
300 }
301
302 fn state(&mut self) -> Result<Vec<ScalarValue>> {
303 self.array_agg_acc.state()
304 }
305
306 fn merge_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
307 self.array_agg_acc.merge_batch(values)
308 }
309}
310
311fn filter_index<T: Clone>(values: &[T], index: usize) -> Vec<T> {
312 values
313 .iter()
314 .enumerate()
315 .filter(|(i, _)| *i != index)
316 .map(|(_, v)| v)
317 .cloned()
318 .collect::<Vec<_>>()
319}
320
321#[derive(Debug)]
323struct StringAggGroupsAccumulator {
324 delimiter: String,
326 values: Vec<Option<String>>,
331 total_data_bytes: usize,
333}
334
335impl StringAggGroupsAccumulator {
336 fn new(delimiter: String) -> Self {
337 Self {
338 delimiter,
339 values: Vec::new(),
340 total_data_bytes: 0,
341 }
342 }
343
344 fn append_batch<'a>(
345 &mut self,
346 iter: impl Iterator<Item = Option<&'a str>>,
347 group_indices: &[usize],
348 ) {
349 for (opt_value, &group_idx) in iter.zip(group_indices.iter()) {
350 if let Some(value) = opt_value {
351 match &mut self.values[group_idx] {
352 Some(existing) => {
353 let added = self.delimiter.len() + value.len();
354 existing.reserve(added);
355 existing.push_str(&self.delimiter);
356 existing.push_str(value);
357 self.total_data_bytes += added;
358 }
359 slot @ None => {
360 *slot = Some(value.to_string());
361 self.total_data_bytes += value.len();
362 }
363 }
364 }
365 }
366 }
367}
368
369impl GroupsAccumulator for StringAggGroupsAccumulator {
370 fn update_batch(
371 &mut self,
372 values: &[ArrayRef],
373 group_indices: &[usize],
374 opt_filter: Option<&BooleanArray>,
375 total_num_groups: usize,
376 ) -> Result<()> {
377 self.values.resize(total_num_groups, None);
378 let array = apply_filter_as_nulls(&values[0], opt_filter)?;
379 match array.data_type() {
380 DataType::Utf8 => {
381 self.append_batch(array.as_string::<i32>().iter(), group_indices)
382 }
383 DataType::LargeUtf8 => {
384 self.append_batch(array.as_string::<i64>().iter(), group_indices)
385 }
386 DataType::Utf8View => {
387 self.append_batch(array.as_string_view().iter(), group_indices)
388 }
389 other => {
390 return internal_err!("string_agg unexpected data type: {other}");
391 }
392 }
393 Ok(())
394 }
395
396 fn evaluate(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
397 let to_emit = emit_to.take_needed(&mut self.values);
398 let emitted_bytes: usize = to_emit
399 .iter()
400 .filter_map(|opt| opt.as_ref().map(|s| s.len()))
401 .sum();
402 self.total_data_bytes -= emitted_bytes;
403
404 let result: ArrayRef = Arc::new(LargeStringArray::from(to_emit));
405 Ok(result)
406 }
407
408 fn state(&mut self, emit_to: EmitTo) -> Result<Vec<ArrayRef>> {
409 self.evaluate(emit_to).map(|arr| vec![arr])
410 }
411
412 fn merge_batch(
413 &mut self,
414 values: &[ArrayRef],
415 group_indices: &[usize],
416 total_num_groups: usize,
417 ) -> Result<()> {
418 self.update_batch(values, group_indices, None, total_num_groups)
420 }
421
422 fn convert_to_state(
423 &self,
424 values: &[ArrayRef],
425 opt_filter: Option<&BooleanArray>,
426 ) -> Result<Vec<ArrayRef>> {
427 let input = apply_filter_as_nulls(&values[0], opt_filter)?;
428 let result = if input.data_type() == &DataType::LargeUtf8 {
429 input
430 } else {
431 arrow::compute::cast(&input, &DataType::LargeUtf8)?
432 };
433 Ok(vec![result])
434 }
435 fn size(&self) -> usize {
436 self.total_data_bytes
437 + self.values.capacity() * size_of::<Option<String>>()
438 + self.delimiter.capacity()
439 + size_of_val(self)
440 }
441}
442
443#[derive(Debug)]
446pub(crate) struct SimpleStringAggAccumulator {
447 delimiter: String,
448 accumulated_string: String,
450 has_value: bool,
451}
452
453impl SimpleStringAggAccumulator {
454 pub fn new(delimiter: &str) -> Self {
455 Self {
456 delimiter: delimiter.to_string(),
457 accumulated_string: String::new(),
458 has_value: false,
459 }
460 }
461
462 #[inline]
463 fn append_strings<'a, I>(&mut self, iter: I)
464 where
465 I: Iterator<Item = Option<&'a str>>,
466 {
467 for value in iter.flatten() {
468 if self.has_value {
469 self.accumulated_string.push_str(&self.delimiter);
470 }
471
472 self.accumulated_string.push_str(value);
473 self.has_value = true;
474 }
475 }
476}
477
478impl Accumulator for SimpleStringAggAccumulator {
479 fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
480 let string_arr = values.first().ok_or_else(|| {
481 internal_datafusion_err!(
482 "Planner should ensure its first arg is Utf8/Utf8View"
483 )
484 })?;
485
486 match string_arr.data_type() {
487 DataType::Utf8 => self.append_strings(string_arr.as_string::<i32>().iter()),
488 DataType::LargeUtf8 => {
489 self.append_strings(string_arr.as_string::<i64>().iter())
490 }
491 DataType::Utf8View => self.append_strings(string_arr.as_string_view().iter()),
492 other => {
493 return internal_err!(
494 "Planner should ensure string_agg first argument is Utf8-like, found {other}"
495 );
496 }
497 }
498
499 Ok(())
500 }
501
502 fn evaluate(&mut self) -> Result<ScalarValue> {
503 if self.has_value {
504 Ok(ScalarValue::LargeUtf8(Some(
505 self.accumulated_string.clone(),
506 )))
507 } else {
508 Ok(ScalarValue::LargeUtf8(None))
509 }
510 }
511
512 fn size(&self) -> usize {
513 size_of_val(self) + self.delimiter.capacity() + self.accumulated_string.capacity()
514 }
515
516 fn state(&mut self) -> Result<Vec<ScalarValue>> {
517 let result = if self.has_value {
518 ScalarValue::LargeUtf8(Some(std::mem::take(&mut self.accumulated_string)))
519 } else {
520 ScalarValue::LargeUtf8(None)
521 };
522 self.has_value = false;
523
524 Ok(vec![result])
525 }
526
527 fn merge_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
528 self.update_batch(values)
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535 use arrow::array::LargeStringArray;
536 use arrow::compute::SortOptions;
537 use arrow::datatypes::{Fields, Schema};
538 use datafusion_physical_expr::expressions::Column;
539 use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr;
540 use std::sync::Arc;
541
542 #[test]
543 fn no_duplicates_no_distinct() -> Result<()> {
544 let (mut acc1, mut acc2) = StringAggAccumulatorBuilder::new(",").build_two()?;
545
546 acc1.update_batch(&[data(["a", "b", "c"]), data([","])])?;
547 acc2.update_batch(&[data(["d", "e", "f"]), data([","])])?;
548 acc1 = merge(acc1, acc2)?;
549
550 let result = some_str(acc1.evaluate()?);
551
552 assert_eq!(result, "a,b,c,d,e,f");
553
554 Ok(())
555 }
556
557 #[test]
558 fn no_duplicates_distinct() -> Result<()> {
559 let (mut acc1, mut acc2) = StringAggAccumulatorBuilder::new(",")
560 .distinct()
561 .build_two()?;
562
563 acc1.update_batch(&[data(["a", "b", "c"]), data([","])])?;
564 acc2.update_batch(&[data(["d", "e", "f"]), data([","])])?;
565 acc1 = merge(acc1, acc2)?;
566
567 let result = some_str_sorted(acc1.evaluate()?, ",");
568
569 assert_eq!(result, "a,b,c,d,e,f");
570
571 Ok(())
572 }
573
574 #[test]
575 fn duplicates_no_distinct() -> Result<()> {
576 let (mut acc1, mut acc2) = StringAggAccumulatorBuilder::new(",").build_two()?;
577
578 acc1.update_batch(&[data(["a", "b", "c"]), data([","])])?;
579 acc2.update_batch(&[data(["a", "b", "c"]), data([","])])?;
580 acc1 = merge(acc1, acc2)?;
581
582 let result = some_str(acc1.evaluate()?);
583
584 assert_eq!(result, "a,b,c,a,b,c");
585
586 Ok(())
587 }
588
589 #[test]
590 fn duplicates_distinct() -> Result<()> {
591 let (mut acc1, mut acc2) = StringAggAccumulatorBuilder::new(",")
592 .distinct()
593 .build_two()?;
594
595 acc1.update_batch(&[data(["a", "b", "c"]), data([","])])?;
596 acc2.update_batch(&[data(["a", "b", "c"]), data([","])])?;
597 acc1 = merge(acc1, acc2)?;
598
599 let result = some_str_sorted(acc1.evaluate()?, ",");
600
601 assert_eq!(result, "a,b,c");
602
603 Ok(())
604 }
605
606 #[test]
607 fn no_duplicates_distinct_sort_asc() -> Result<()> {
608 let (mut acc1, mut acc2) = StringAggAccumulatorBuilder::new(",")
609 .distinct()
610 .order_by_col("col", SortOptions::new(false, false))
611 .build_two()?;
612
613 acc1.update_batch(&[data(["e", "b", "d"]), data([","])])?;
614 acc2.update_batch(&[data(["f", "a", "c"]), data([","])])?;
615 acc1 = merge(acc1, acc2)?;
616
617 let result = some_str(acc1.evaluate()?);
618
619 assert_eq!(result, "a,b,c,d,e,f");
620
621 Ok(())
622 }
623
624 #[test]
625 fn no_duplicates_distinct_sort_desc() -> Result<()> {
626 let (mut acc1, mut acc2) = StringAggAccumulatorBuilder::new(",")
627 .distinct()
628 .order_by_col("col", SortOptions::new(true, false))
629 .build_two()?;
630
631 acc1.update_batch(&[data(["e", "b", "d"]), data([","])])?;
632 acc2.update_batch(&[data(["f", "a", "c"]), data([","])])?;
633 acc1 = merge(acc1, acc2)?;
634
635 let result = some_str(acc1.evaluate()?);
636
637 assert_eq!(result, "f,e,d,c,b,a");
638
639 Ok(())
640 }
641
642 #[test]
643 fn duplicates_distinct_sort_asc() -> Result<()> {
644 let (mut acc1, mut acc2) = StringAggAccumulatorBuilder::new(",")
645 .distinct()
646 .order_by_col("col", SortOptions::new(false, false))
647 .build_two()?;
648
649 acc1.update_batch(&[data(["a", "c", "b"]), data([","])])?;
650 acc2.update_batch(&[data(["b", "c", "a"]), data([","])])?;
651 acc1 = merge(acc1, acc2)?;
652
653 let result = some_str(acc1.evaluate()?);
654
655 assert_eq!(result, "a,b,c");
656
657 Ok(())
658 }
659
660 #[test]
661 fn duplicates_distinct_sort_desc() -> Result<()> {
662 let (mut acc1, mut acc2) = StringAggAccumulatorBuilder::new(",")
663 .distinct()
664 .order_by_col("col", SortOptions::new(true, false))
665 .build_two()?;
666
667 acc1.update_batch(&[data(["a", "c", "b"]), data([","])])?;
668 acc2.update_batch(&[data(["b", "c", "a"]), data([","])])?;
669 acc1 = merge(acc1, acc2)?;
670
671 let result = some_str(acc1.evaluate()?);
672
673 assert_eq!(result, "c,b,a");
674
675 Ok(())
676 }
677
678 struct StringAggAccumulatorBuilder {
679 sep: String,
680 distinct: bool,
681 order_bys: Vec<PhysicalSortExpr>,
682 schema: Schema,
683 }
684
685 impl StringAggAccumulatorBuilder {
686 fn new(sep: &str) -> Self {
687 Self {
688 sep: sep.to_string(),
689 distinct: Default::default(),
690 order_bys: vec![],
691 schema: Schema {
692 fields: Fields::from(vec![Field::new(
693 "col",
694 DataType::LargeUtf8,
695 true,
696 )]),
697 metadata: Default::default(),
698 },
699 }
700 }
701 fn distinct(mut self) -> Self {
702 self.distinct = true;
703 self
704 }
705
706 fn order_by_col(mut self, col: &str, sort_options: SortOptions) -> Self {
707 self.order_bys.extend([PhysicalSortExpr::new(
708 Arc::new(
709 Column::new_with_schema(col, &self.schema)
710 .expect("column not available in schema"),
711 ),
712 sort_options,
713 )]);
714 self
715 }
716
717 fn build(&self) -> Result<Box<dyn Accumulator>> {
718 StringAgg::new().accumulator(AccumulatorArgs {
719 return_field: Field::new("f", DataType::LargeUtf8, true).into(),
720 schema: &self.schema,
721 expr_fields: &[
722 Field::new("col", DataType::LargeUtf8, true).into(),
723 Field::new("lit", DataType::Utf8, false).into(),
724 ],
725 ignore_nulls: false,
726 order_bys: &self.order_bys,
727 is_reversed: false,
728 name: "",
729 is_distinct: self.distinct,
730 exprs: &[
731 Arc::new(Column::new("col", 0)),
732 Arc::new(Literal::new(ScalarValue::Utf8(Some(self.sep.to_string())))),
733 ],
734 })
735 }
736
737 fn build_two(&self) -> Result<(Box<dyn Accumulator>, Box<dyn Accumulator>)> {
738 Ok((self.build()?, self.build()?))
739 }
740 }
741
742 fn some_str(value: ScalarValue) -> String {
743 str(value)
744 .expect("ScalarValue was not a String")
745 .expect("ScalarValue was None")
746 }
747
748 fn some_str_sorted(value: ScalarValue, sep: &str) -> String {
749 let value = some_str(value);
750 let mut parts: Vec<&str> = value.split(sep).collect();
751 parts.sort();
752 parts.join(sep)
753 }
754
755 fn str(value: ScalarValue) -> Result<Option<String>> {
756 match value {
757 ScalarValue::LargeUtf8(v) => Ok(v),
758 _ => internal_err!(
759 "Expected ScalarValue::LargeUtf8, got {}",
760 value.data_type()
761 ),
762 }
763 }
764
765 fn data<const N: usize>(list: [&str; N]) -> ArrayRef {
766 Arc::new(LargeStringArray::from(list.to_vec()))
767 }
768
769 fn merge(
770 mut acc1: Box<dyn Accumulator>,
771 mut acc2: Box<dyn Accumulator>,
772 ) -> Result<Box<dyn Accumulator>> {
773 let intermediate_state = acc2.state().and_then(|e| {
774 e.iter()
775 .map(|v| v.to_array())
776 .collect::<Result<Vec<ArrayRef>>>()
777 })?;
778 acc1.merge_batch(&intermediate_state)?;
779 Ok(acc1)
780 }
781
782 fn make_groups_acc(delimiter: &str) -> StringAggGroupsAccumulator {
787 StringAggGroupsAccumulator::new(delimiter.to_string())
788 }
789
790 fn evaluate_groups(
792 acc: &mut StringAggGroupsAccumulator,
793 emit_to: EmitTo,
794 ) -> Vec<Option<String>> {
795 let result = acc.evaluate(emit_to).unwrap();
796 let arr = result.as_any().downcast_ref::<LargeStringArray>().unwrap();
797 arr.iter().map(|v| v.map(|s| s.to_string())).collect()
798 }
799
800 #[test]
801 fn groups_basic() -> Result<()> {
802 let mut acc = make_groups_acc(",");
803
804 let values: ArrayRef =
806 Arc::new(LargeStringArray::from(vec!["a", "b", "c", "d", "e", "f"]));
807 let group_indices = vec![0, 1, 2, 0, 1, 2];
808 acc.update_batch(&[values], &group_indices, None, 3)?;
809
810 let result = evaluate_groups(&mut acc, EmitTo::All);
811 assert_eq!(
812 result,
813 vec![
814 Some("a,d".to_string()),
815 Some("b,e".to_string()),
816 Some("c,f".to_string()),
817 ]
818 );
819 Ok(())
820 }
821
822 #[test]
823 fn groups_with_nulls() -> Result<()> {
824 let mut acc = make_groups_acc("|");
825
826 let values: ArrayRef = Arc::new(LargeStringArray::from(vec![
830 Some("a"),
831 None,
832 Some("c"),
833 None,
834 Some("b"),
835 None,
836 ]));
837 let group_indices = vec![0, 1, 0, 2, 1, 2];
838 acc.update_batch(&[values], &group_indices, None, 3)?;
839
840 let result = evaluate_groups(&mut acc, EmitTo::All);
841 assert_eq!(
842 result,
843 vec![Some("a|c".to_string()), Some("b".to_string()), None,]
844 );
845 Ok(())
846 }
847
848 #[test]
849 fn groups_with_filter() -> Result<()> {
850 let mut acc = make_groups_acc(",");
851
852 let values: ArrayRef = Arc::new(LargeStringArray::from(vec!["a", "b", "c", "d"]));
853 let group_indices = vec![0, 0, 1, 1];
854 let filter = BooleanArray::from(vec![true, false, false, true]);
856 acc.update_batch(&[values], &group_indices, Some(&filter), 2)?;
857
858 let result = evaluate_groups(&mut acc, EmitTo::All);
859 assert_eq!(result, vec![Some("a".to_string()), Some("d".to_string())]);
860 Ok(())
861 }
862
863 #[test]
864 fn groups_emit_first() -> Result<()> {
865 let mut acc = make_groups_acc(",");
866
867 let values: ArrayRef =
868 Arc::new(LargeStringArray::from(vec!["a", "b", "c", "d", "e", "f"]));
869 let group_indices = vec![0, 1, 2, 0, 1, 2];
870 acc.update_batch(&[values], &group_indices, None, 3)?;
871
872 let result = evaluate_groups(&mut acc, EmitTo::First(2));
874 assert_eq!(
875 result,
876 vec![Some("a,d".to_string()), Some("b,e".to_string())]
877 );
878
879 let result = evaluate_groups(&mut acc, EmitTo::All);
881 assert_eq!(result, vec![Some("c,f".to_string())]);
882 Ok(())
883 }
884
885 #[test]
886 fn groups_merge_batch() -> Result<()> {
887 let mut acc = make_groups_acc(",");
888
889 let values: ArrayRef = Arc::new(LargeStringArray::from(vec!["a", "b"]));
891 acc.update_batch(&[values], &[0, 1], None, 2)?;
892
893 let partial_state: ArrayRef = Arc::new(LargeStringArray::from(vec!["c,d", "e"]));
895 acc.merge_batch(&[partial_state], &[0, 1], 2)?;
896
897 let result = evaluate_groups(&mut acc, EmitTo::All);
898 assert_eq!(
899 result,
900 vec![Some("a,c,d".to_string()), Some("b,e".to_string())]
901 );
902 Ok(())
903 }
904
905 #[test]
906 fn groups_empty_groups() -> Result<()> {
907 let mut acc = make_groups_acc(",");
908
909 let values: ArrayRef = Arc::new(LargeStringArray::from(vec!["a", "b"]));
911 acc.update_batch(&[values], &[0, 2], None, 4)?;
912
913 let result = evaluate_groups(&mut acc, EmitTo::All);
914 assert_eq!(
915 result,
916 vec![
917 Some("a".to_string()),
918 None, Some("b".to_string()),
920 None, ]
922 );
923 Ok(())
924 }
925
926 #[test]
927 fn groups_multiple_batches() -> Result<()> {
928 let mut acc = make_groups_acc("|");
929
930 let values: ArrayRef = Arc::new(LargeStringArray::from(vec!["a", "b"]));
932 acc.update_batch(&[values], &[0, 1], None, 2)?;
933
934 let values: ArrayRef = Arc::new(LargeStringArray::from(vec!["c", "d", "e"]));
936 acc.update_batch(&[values], &[0, 1, 2], None, 3)?;
937
938 let result = evaluate_groups(&mut acc, EmitTo::All);
939 assert_eq!(
940 result,
941 vec![
942 Some("a|c".to_string()),
943 Some("b|d".to_string()),
944 Some("e".to_string()),
945 ]
946 );
947 Ok(())
948 }
949}