1use std::hash::BuildHasher;
16use std::marker::PhantomData;
17use std::sync::Arc;
18
19use bytes::Bytes;
20use futures_async_stream::try_stream;
21use hashbrown::hash_map::Entry;
22use itertools::Itertools;
23use prometheus::core::Atomic;
24use risingwave_common::array::{DataChunk, StreamChunk};
25use risingwave_common::bitmap::{Bitmap, FilterByBitmap};
26use risingwave_common::catalog::{Field, Schema};
27use risingwave_common::hash::{HashKey, HashKeyDispatcher, PrecomputedBuildHasher};
28use risingwave_common::memory::MemoryContext;
29use risingwave_common::metrics::TrAdderAtomic;
30use risingwave_common::row::{OwnedRow, Row, RowExt};
31use risingwave_common::types::{DataType, ToOwnedDatum};
32use risingwave_common::util::chunk_coalesce::DataChunkBuilder;
33use risingwave_common::util::iter_util::ZipEqFast;
34use risingwave_common_estimate_size::EstimateSize;
35use risingwave_expr::aggregate::{AggCall, AggregateState, BoxedAggregateFunction};
36use risingwave_pb::Message;
37use risingwave_pb::batch_plan::HashAggNode;
38use risingwave_pb::batch_plan::plan_node::NodeBody;
39use risingwave_pb::data::DataChunk as PbDataChunk;
40
41use crate::error::{BatchError, Result};
42use crate::executor::aggregation::build as build_agg;
43use crate::executor::{
44 BoxedDataChunkStream, BoxedExecutor, BoxedExecutorBuilder, Executor, ExecutorBuilder,
45 WrapStreamExecutor,
46};
47use crate::monitor::BatchSpillMetrics;
48use crate::spill::spill_op::SpillBackend::Disk;
49use crate::spill::spill_op::{
50 DEFAULT_SPILL_PARTITION_NUM, SPILL_AT_LEAST_MEMORY, SpillBackend, SpillBuildHasher, SpillOp,
51};
52use crate::task::{ShutdownToken, TaskId};
53
54type AggHashMap<K, A> = hashbrown::HashMap<K, Vec<AggregateState>, PrecomputedBuildHasher, A>;
55
56impl HashKeyDispatcher for HashAggExecutorBuilder {
58 type Output = BoxedExecutor;
59
60 fn dispatch_impl<K: HashKey>(self) -> Self::Output {
61 Box::new(HashAggExecutor::<K>::new(
62 Arc::new(self.aggs),
63 self.group_key_columns,
64 self.group_key_types,
65 self.schema,
66 self.child,
67 self.identity,
68 self.chunk_size,
69 self.mem_context,
70 self.spill_backend,
71 self.spill_metrics,
72 self.shutdown_rx,
73 ))
74 }
75
76 fn data_types(&self) -> &[DataType] {
77 &self.group_key_types
78 }
79}
80
81pub struct HashAggExecutorBuilder {
82 aggs: Vec<BoxedAggregateFunction>,
83 group_key_columns: Vec<usize>,
84 group_key_types: Vec<DataType>,
85 child: BoxedExecutor,
86 schema: Schema,
87 #[expect(dead_code)]
88 task_id: TaskId,
89 identity: String,
90 chunk_size: usize,
91 mem_context: MemoryContext,
92 spill_backend: Option<SpillBackend>,
93 spill_metrics: Arc<BatchSpillMetrics>,
94 shutdown_rx: ShutdownToken,
95}
96
97impl HashAggExecutorBuilder {
98 fn deserialize(
99 hash_agg_node: &HashAggNode,
100 child: BoxedExecutor,
101 task_id: TaskId,
102 identity: String,
103 chunk_size: usize,
104 mem_context: MemoryContext,
105 spill_backend: Option<SpillBackend>,
106 spill_metrics: Arc<BatchSpillMetrics>,
107 shutdown_rx: ShutdownToken,
108 ) -> Result<BoxedExecutor> {
109 let aggs: Vec<_> = hash_agg_node
110 .get_agg_calls()
111 .iter()
112 .map(|agg| AggCall::from_protobuf(agg).and_then(|agg| build_agg(&agg)))
113 .try_collect()?;
114
115 let group_key_columns = hash_agg_node
116 .get_group_key()
117 .iter()
118 .map(|x| *x as usize)
119 .collect_vec();
120
121 let child_schema = child.schema();
122
123 let group_key_types = group_key_columns
124 .iter()
125 .map(|i| child_schema.fields[*i].data_type.clone())
126 .collect_vec();
127
128 let fields = group_key_types
129 .iter()
130 .cloned()
131 .chain(aggs.iter().map(|e| e.return_type()))
132 .map(Field::unnamed)
133 .collect::<Vec<Field>>();
134
135 let builder = HashAggExecutorBuilder {
136 aggs,
137 group_key_columns,
138 group_key_types,
139 child,
140 schema: Schema { fields },
141 task_id,
142 identity,
143 chunk_size,
144 mem_context,
145 spill_backend,
146 spill_metrics,
147 shutdown_rx,
148 };
149
150 Ok(builder.dispatch())
151 }
152}
153
154impl BoxedExecutorBuilder for HashAggExecutorBuilder {
155 async fn new_boxed_executor(
156 source: &ExecutorBuilder<'_>,
157 inputs: Vec<BoxedExecutor>,
158 ) -> Result<BoxedExecutor> {
159 let [child]: [_; 1] = inputs.try_into().unwrap();
160
161 let hash_agg_node = try_match_expand!(
162 source.plan_node().get_node_body().unwrap(),
163 NodeBody::HashAgg
164 )?;
165
166 let identity = source.plan_node().get_identity();
167
168 let spill_metrics = source.context().spill_metrics();
169
170 Self::deserialize(
171 hash_agg_node,
172 child,
173 source.task_id.clone(),
174 identity.clone(),
175 source.context().get_config().developer.chunk_size,
176 source.context().create_executor_mem_context(identity),
177 if source.context().get_config().enable_spill {
178 Some(Disk)
179 } else {
180 None
181 },
182 spill_metrics,
183 source.shutdown_rx().clone(),
184 )
185 }
186}
187
188pub struct HashAggExecutor<K> {
190 aggs: Arc<Vec<BoxedAggregateFunction>>,
192 group_key_columns: Vec<usize>,
194 group_key_types: Vec<DataType>,
196 schema: Schema,
198 child: BoxedExecutor,
199 init_agg_state_executor: Option<BoxedExecutor>,
201 identity: String,
202 chunk_size: usize,
203 mem_context: MemoryContext,
204 spill_backend: Option<SpillBackend>,
205 spill_metrics: Arc<BatchSpillMetrics>,
206 memory_upper_bound: Option<u64>,
208 shutdown_rx: ShutdownToken,
209 _phantom: PhantomData<K>,
210}
211
212impl<K> HashAggExecutor<K> {
213 #[expect(clippy::too_many_arguments)]
214 pub fn new(
215 aggs: Arc<Vec<BoxedAggregateFunction>>,
216 group_key_columns: Vec<usize>,
217 group_key_types: Vec<DataType>,
218 schema: Schema,
219 child: BoxedExecutor,
220 identity: String,
221 chunk_size: usize,
222 mem_context: MemoryContext,
223 spill_backend: Option<SpillBackend>,
224 spill_metrics: Arc<BatchSpillMetrics>,
225 shutdown_rx: ShutdownToken,
226 ) -> Self {
227 Self::new_inner(
228 aggs,
229 group_key_columns,
230 group_key_types,
231 schema,
232 child,
233 None,
234 identity,
235 chunk_size,
236 mem_context,
237 spill_backend,
238 spill_metrics,
239 None,
240 shutdown_rx,
241 )
242 }
243
244 #[expect(clippy::too_many_arguments)]
245 fn new_inner(
246 aggs: Arc<Vec<BoxedAggregateFunction>>,
247 group_key_columns: Vec<usize>,
248 group_key_types: Vec<DataType>,
249 schema: Schema,
250 child: BoxedExecutor,
251 init_agg_state_executor: Option<BoxedExecutor>,
252 identity: String,
253 chunk_size: usize,
254 mem_context: MemoryContext,
255 spill_backend: Option<SpillBackend>,
256 spill_metrics: Arc<BatchSpillMetrics>,
257 memory_upper_bound: Option<u64>,
258 shutdown_rx: ShutdownToken,
259 ) -> Self {
260 HashAggExecutor {
261 aggs,
262 group_key_columns,
263 group_key_types,
264 schema,
265 child,
266 init_agg_state_executor,
267 identity,
268 chunk_size,
269 mem_context,
270 spill_backend,
271 spill_metrics,
272 memory_upper_bound,
273 shutdown_rx,
274 _phantom: PhantomData,
275 }
276 }
277}
278
279impl<K: HashKey + Send + Sync> Executor for HashAggExecutor<K> {
280 fn schema(&self) -> &Schema {
281 &self.schema
282 }
283
284 fn identity(&self) -> &str {
285 &self.identity
286 }
287
288 fn execute(self: Box<Self>) -> BoxedDataChunkStream {
289 self.do_execute()
290 }
291}
292
293pub struct AggSpillManager {
308 op: SpillOp,
309 partition_num: usize,
310 agg_state_writers: Vec<opendal::Writer>,
311 agg_state_chunk_builder: Vec<DataChunkBuilder>,
312 input_writers: Vec<opendal::Writer>,
313 input_chunk_builders: Vec<DataChunkBuilder>,
314 spill_build_hasher: SpillBuildHasher,
315 group_key_types: Vec<DataType>,
316 child_data_types: Vec<DataType>,
317 agg_data_types: Vec<DataType>,
318 spill_chunk_size: usize,
319 spill_metrics: Arc<BatchSpillMetrics>,
320}
321
322impl AggSpillManager {
323 fn new(
324 spill_backend: SpillBackend,
325 agg_identity: &String,
326 partition_num: usize,
327 group_key_types: Vec<DataType>,
328 agg_data_types: Vec<DataType>,
329 child_data_types: Vec<DataType>,
330 spill_chunk_size: usize,
331 spill_metrics: Arc<BatchSpillMetrics>,
332 ) -> Result<Self> {
333 let suffix_uuid = uuid::Uuid::new_v4();
334 let dir = format!("{}-{}/", agg_identity, suffix_uuid);
335 let op = SpillOp::create(dir, spill_backend)?;
336 let agg_state_writers = Vec::with_capacity(partition_num);
337 let agg_state_chunk_builder = Vec::with_capacity(partition_num);
338 let input_writers = Vec::with_capacity(partition_num);
339 let input_chunk_builders = Vec::with_capacity(partition_num);
340 let spill_build_hasher = SpillBuildHasher(suffix_uuid.as_u64_pair().1);
342 Ok(Self {
343 op,
344 partition_num,
345 agg_state_writers,
346 agg_state_chunk_builder,
347 input_writers,
348 input_chunk_builders,
349 spill_build_hasher,
350 group_key_types,
351 child_data_types,
352 agg_data_types,
353 spill_chunk_size,
354 spill_metrics,
355 })
356 }
357
358 async fn init_writers(&mut self) -> Result<()> {
359 for i in 0..self.partition_num {
360 let agg_state_partition_file_name = format!("agg-state-p{}", i);
361 let w = self.op.writer_with(&agg_state_partition_file_name).await?;
362 self.agg_state_writers.push(w);
363
364 let partition_file_name = format!("input-chunks-p{}", i);
365 let w = self.op.writer_with(&partition_file_name).await?;
366 self.input_writers.push(w);
367 self.input_chunk_builders.push(DataChunkBuilder::new(
368 self.child_data_types.clone(),
369 self.spill_chunk_size,
370 ));
371 self.agg_state_chunk_builder.push(DataChunkBuilder::new(
372 self.group_key_types
373 .iter()
374 .cloned()
375 .chain(self.agg_data_types.iter().cloned())
376 .collect(),
377 self.spill_chunk_size,
378 ));
379 }
380 Ok(())
381 }
382
383 async fn write_agg_state_row(&mut self, row: impl Row, hash_code: u64) -> Result<()> {
384 let partition = hash_code as usize % self.partition_num;
385 if let Some(output_chunk) = self.agg_state_chunk_builder[partition].append_one_row(row) {
386 let chunk_pb: PbDataChunk = output_chunk.to_protobuf();
387 let buf = Message::encode_to_vec(&chunk_pb);
388 let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes());
389 self.spill_metrics
390 .batch_spill_write_bytes
391 .inc_by((buf.len() + len_bytes.len()) as u64);
392 self.agg_state_writers[partition].write(len_bytes).await?;
393 self.agg_state_writers[partition].write(buf).await?;
394 }
395 Ok(())
396 }
397
398 async fn write_input_chunk(&mut self, chunk: DataChunk, hash_codes: Vec<u64>) -> Result<()> {
399 let (columns, vis) = chunk.into_parts_v2();
400 for partition in 0..self.partition_num {
401 let new_vis = vis.clone()
402 & Bitmap::from_iter(
403 hash_codes
404 .iter()
405 .map(|hash_code| (*hash_code as usize % self.partition_num) == partition),
406 );
407 let new_chunk = DataChunk::from_parts(columns.clone(), new_vis);
408 for output_chunk in self.input_chunk_builders[partition].append_chunk(new_chunk) {
409 let chunk_pb: PbDataChunk = output_chunk.to_protobuf();
410 let buf = Message::encode_to_vec(&chunk_pb);
411 let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes());
412 self.spill_metrics
413 .batch_spill_write_bytes
414 .inc_by((buf.len() + len_bytes.len()) as u64);
415 self.input_writers[partition].write(len_bytes).await?;
416 self.input_writers[partition].write(buf).await?;
417 }
418 }
419 Ok(())
420 }
421
422 async fn close_writers(&mut self) -> Result<()> {
423 for partition in 0..self.partition_num {
424 if let Some(output_chunk) = self.agg_state_chunk_builder[partition].consume_all() {
425 let chunk_pb: PbDataChunk = output_chunk.to_protobuf();
426 let buf = Message::encode_to_vec(&chunk_pb);
427 let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes());
428 self.spill_metrics
429 .batch_spill_write_bytes
430 .inc_by((buf.len() + len_bytes.len()) as u64);
431 self.agg_state_writers[partition].write(len_bytes).await?;
432 self.agg_state_writers[partition].write(buf).await?;
433 }
434
435 if let Some(output_chunk) = self.input_chunk_builders[partition].consume_all() {
436 let chunk_pb: PbDataChunk = output_chunk.to_protobuf();
437 let buf = Message::encode_to_vec(&chunk_pb);
438 let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes());
439 self.spill_metrics
440 .batch_spill_write_bytes
441 .inc_by((buf.len() + len_bytes.len()) as u64);
442 self.input_writers[partition].write(len_bytes).await?;
443 self.input_writers[partition].write(buf).await?;
444 }
445 }
446
447 for mut w in self.agg_state_writers.drain(..) {
448 w.close().await?;
449 }
450 for mut w in self.input_writers.drain(..) {
451 w.close().await?;
452 }
453 Ok(())
454 }
455
456 async fn read_agg_state_partition(&mut self, partition: usize) -> Result<BoxedDataChunkStream> {
457 let agg_state_partition_file_name = format!("agg-state-p{}", partition);
458 let r = self.op.reader_with(&agg_state_partition_file_name).await?;
459 Ok(SpillOp::read_stream(r, self.spill_metrics.clone()))
460 }
461
462 async fn read_input_partition(&mut self, partition: usize) -> Result<BoxedDataChunkStream> {
463 let input_partition_file_name = format!("input-chunks-p{}", partition);
464 let r = self.op.reader_with(&input_partition_file_name).await?;
465 Ok(SpillOp::read_stream(r, self.spill_metrics.clone()))
466 }
467
468 async fn estimate_partition_size(&self, partition: usize) -> Result<u64> {
469 let agg_state_partition_file_name = format!("agg-state-p{}", partition);
470 let agg_state_size = self
471 .op
472 .stat(&agg_state_partition_file_name)
473 .await?
474 .content_length();
475 let input_partition_file_name = format!("input-chunks-p{}", partition);
476 let input_size = self
477 .op
478 .stat(&input_partition_file_name)
479 .await?
480 .content_length();
481 Ok(agg_state_size + input_size)
482 }
483
484 async fn clear_partition(&mut self, partition: usize) -> Result<()> {
485 let agg_state_partition_file_name = format!("agg-state-p{}", partition);
486 self.op.delete(&agg_state_partition_file_name).await?;
487 let input_partition_file_name = format!("input-chunks-p{}", partition);
488 self.op.delete(&input_partition_file_name).await?;
489 Ok(())
490 }
491}
492
493impl<K: HashKey + Send + Sync> HashAggExecutor<K> {
494 #[try_stream(boxed, ok = DataChunk, error = BatchError)]
495 async fn do_execute(self: Box<Self>) {
496 let mem_context = MemoryContext::new(Some(self.mem_context.clone()), TrAdderAtomic::new(0));
499 let child_schema = self.child.schema().clone();
500 let mut need_to_spill = false;
501 let check_memory = match self.memory_upper_bound {
503 Some(upper_bound) => upper_bound > SPILL_AT_LEAST_MEMORY,
504 None => true,
505 };
506
507 let mut states_heap_size = 0;
509 let mut groups = AggHashMap::<K, _>::with_hasher_in(
511 PrecomputedBuildHasher,
512 mem_context.global_allocator(),
513 );
514
515 if let Some(init_agg_state_executor) = self.init_agg_state_executor {
516 let mut init_agg_state_stream = init_agg_state_executor.execute();
519 #[for_await]
520 for chunk in &mut init_agg_state_stream {
521 let chunk = chunk?;
522 let group_key_indices = (0..self.group_key_columns.len()).collect_vec();
523 let keys = K::build_many(&group_key_indices, &chunk);
524 let mut memory_usage_diff = 0;
525 for (row_id, key) in keys.into_iter().enumerate() {
526 let mut agg_states = vec![];
527 for i in 0..self.aggs.len() {
528 let agg = &self.aggs[i];
529 let datum = chunk
530 .row_at(row_id)
531 .0
532 .datum_at(self.group_key_columns.len() + i)
533 .to_owned_datum();
534 let agg_state = agg.decode_state(datum)?;
535 memory_usage_diff += agg_state.estimated_size() as i64;
536 agg_states.push(agg_state);
537 }
538 groups.try_insert(key, agg_states).unwrap();
539 }
540
541 states_heap_size += memory_usage_diff;
543 mem_context.add_unchecked(memory_usage_diff);
544 if check_memory && !mem_context.check_memory_usage() {
545 warn!(
546 "not enough memory to load one partition agg state after spill which is not a normal case, so keep going"
547 );
548 }
549 }
550 }
551
552 let mut input_stream = self.child.execute();
553 #[for_await]
555 for chunk in &mut input_stream {
556 let chunk = StreamChunk::from(chunk?);
557 let keys = K::build_many(self.group_key_columns.as_slice(), &chunk);
558 let mut memory_usage_diff = 0;
559 for (row_id, key) in keys
560 .into_iter()
561 .enumerate()
562 .filter_by_bitmap(chunk.visibility())
563 {
564 let mut new_group = false;
565 let states = match groups.entry(key) {
566 Entry::Occupied(entry) => entry.into_mut(),
567 Entry::Vacant(entry) => {
568 new_group = true;
569 let states = self
570 .aggs
571 .iter()
572 .map(|agg| agg.create_state())
573 .try_collect()?;
574 entry.insert(states)
575 }
576 };
577
578 for (agg, state) in self.aggs.iter().zip_eq_fast(states) {
580 if !new_group {
581 memory_usage_diff -= state.estimated_size() as i64;
582 }
583 agg.update_range(state, &chunk, row_id..row_id + 1).await?;
584 memory_usage_diff += state.estimated_size() as i64;
585 }
586 }
587 states_heap_size += memory_usage_diff;
589 mem_context.add_unchecked(memory_usage_diff);
590 if check_memory && !mem_context.check_memory_usage() {
591 if self.spill_backend.is_some() {
592 need_to_spill = true;
593 break;
594 } else {
595 Err(BatchError::OutOfMemory(self.mem_context.mem_limit()))?;
596 }
597 }
598 }
599
600 if need_to_spill {
601 info!(
609 "batch hash agg executor {} starts to spill out",
610 &self.identity
611 );
612 let mut agg_spill_manager = AggSpillManager::new(
613 self.spill_backend.clone().unwrap(),
614 &self.identity,
615 DEFAULT_SPILL_PARTITION_NUM,
616 self.group_key_types.clone(),
617 self.aggs.iter().map(|agg| agg.return_type()).collect(),
618 child_schema.data_types(),
619 self.chunk_size,
620 self.spill_metrics.clone(),
621 )?;
622 agg_spill_manager.init_writers().await?;
623
624 let mut memory_usage_diff = 0;
625 for (key, states) in groups {
627 let key_row = key.deserialize(&self.group_key_types)?;
628 let mut agg_datums = vec![];
629 for (agg, state) in self.aggs.iter().zip_eq_fast(states) {
630 let encode_state = agg.encode_state(&state)?;
631 memory_usage_diff -= state.estimated_size() as i64;
632 agg_datums.push(encode_state);
633 }
634 let agg_state_row = OwnedRow::from_iter(agg_datums.into_iter());
635 let hash_code = agg_spill_manager.spill_build_hasher.hash_one(key);
636 agg_spill_manager
637 .write_agg_state_row(key_row.chain(agg_state_row), hash_code)
638 .await?;
639 }
640
641 debug_assert_eq!(memory_usage_diff, -states_heap_size);
643 mem_context.add_unchecked(memory_usage_diff);
644
645 #[for_await]
647 for chunk in input_stream {
648 let chunk: DataChunk = chunk?;
649 let hash_codes = chunk.get_hash_values(
650 self.group_key_columns.as_slice(),
651 agg_spill_manager.spill_build_hasher,
652 );
653 agg_spill_manager
654 .write_input_chunk(
655 chunk,
656 hash_codes
657 .into_iter()
658 .map(|hash_code| hash_code.value())
659 .collect(),
660 )
661 .await?;
662 }
663
664 agg_spill_manager.close_writers().await?;
665
666 for i in 0..agg_spill_manager.partition_num {
668 let partition_size = agg_spill_manager.estimate_partition_size(i).await?;
669
670 let agg_state_stream = agg_spill_manager.read_agg_state_partition(i).await?;
671 let input_stream = agg_spill_manager.read_input_partition(i).await?;
672
673 let sub_hash_agg_executor: HashAggExecutor<K> = HashAggExecutor::new_inner(
674 self.aggs.clone(),
675 self.group_key_columns.clone(),
676 self.group_key_types.clone(),
677 self.schema.clone(),
678 Box::new(WrapStreamExecutor::new(child_schema.clone(), input_stream)),
679 Some(Box::new(WrapStreamExecutor::new(
680 self.schema.clone(),
681 agg_state_stream,
682 ))),
683 format!("{}-sub{}", self.identity.clone(), i),
684 self.chunk_size,
685 self.mem_context.clone(),
686 self.spill_backend.clone(),
687 self.spill_metrics.clone(),
688 Some(partition_size),
689 self.shutdown_rx.clone(),
690 );
691
692 debug!(
693 "create sub_hash_agg {} for hash_agg {} to spill",
694 sub_hash_agg_executor.identity, self.identity
695 );
696
697 let sub_hash_agg_stream = Box::new(sub_hash_agg_executor).execute();
698
699 #[for_await]
700 for chunk in sub_hash_agg_stream {
701 let chunk = chunk?;
702 yield chunk;
703 }
704
705 agg_spill_manager.clear_partition(i).await?;
707 }
708 } else {
709 let mut result = groups.iter_mut();
711 let cardinality = self.chunk_size;
712 loop {
713 let mut group_builders: Vec<_> = self
714 .group_key_types
715 .iter()
716 .map(|datatype| datatype.create_array_builder(cardinality))
717 .collect();
718
719 let mut agg_builders: Vec<_> = self
720 .aggs
721 .iter()
722 .map(|agg| agg.return_type().create_array_builder(cardinality))
723 .collect();
724
725 let mut has_next = false;
726 let mut array_len = 0;
727 for (key, states) in result.by_ref().take(cardinality) {
728 self.shutdown_rx.check()?;
729 has_next = true;
730 array_len += 1;
731 key.deserialize_to_builders(&mut group_builders[..], &self.group_key_types)?;
732 for ((agg, state), builder) in (self.aggs.iter())
733 .zip_eq_fast(states)
734 .zip_eq_fast(&mut agg_builders)
735 {
736 let result = agg.get_result(state).await?;
737 builder.append(result);
738 }
739 }
740 if !has_next {
741 break; }
743
744 let columns = group_builders
745 .into_iter()
746 .chain(agg_builders)
747 .map(|b| b.finish().into())
748 .collect::<Vec<_>>();
749
750 let output = DataChunk::new(columns, array_len);
751 yield output;
752 }
753 }
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use std::alloc::{AllocError, Allocator, Global, Layout};
760 use std::ptr::NonNull;
761 use std::sync::atomic::{AtomicBool, Ordering};
762
763 use allocator_api2::alloc::{AllocError as AllocErrorApi2, Allocator as AllocatorApi2};
764 use futures_async_stream::for_await;
765 use risingwave_common::metrics::LabelGuardedIntGauge;
766 use risingwave_common::test_prelude::DataChunkTestExt;
767 use risingwave_common::util::sort_util::{ColumnOrder, OrderType};
768 use risingwave_pb::data::PbDataType;
769 use risingwave_pb::data::data_type::TypeName;
770 use risingwave_pb::expr::agg_call::PbKind as PbAggKind;
771 use risingwave_pb::expr::{AggCall, InputRef};
772
773 use super::*;
774 use crate::executor::SortExecutor;
775 use crate::executor::test_utils::{MockExecutor, diff_executor_output};
776
777 const CHUNK_SIZE: usize = 1024;
778
779 #[tokio::test]
780 async fn execute_int32_grouped() {
781 let parent_mem = MemoryContext::root(LabelGuardedIntGauge::test_int_gauge::<4>(), u64::MAX);
782 {
783 let src_exec = Box::new(MockExecutor::with_chunk(
784 DataChunk::from_pretty(
785 "i i i
786 0 1 1
787 1 1 1
788 0 0 1
789 1 1 2
790 1 0 1
791 0 0 2
792 1 1 3
793 0 1 2",
794 ),
795 Schema::new(vec![
796 Field::unnamed(DataType::Int32),
797 Field::unnamed(DataType::Int32),
798 Field::unnamed(DataType::Int64),
799 ]),
800 ));
801
802 let agg_call = AggCall {
803 kind: PbAggKind::Sum as i32,
804 args: vec![InputRef {
805 index: 2,
806 r#type: Some(PbDataType {
807 type_name: TypeName::Int32 as i32,
808 ..Default::default()
809 }),
810 }],
811 return_type: Some(PbDataType {
812 type_name: TypeName::Int64 as i32,
813 ..Default::default()
814 }),
815 distinct: false,
816 order_by: vec![],
817 filter: None,
818 direct_args: vec![],
819 udf: None,
820 scalar: None,
821 };
822
823 let agg_prost = HashAggNode {
824 group_key: vec![0, 1],
825 agg_calls: vec![agg_call],
826 };
827
828 let mem_context = MemoryContext::new(
829 Some(parent_mem.clone()),
830 LabelGuardedIntGauge::test_int_gauge::<4>(),
831 );
832 let actual_exec = HashAggExecutorBuilder::deserialize(
833 &agg_prost,
834 src_exec,
835 TaskId::default(),
836 "HashAggExecutor".to_owned(),
837 CHUNK_SIZE,
838 mem_context.clone(),
839 None,
840 BatchSpillMetrics::for_test(),
841 ShutdownToken::empty(),
842 )
843 .unwrap();
844
845 let expect_exec = Box::new(MockExecutor::with_chunk(
847 DataChunk::from_pretty(
848 "i i I
849 1 0 1
850 0 0 3
851 0 1 3
852 1 1 6",
853 ),
854 Schema::new(vec![
855 Field::unnamed(DataType::Int32),
856 Field::unnamed(DataType::Int32),
857 Field::unnamed(DataType::Int64),
858 ]),
859 ));
860 diff_executor_output(actual_exec, expect_exec).await;
861
862 assert_eq!(mem_context.get_bytes_used(), 0);
864 }
865
866 assert_eq!(0, parent_mem.get_bytes_used());
868 }
869
870 #[tokio::test]
871 async fn execute_count_star() {
872 let src_exec = MockExecutor::with_chunk(
873 DataChunk::from_pretty(
874 "i
875 0
876 1
877 0
878 1
879 1
880 0
881 1
882 0",
883 ),
884 Schema::new(vec![Field::unnamed(DataType::Int32)]),
885 );
886
887 let agg_call = AggCall {
888 kind: PbAggKind::Count as i32,
889 args: vec![],
890 return_type: Some(PbDataType {
891 type_name: TypeName::Int64 as i32,
892 ..Default::default()
893 }),
894 distinct: false,
895 order_by: vec![],
896 filter: None,
897 direct_args: vec![],
898 udf: None,
899 scalar: None,
900 };
901
902 let agg_prost = HashAggNode {
903 group_key: vec![],
904 agg_calls: vec![agg_call],
905 };
906
907 let actual_exec = HashAggExecutorBuilder::deserialize(
908 &agg_prost,
909 Box::new(src_exec),
910 TaskId::default(),
911 "HashAggExecutor".to_owned(),
912 CHUNK_SIZE,
913 MemoryContext::none(),
914 None,
915 BatchSpillMetrics::for_test(),
916 ShutdownToken::empty(),
917 )
918 .unwrap();
919
920 let expect_exec = MockExecutor::with_chunk(
921 DataChunk::from_pretty(
922 "I
923 8",
924 ),
925 Schema::new(vec![Field::unnamed(DataType::Int64)]),
926 );
927 diff_executor_output(actual_exec, Box::new(expect_exec)).await;
928 }
929
930 #[test]
932 #[should_panic] fn test_hashmap_into_iter_bug() {
934 let dropped: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
935
936 {
937 struct MyAllocInner {
938 drop_flag: Arc<AtomicBool>,
939 }
940
941 #[derive(Clone)]
942 struct MyAlloc {
943 #[expect(dead_code)]
944 inner: Arc<MyAllocInner>,
945 }
946
947 impl Drop for MyAllocInner {
948 fn drop(&mut self) {
949 println!("MyAlloc freed.");
950 self.drop_flag.store(true, Ordering::SeqCst);
951 }
952 }
953
954 unsafe impl Allocator for MyAlloc {
955 fn allocate(
956 &self,
957 layout: Layout,
958 ) -> std::result::Result<NonNull<[u8]>, AllocError> {
959 let g = Global;
960 g.allocate(layout)
961 }
962
963 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
964 unsafe {
965 let g = Global;
966 g.deallocate(ptr, layout)
967 }
968 }
969 }
970
971 unsafe impl AllocatorApi2 for MyAlloc {
972 fn allocate(
973 &self,
974 layout: Layout,
975 ) -> std::result::Result<NonNull<[u8]>, AllocErrorApi2> {
976 let g = Global;
977 g.allocate(layout).map_err(|_| AllocErrorApi2)
978 }
979
980 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
981 unsafe {
982 let g = Global;
983 g.deallocate(ptr, layout)
984 }
985 }
986 }
987
988 let mut map = hashbrown::HashMap::with_capacity_in(
989 10,
990 MyAlloc {
991 inner: Arc::new(MyAllocInner {
992 drop_flag: dropped.clone(),
993 }),
994 },
995 );
996 for i in 0..10 {
997 map.entry(i).or_insert_with(|| "i".to_owned());
998 }
999
1000 for (k, v) in map {
1001 println!("{}, {}", k, v);
1002 }
1003 }
1004
1005 assert!(!dropped.load(Ordering::SeqCst));
1006 }
1007
1008 #[tokio::test]
1009 async fn test_shutdown() {
1010 let src_exec = MockExecutor::with_chunk(
1011 DataChunk::from_pretty(
1012 "i i i
1013 0 1 1",
1014 ),
1015 Schema::new(vec![Field::unnamed(DataType::Int32); 3]),
1016 );
1017
1018 let agg_call = AggCall {
1019 kind: PbAggKind::Sum as i32,
1020 args: vec![InputRef {
1021 index: 2,
1022 r#type: Some(PbDataType {
1023 type_name: TypeName::Int32 as i32,
1024 ..Default::default()
1025 }),
1026 }],
1027 return_type: Some(PbDataType {
1028 type_name: TypeName::Int64 as i32,
1029 ..Default::default()
1030 }),
1031 distinct: false,
1032 order_by: vec![],
1033 filter: None,
1034 direct_args: vec![],
1035 udf: None,
1036 scalar: None,
1037 };
1038
1039 let agg_prost = HashAggNode {
1040 group_key: vec![0, 1],
1041 agg_calls: vec![agg_call],
1042 };
1043
1044 let (shutdown_tx, shutdown_rx) = ShutdownToken::new();
1045 let actual_exec = HashAggExecutorBuilder::deserialize(
1046 &agg_prost,
1047 Box::new(src_exec),
1048 TaskId::default(),
1049 "HashAggExecutor".to_owned(),
1050 CHUNK_SIZE,
1051 MemoryContext::none(),
1052 None,
1053 BatchSpillMetrics::for_test(),
1054 shutdown_rx,
1055 )
1056 .unwrap();
1057
1058 shutdown_tx.cancel();
1059
1060 #[for_await]
1061 for data in actual_exec.execute() {
1062 assert!(data.is_err());
1063 break;
1064 }
1065 }
1066
1067 fn create_order_by_executor(child: BoxedExecutor) -> BoxedExecutor {
1068 let column_orders = child
1069 .schema()
1070 .fields
1071 .iter()
1072 .enumerate()
1073 .map(|(i, _)| ColumnOrder {
1074 column_index: i,
1075 order_type: OrderType::ascending(),
1076 })
1077 .collect_vec();
1078
1079 Box::new(SortExecutor::new(
1080 child,
1081 Arc::new(column_orders),
1082 "SortExecutor".into(),
1083 CHUNK_SIZE,
1084 MemoryContext::none(),
1085 None,
1086 BatchSpillMetrics::for_test(),
1087 ))
1088 }
1089
1090 #[tokio::test]
1091 async fn test_spill_hash_agg() {
1092 let src_exec = Box::new(MockExecutor::with_chunk(
1093 DataChunk::from_pretty(
1094 "i i i
1095 0 1 1
1096 1 1 1
1097 0 0 1
1098 1 1 2
1099 1 0 1
1100 0 0 2
1101 1 1 3
1102 0 1 2",
1103 ),
1104 Schema::new(vec![
1105 Field::unnamed(DataType::Int32),
1106 Field::unnamed(DataType::Int32),
1107 Field::unnamed(DataType::Int64),
1108 ]),
1109 ));
1110
1111 let agg_call = AggCall {
1112 kind: PbAggKind::Sum as i32,
1113 args: vec![InputRef {
1114 index: 2,
1115 r#type: Some(PbDataType {
1116 type_name: TypeName::Int32 as i32,
1117 ..Default::default()
1118 }),
1119 }],
1120 return_type: Some(PbDataType {
1121 type_name: TypeName::Int64 as i32,
1122 ..Default::default()
1123 }),
1124 distinct: false,
1125 order_by: vec![],
1126 filter: None,
1127 direct_args: vec![],
1128 udf: None,
1129 scalar: None,
1130 };
1131
1132 let agg_prost = HashAggNode {
1133 group_key: vec![0, 1],
1134 agg_calls: vec![agg_call],
1135 };
1136
1137 let mem_context =
1138 MemoryContext::new_with_mem_limit(None, LabelGuardedIntGauge::test_int_gauge::<4>(), 0);
1139 let actual_exec = HashAggExecutorBuilder::deserialize(
1140 &agg_prost,
1141 src_exec,
1142 TaskId::default(),
1143 "HashAggExecutor".to_owned(),
1144 CHUNK_SIZE,
1145 mem_context.clone(),
1146 Some(SpillBackend::Memory),
1147 BatchSpillMetrics::for_test(),
1148 ShutdownToken::empty(),
1149 )
1150 .unwrap();
1151
1152 let actual_exec = create_order_by_executor(actual_exec);
1153
1154 let expect_exec = Box::new(MockExecutor::with_chunk(
1155 DataChunk::from_pretty(
1156 "i i I
1157 1 0 1
1158 0 0 3
1159 0 1 3
1160 1 1 6",
1161 ),
1162 Schema::new(vec![
1163 Field::unnamed(DataType::Int32),
1164 Field::unnamed(DataType::Int32),
1165 Field::unnamed(DataType::Int64),
1166 ]),
1167 ));
1168
1169 let expect_exec = create_order_by_executor(expect_exec);
1170 diff_executor_output(actual_exec, expect_exec).await;
1171 }
1172}