Skip to main content

linkrs_query/executor/streaming/operators/blocking/
aggregate_operator.rs

1use std::collections::HashMap;
2
3use linkrs_core::columnar::{MaterializedBatch, RowKey};
4use linkrs_core::error::QueryError;
5use linkrs_core::types::expr::Expression;
6use linkrs_core::types::operators::AggregateFunction;
7use linkrs_core::value::NullType;
8use linkrs_core::Value;
9
10use crate::executor::base::{MemoryBudget, MemoryTracker};
11use crate::executor::expression::evaluator::ExpressionEvaluator;
12use crate::executor::streaming::chunk::DataChunk;
13use crate::executor::streaming::executor::{StreamingExecutor, ValueRowContext};
14use crate::executor::streaming::helpers::accumulator_states::{
15    accumulator_to_value, AggregateAccumulator,
16};
17use crate::executor::streaming::spill::{
18    finalize_partitions_with_runtime, HashPartitionConfig, HashPartitionSpiller, SpillManager,
19};
20
21use super::aggregate::{
22    value_to_partial_accumulator, AggregateState, FinalAggregateState, GroupByState,
23    PartialAggregateState, ACCUMULATOR_OVERHEAD_BYTES,
24};
25use super::helpers::{
26    aggregate_arg_field_name, count_identity_arg, emit_batch_slice, BlockingContext,
27};
28
29type BatchEvalResult = Option<(Vec<Vec<Value>>, Vec<Vec<Value>>)>;
30
31/// Evaluate the `GROUP BY` key of one row.
32///
33/// A key component that cannot be evaluated for this row becomes NULL, which
34/// is SQL's own propagation rule for an unknown key value: the row joins the
35/// NULL group instead of aborting the whole aggregation. Batch evaluation is
36/// the fast path; this is the authoritative per-row fallback, so all
37/// aggregation phases must funnel through it to keep group identity equal.
38fn eval_group_key(
39    row: &[Value],
40    col_names: &[String],
41    group_by_expressions: &[Expression],
42) -> Vec<Value> {
43    group_by_expressions
44        .iter()
45        .map(|expr| {
46            let mut ctx = ValueRowContext::from_names(row.to_vec(), col_names.to_vec());
47            ExpressionEvaluator::evaluate(expr, &mut ctx).unwrap_or(Value::Null(NullType::Null))
48        })
49        .collect()
50}
51
52pub(super) fn open_aggregate(state: &mut Option<AggregateState>, num_agg_funcs: usize) {
53    *state = Some(AggregateState {
54        group_map: HashMap::new(),
55        accumulator_overhead: num_agg_funcs * ACCUMULATOR_OVERHEAD_BYTES,
56        result_batch: MaterializedBatch::new(0, 0),
57        emitted_offset: 0,
58        partition_spiller: None,
59        spilled_runs: vec![],
60        current_partition: 0,
61        has_spilled: false,
62        output_complete: false,
63        col_names: vec![],
64    });
65}
66
67pub(super) fn open_groupby(state: &mut Option<GroupByState>) {
68    *state = Some(GroupByState {
69        batch: MaterializedBatch::new(0, 0),
70        col_names: vec![],
71        result_batch: MaterializedBatch::new(0, 0),
72        emitted_offset: 0,
73        partition_spiller: None,
74        spilled_runs: vec![],
75        current_partition: 0,
76        has_spilled: false,
77        output_complete: false,
78    });
79}
80
81pub(super) fn open_partial_aggregate(state: &mut Option<PartialAggregateState>) {
82    *state = Some(PartialAggregateState {
83        group_map: HashMap::new(),
84        col_names: vec![],
85        result_batch: MaterializedBatch::new(0, 0),
86        emitted_offset: 0,
87    });
88}
89
90pub(super) fn open_final_aggregate(state: &mut Option<FinalAggregateState>) {
91    *state = Some(FinalAggregateState {
92        group_map: HashMap::new(),
93        col_names: vec![],
94        result_batch: MaterializedBatch::new(0, 0),
95        emitted_offset: 0,
96    });
97}
98
99pub(super) fn next_aggregate(
100    group_by_expressions: &[Expression],
101    aggregate_functions: &[(AggregateFunction, Vec<Expression>)],
102    memory_tracker: &mut MemoryTracker,
103    state: &mut AggregateState,
104    ctx: &BlockingContext<'_>,
105    input: &mut StreamingExecutor,
106) -> Result<Option<DataChunk>, QueryError> {
107    let num_group_keys = group_by_expressions.len();
108    let has_group_keys = num_group_keys > 0;
109    let group_overhead = state.accumulator_overhead;
110
111    loop {
112        // Output phase: serve slices from the result batch.
113        if state.emitted_offset < state.result_batch.num_rows() {
114            return Ok(Some(emit_batch_slice(
115                &state.result_batch,
116                &mut state.emitted_offset,
117                ctx,
118            )));
119        }
120        if state.emitted_offset > 0 {
121            state.emitted_offset = 0;
122            state.result_batch.clear();
123            if !state.has_spilled || state.current_partition >= state.spilled_runs.len() {
124                state.output_complete = true;
125                return Ok(None);
126            }
127        }
128
129        // Replay phase
130        if state.has_spilled && state.partition_spiller.is_none() {
131            while state.current_partition < state.spilled_runs.len() {
132                if let Some(rt) = ctx.runtime.as_ref() {
133                    rt.ensure_not_cancelled()?;
134                }
135
136                let run = match &state.spilled_runs[state.current_partition] {
137                    Some(r) => r,
138                    None => {
139                        state.current_partition += 1;
140                        continue;
141                    }
142                };
143
144                let mut reader = crate::executor::streaming::spill::RunReader::open(run)?;
145                let mut partition_results = Vec::new();
146                let mut group_map: HashMap<RowKey, Vec<AggregateAccumulator>> = HashMap::new();
147                while let Some(row) = reader.read_row()? {
148                    let group_key: Vec<Value> = row.iter().take(num_group_keys).cloned().collect();
149                    let accs = group_map.entry(group_key).or_insert_with(|| {
150                        aggregate_functions
151                            .iter()
152                            .map(|(f, args)| {
153                                AggregateAccumulator::for_function(f, args)
154                                    .expect("every aggregate function has an accumulator")
155                            })
156                            .collect()
157                    });
158                    for (i, func) in aggregate_functions.iter().enumerate() {
159                        if let Some(acc) = accs.get_mut(i) {
160                            let partial_value = row
161                                .get(num_group_keys + i)
162                                .cloned()
163                                .unwrap_or(Value::Null(NullType::Null));
164                            if let Some(other) =
165                                value_to_partial_accumulator(&func.0, &func.1, &partial_value)
166                            {
167                                acc.merge(&other);
168                            }
169                        }
170                    }
171                }
172                for (group_key, accs) in group_map {
173                    let mut result_row = if has_group_keys {
174                        group_key
175                    } else {
176                        Vec::new()
177                    };
178                    for acc in accs {
179                        result_row.push(acc.finalize());
180                    }
181                    partition_results.push(result_row);
182                }
183
184                let _ = std::fs::remove_file(&run.path);
185                state.current_partition += 1;
186
187                if !partition_results.is_empty() {
188                    state.result_batch.clear();
189                    state.emitted_offset = 0;
190                    for result_row in partition_results {
191                        state.result_batch.append_row(result_row);
192                    }
193                    return Ok(Some(emit_batch_slice(
194                        &state.result_batch,
195                        &mut state.emitted_offset,
196                        ctx,
197                    )));
198                }
199            }
200            state.output_complete = true;
201            return Ok(None);
202        }
203
204        // Accumulation phase
205        let mut accumulating = true;
206        while accumulating {
207            match input.advance()? {
208                Some(mut chunk) => {
209                    if let Some(rt) = ctx.runtime.as_ref() {
210                        rt.ensure_not_cancelled()?;
211                    }
212                    // Opaque consumer: expand symbolic multiplicity so each logical row
213                    // accumulates once. The selection vector stays in place (consumed via
214                    // `visible_indices` below with no compaction benefit).
215                    let _ = chunk.expand_multiplicity_in_place();
216                    // Per-chunk fast path flags: a `Count` over a bare
217                    // variable backed by an identity column counts every
218                    // visible row without evaluating its argument.
219                    let count_units: Vec<bool> = aggregate_functions
220                        .iter()
221                        .map(|(func, func_args)| count_identity_arg(func, func_args, &chunk))
222                        .collect();
223                    if state.col_names.is_empty() {
224                        state.col_names = chunk.col_names();
225                    }
226                    let sm = ctx.runtime.as_ref().and_then(|rt| rt.get_spill_manager());
227                    // Rowless positions: the scan skipped the row view, so the
228                    // chunk carries data only in typed columns. With no group
229                    // keys and every aggregate a row-counting `Count`
230                    // (identity-backed, hence a unit, or argument-free), each
231                    // typed position contributes one unit through the shared
232                    // loop below: group keys stay empty and args stay units,
233                    // so no row is ever dereferenced. Any other rowless shape
234                    // is a planner-contract violation and panics loudly at
235                    // the row access instead of silently dropping input.
236                    let rowless_units: Option<usize> = if chunk.rows.is_empty()
237                        && !has_group_keys
238                        && aggregate_functions
239                            .iter()
240                            .enumerate()
241                            .all(|(i, (func, args))| {
242                                matches!(func, AggregateFunction::Count)
243                                    && (count_units[i] || args.is_empty())
244                            }) {
245                        chunk.typed_len()
246                    } else {
247                        None
248                    };
249                    let batch_eval: BatchEvalResult = if chunk.selection.is_none()
250                        && (!chunk.rows.is_empty() || rowless_units.is_some())
251                    {
252                        match chunk.evaluate_expressions(group_by_expressions, None) {
253                            Ok(keys) => {
254                                let mut args = Vec::with_capacity(aggregate_functions.len());
255                                let mut ok = true;
256                                for (i, (_func, func_args)) in
257                                    aggregate_functions.iter().enumerate()
258                                {
259                                    if count_units[i] {
260                                        // Identity columns are provably non-null and
261                                        // `Count` only observes null-ness: feed one
262                                        // unit per row without evaluating the argument.
263                                        // Rowless chunks contribute one unit per
264                                        // typed position.
265                                        args.push(vec![
266                                            Value::Int(1);
267                                            rowless_units.unwrap_or(chunk.rows.len())
268                                        ]);
269                                        continue;
270                                    }
271                                    if let Some(expr) = func_args.first() {
272                                        match chunk.evaluate_expression(expr, None) {
273                                            Ok(col) => args.push(col),
274                                            Err(_) => {
275                                                ok = false;
276                                                break;
277                                            }
278                                        }
279                                    } else {
280                                        ok = false;
281                                        break;
282                                    }
283                                }
284                                if ok {
285                                    Some((keys, args))
286                                } else {
287                                    None
288                                }
289                            }
290                            Err(_) => None,
291                        }
292                    } else {
293                        None
294                    };
295
296                    let empty_row: Vec<Value> = Vec::new();
297                    let visit: Vec<usize> = match rowless_units {
298                        Some(n) if chunk.selection.is_none() => (0..n).collect(),
299                        _ => chunk.visible_indices(),
300                    };
301                    for idx in visit {
302                        let row: &[Value] = match (rowless_units, chunk.rows.get(idx)) {
303                            (_, Some(stored)) => stored,
304                            (Some(_), None) => &empty_row,
305                            (None, None) => panic!(
306                                "rowless chunk reached the row loop outside the audited count morphology"
307                            ),
308                        };
309                        let group_key: Vec<Value> = match &batch_eval {
310                            Some((keys, _)) => keys.iter().map(|c| c[idx].clone()).collect(),
311                            None => eval_group_key(row, &state.col_names, group_by_expressions),
312                        };
313                        let arg_values: Vec<Value> = match &batch_eval {
314                            Some((_, args)) => args.iter().map(|c| c[idx].clone()).collect(),
315                            None => {
316                                let mut values = Vec::with_capacity(aggregate_functions.len());
317                                for (i, (_func, func_args)) in
318                                    aggregate_functions.iter().enumerate()
319                                {
320                                    if count_units[i] {
321                                        values.push(Value::Int(1));
322                                        continue;
323                                    }
324                                    let mut ctx = ValueRowContext::from_names(
325                                        row.to_vec(),
326                                        state.col_names.clone(),
327                                    );
328                                    let expr = func_args
329                                        .first()
330                                        .cloned()
331                                        .unwrap_or_else(|| Expression::Literal(Value::Int(1)));
332                                    values.push(
333                                        match ExpressionEvaluator::evaluate(&expr, &mut ctx) {
334                                            Ok(v) => v,
335                                            Err(_) => Value::Null(NullType::Null),
336                                        },
337                                    );
338                                }
339                                values
340                            }
341                        };
342                        let partial_row_of =
343                            |group_key: &[Value], arg_values: &[Value]| -> Vec<Value> {
344                                let mut partial_row = group_key.to_vec();
345                                for (i, (func, args)) in aggregate_functions.iter().enumerate() {
346                                    let value = arg_values
347                                        .get(i)
348                                        .cloned()
349                                        .unwrap_or_else(|| Value::Null(NullType::Null));
350                                    let mut acc = AggregateAccumulator::for_function(func, args)
351                                        .expect("every aggregate function has an accumulator");
352                                    acc.accumulate(&value);
353                                    partial_row.push(accumulator_to_value(&acc));
354                                }
355                                partial_row
356                            };
357                        if let Some(ref mut spiller) = state.partition_spiller {
358                            let manager = sm.clone().ok_or_else(|| {
359                                QueryError::execution("Spill manager not available".to_string())
360                            })?;
361                            let p = crate::executor::streaming::spill::hash_row_partition(
362                                &group_key,
363                                spiller.num_partitions(),
364                            ) as usize;
365                            let partial_row = partial_row_of(&group_key, &arg_values);
366                            spiller.insert_row_to_partition(&partial_row, p, &manager)?;
367                            continue;
368                        }
369                        if !state.group_map.contains_key(&group_key) {
370                            if let Err(e) = memory_tracker.try_reserve(
371                                MemoryBudget::estimate_row_memory(&group_key) + group_overhead,
372                            ) {
373                                if let Some(sm) =
374                                    ctx.runtime.as_ref().and_then(|rt| rt.get_spill_manager())
375                                {
376                                    let config = HashPartitionConfig::default();
377                                    let num_partitions = config.num_partitions;
378                                    let mut spiller = HashPartitionSpiller::new(config, &sm, 0)?;
379
380                                    for (key, accs) in std::mem::take(&mut state.group_map) {
381                                        let p =
382                                            crate::executor::streaming::spill::hash_row_partition(
383                                                &key,
384                                                num_partitions,
385                                            ) as usize;
386                                        let mut partial_row = key.clone();
387                                        for acc in &accs {
388                                            partial_row.push(accumulator_to_value(acc));
389                                        }
390                                        spiller.insert_row_to_partition(&partial_row, p, &sm)?;
391                                        memory_tracker.release(
392                                            MemoryBudget::estimate_row_memory(&key)
393                                                + group_overhead,
394                                        );
395                                    }
396
397                                    let p = crate::executor::streaming::spill::hash_row_partition(
398                                        &group_key,
399                                        num_partitions,
400                                    ) as usize;
401                                    let partial_row = partial_row_of(&group_key, &arg_values);
402                                    spiller.insert_row_to_partition(&partial_row, p, &sm)?;
403
404                                    state.partition_spiller = Some(spiller);
405                                    state.has_spilled = true;
406                                    continue;
407                                } else {
408                                    return Err(e);
409                                }
410                            }
411                        }
412
413                        let accs = state.group_map.entry(group_key).or_insert_with(|| {
414                            aggregate_functions
415                                .iter()
416                                .map(|(f, args)| {
417                                    AggregateAccumulator::for_function(f, args)
418                                        .expect("every aggregate function has an accumulator")
419                                })
420                                .collect()
421                        });
422                        for (i, (_func, _expr)) in aggregate_functions.iter().enumerate() {
423                            if let Some(acc) = accs.get_mut(i) {
424                                let value = arg_values
425                                    .get(i)
426                                    .cloned()
427                                    .unwrap_or_else(|| Value::Null(NullType::Null));
428                                acc.accumulate(&value);
429                            }
430                        }
431                    }
432                }
433                None => {
434                    accumulating = false;
435                }
436            }
437        }
438
439        // Finalize spilled runs and replay them within the loop.
440        if state.partition_spiller.is_some() {
441            let runs = finalize_partitions_with_runtime(
442                state.partition_spiller.take().unwrap(),
443                ctx.runtime.as_ref(),
444            )?;
445            state.spilled_runs = runs;
446            state.current_partition = 0;
447            continue;
448        }
449
450        // In-memory output: finalize accumulated groups into the result batch.
451        let group_map = std::mem::take(&mut state.group_map);
452        state.result_batch.clear();
453        state.emitted_offset = 0;
454        for (group_key, accs) in group_map {
455            let mut result_row = if has_group_keys {
456                group_key
457            } else {
458                Vec::new()
459            };
460            for acc in accs {
461                result_row.push(acc.finalize());
462            }
463            state.result_batch.append_row(result_row);
464        }
465        // Global aggregation (no GROUP BY keys) over empty input still emits
466        // a single row with default aggregate values (e.g. count = 0).
467        // (Empty input never spills, so the spilled path needs no equivalent.)
468        if !has_group_keys && state.result_batch.is_empty() {
469            let mut result_row = Vec::new();
470            for (func, args) in aggregate_functions.iter() {
471                let acc = AggregateAccumulator::for_function(func, args)
472                    .expect("every aggregate function has an accumulator");
473                result_row.push(acc.finalize());
474            }
475            state.result_batch.append_row(result_row);
476        }
477
478        if state.result_batch.is_empty() {
479            state.output_complete = true;
480            return Ok(None);
481        }
482        return Ok(Some(emit_batch_slice(
483            &state.result_batch,
484            &mut state.emitted_offset,
485            ctx,
486        )));
487    }
488}
489
490pub(super) fn next_groupby(
491    group_by_expressions: &[Expression],
492    memory_tracker: &mut MemoryTracker,
493    state: &mut GroupByState,
494    ctx: &BlockingContext<'_>,
495    input: &mut StreamingExecutor,
496) -> Result<Option<DataChunk>, QueryError> {
497    use crate::executor::streaming::spill::RunReader;
498
499    let group_rows = |rows: Vec<Vec<Value>>, col_names: &[String]| -> Vec<Vec<Value>> {
500        let mut groups: HashMap<String, Vec<Vec<Value>>> = HashMap::new();
501        for row in rows {
502            let key_parts: Vec<String> = eval_group_key(&row, col_names, group_by_expressions)
503                .iter()
504                .map(|v| format!("{:?}", v))
505                .collect();
506            let key = key_parts.join("|");
507            groups.entry(key).or_default().push(row);
508        }
509        groups.into_values().flatten().collect()
510    };
511
512    loop {
513        if state.output_complete {
514            return Ok(None);
515        }
516
517        // Output phase: serve slices from the result batch.
518        if state.emitted_offset < state.result_batch.num_rows() {
519            return Ok(Some(emit_batch_slice(
520                &state.result_batch,
521                &mut state.emitted_offset,
522                ctx,
523            )));
524        }
525        if state.emitted_offset > 0 {
526            state.emitted_offset = 0;
527            state.result_batch.clear();
528            if !state.has_spilled || state.current_partition >= state.spilled_runs.len() {
529                state.output_complete = true;
530                return Ok(None);
531            }
532        }
533
534        // Replay phase
535        if state.has_spilled && state.partition_spiller.is_none() {
536            while state.current_partition < state.spilled_runs.len() {
537                if let Some(rt) = ctx.runtime.as_ref() {
538                    rt.ensure_not_cancelled()?;
539                }
540
541                let run = match &state.spilled_runs[state.current_partition] {
542                    Some(r) => r,
543                    None => {
544                        state.current_partition += 1;
545                        continue;
546                    }
547                };
548
549                let mut reader = RunReader::open(run)?;
550                let mut partition_rows = Vec::new();
551                while let Some(row) = reader.read_row()? {
552                    memory_tracker.try_reserve_row(&row)?;
553                    partition_rows.push(row);
554                }
555
556                let col_names = if state.col_names.is_empty() {
557                    (0..partition_rows.first().map_or(0, |r| r.len()))
558                        .map(|i| format!("col_{}", i))
559                        .collect()
560                } else {
561                    state.col_names.clone()
562                };
563                let result_rows = group_rows(partition_rows, &col_names);
564
565                let _ = std::fs::remove_file(&run.path);
566                state.current_partition += 1;
567                memory_tracker.reset();
568
569                if !result_rows.is_empty() {
570                    state.result_batch.clear();
571                    state.emitted_offset = 0;
572                    for result_row in result_rows {
573                        state.result_batch.append_row(result_row);
574                    }
575                    return Ok(Some(emit_batch_slice(
576                        &state.result_batch,
577                        &mut state.emitted_offset,
578                        ctx,
579                    )));
580                }
581            }
582            state.output_complete = true;
583            return Ok(None);
584        }
585
586        // Accumulation phase
587        let mut accumulating = true;
588        while accumulating {
589            match input.advance()? {
590                Some(mut chunk) => {
591                    if let Some(rt) = ctx.runtime.as_ref() {
592                        rt.ensure_not_cancelled()?;
593                    }
594                    // Opaque consumer: expand symbolic multiplicity and compact the
595                    // selection so every logical row enters the batch exactly once.
596                    chunk.normalize_for_opaque("GroupBy");
597                    if state.col_names.is_empty() {
598                        state.col_names = match chunk.col_names() {
599                            names if !names.is_empty() => names,
600                            _ => (0..chunk.rows.first().map_or(0, |r| r.len()))
601                                .map(|i| format!("col_{}", i))
602                                .collect(),
603                        };
604                    }
605                    for row in chunk.rows {
606                        if let Some(ref mut spiller) = state.partition_spiller {
607                            let manager = ctx
608                                .runtime
609                                .as_ref()
610                                .and_then(|rt| rt.get_spill_manager())
611                                .ok_or_else(|| {
612                                    QueryError::execution("Spill manager not available".to_string())
613                                })?;
614                            let group_key =
615                                eval_group_key(&row, &state.col_names, group_by_expressions);
616                            let p = crate::executor::streaming::spill::hash_row_partition(
617                                &group_key,
618                                spiller.num_partitions(),
619                            ) as usize;
620                            spiller.insert_row_to_partition(&row, p, &manager)?;
621                            continue;
622                        }
623                        if let Err(e) = memory_tracker.try_reserve_row(&row) {
624                            if let Some(sm) =
625                                ctx.runtime.as_ref().and_then(|rt| rt.get_spill_manager())
626                            {
627                                let config = HashPartitionConfig::default();
628                                let num_partitions = config.num_partitions;
629                                let mut spiller = HashPartitionSpiller::new(config, &sm, 0)?;
630
631                                for pending in state.batch.into_rows() {
632                                    let group_key = eval_group_key(
633                                        &pending,
634                                        &state.col_names,
635                                        group_by_expressions,
636                                    );
637                                    let p = crate::executor::streaming::spill::hash_row_partition(
638                                        &group_key,
639                                        num_partitions,
640                                    ) as usize;
641                                    spiller.insert_row_to_partition(&pending, p, &sm)?;
642                                    memory_tracker
643                                        .release(MemoryBudget::estimate_row_memory(&pending));
644                                }
645
646                                let group_key =
647                                    eval_group_key(&row, &state.col_names, group_by_expressions);
648                                let p = crate::executor::streaming::spill::hash_row_partition(
649                                    &group_key,
650                                    num_partitions,
651                                ) as usize;
652                                spiller.insert_row_to_partition(&row, p, &sm)?;
653
654                                state.partition_spiller = Some(spiller);
655                                state.has_spilled = true;
656                                continue;
657                            } else {
658                                return Err(e);
659                            }
660                        }
661                        state.batch.append_row(row);
662                    }
663                }
664                None => {
665                    accumulating = false;
666                }
667            }
668        }
669
670        // Finalize spilled runs
671        if state.partition_spiller.is_some() {
672            let runs = finalize_partitions_with_runtime(
673                state.partition_spiller.take().unwrap(),
674                ctx.runtime.as_ref(),
675            )?;
676            state.spilled_runs = runs;
677            state.current_partition = 0;
678            continue;
679        }
680
681        // In-memory output
682        if state.batch.is_empty() {
683            state.output_complete = true;
684            return Ok(None);
685        }
686        let col_names = if state.col_names.is_empty() {
687            (0..state.batch.num_columns())
688                .map(|i| format!("col_{}", i))
689                .collect()
690        } else {
691            state.col_names.clone()
692        };
693        let result_rows = group_rows(state.batch.into_rows(), &col_names);
694        state.result_batch.clear();
695        state.emitted_offset = 0;
696        for result_row in result_rows {
697            state.result_batch.append_row(result_row);
698        }
699        if state.result_batch.is_empty() {
700            state.output_complete = true;
701            return Ok(None);
702        }
703        return Ok(Some(emit_batch_slice(
704            &state.result_batch,
705            &mut state.emitted_offset,
706            ctx,
707        )));
708    }
709}
710
711pub(super) fn next_partial_aggregate(
712    group_by_expressions: &[Expression],
713    aggregate_functions: &[(AggregateFunction, Vec<Expression>)],
714    memory_tracker: &mut MemoryTracker,
715    state: &mut PartialAggregateState,
716    ctx: &BlockingContext<'_>,
717    input: &mut StreamingExecutor,
718) -> Result<Option<DataChunk>, QueryError> {
719    if state.emitted_offset < state.result_batch.num_rows() {
720        return Ok(Some(emit_batch_slice(
721            &state.result_batch,
722            &mut state.emitted_offset,
723            ctx,
724        )));
725    }
726    if state.emitted_offset > 0 {
727        return Ok(None);
728    }
729
730    let mut col_names: Vec<String> = vec![];
731    while let Some(mut chunk) = input.advance()? {
732        if let Some(rt) = ctx.runtime.as_ref() {
733            rt.ensure_not_cancelled()?;
734        }
735        // Opaque consumer: expand symbolic multiplicity so each logical row
736        // accumulates once. The selection vector stays in place (consumed via
737        // `visible_indices` below with no compaction benefit).
738        let _ = chunk.expand_multiplicity_in_place();
739        if col_names.is_empty() {
740            col_names = chunk.col_names();
741        }
742        let visible = chunk.visible_indices();
743        for idx in &visible {
744            memory_tracker.try_reserve_row(&chunk.rows[*idx])?;
745        }
746        // Vectorized group-key evaluation is a fast path over the whole
747        // chunk; a chunk it cannot handle falls back to the authoritative
748        // per-row helper below rather than failing the query.
749        let batch_keys: Option<Vec<Vec<Value>>> =
750            if chunk.selection.is_none() && !chunk.rows.is_empty() {
751                chunk.evaluate_expressions(group_by_expressions, None).ok()
752            } else {
753                None
754            };
755        let field_indices: Vec<Option<usize>> = aggregate_functions
756            .iter()
757            .map(|(func, args)| {
758                let field = aggregate_arg_field_name(func, args);
759                field.and_then(|f| col_names.iter().position(|c| c == &f))
760            })
761            .collect();
762        for idx in visible {
763            let row = &chunk.rows[idx];
764            let mut group_key = Vec::new();
765            if group_by_expressions.is_empty() {
766                group_key.push(Value::Null(NullType::Null));
767            } else {
768                match &batch_keys {
769                    Some(keys) => {
770                        group_key = keys.iter().map(|c| c[idx].clone()).collect();
771                    }
772                    None => {
773                        group_key = eval_group_key(row, &col_names, group_by_expressions);
774                    }
775                }
776            }
777
778            let group_accs = state.group_map.entry(group_key).or_insert_with(|| {
779                aggregate_functions
780                    .iter()
781                    .filter_map(|(f, args)| AggregateAccumulator::for_function(f, args))
782                    .collect()
783            });
784
785            for (i, (func, _args)) in aggregate_functions.iter().enumerate() {
786                if let Some(acc) = group_accs.get_mut(i) {
787                    let value = match &field_indices[i] {
788                        Some(j) => row
789                            .get(*j)
790                            .cloned()
791                            .unwrap_or_else(|| Value::Null(NullType::Null)),
792                        None => match func {
793                            AggregateFunction::Count => Value::Int(1),
794                            _ => Value::Null(NullType::Null),
795                        },
796                    };
797                    acc.accumulate(&value);
798                }
799            }
800        }
801    }
802
803    let mut result_rows: Vec<Vec<Value>> = Vec::new();
804    let num_group_keys = group_by_expressions.len();
805    for (group_key, accs) in std::mem::take(&mut state.group_map) {
806        let mut row = if num_group_keys == 0 {
807            vec![]
808        } else {
809            group_key
810        };
811        for (i, _func) in aggregate_functions.iter().enumerate() {
812            if let Some(acc) = accs.get(i) {
813                row.push(accumulator_to_value(acc));
814            } else {
815                row.push(Value::Null(NullType::Null));
816            }
817        }
818        result_rows.push(row);
819    }
820
821    state.result_batch.clear();
822    state.emitted_offset = 0;
823    for row in result_rows {
824        state.result_batch.append_row(row);
825    }
826
827    if state.result_batch.is_empty() {
828        Ok(None)
829    } else {
830        Ok(Some(emit_batch_slice(
831            &state.result_batch,
832            &mut state.emitted_offset,
833            ctx,
834        )))
835    }
836}
837
838pub(super) fn next_final_aggregate(
839    group_by_expressions: &[Expression],
840    aggregate_functions: &[(AggregateFunction, Vec<Expression>)],
841    memory_tracker: &mut MemoryTracker,
842    state: &mut FinalAggregateState,
843    ctx: &BlockingContext<'_>,
844    input: &mut StreamingExecutor,
845) -> Result<Option<DataChunk>, QueryError> {
846    if state.emitted_offset < state.result_batch.num_rows() {
847        return Ok(Some(emit_batch_slice(
848            &state.result_batch,
849            &mut state.emitted_offset,
850            ctx,
851        )));
852    }
853    if state.emitted_offset > 0 {
854        return Ok(None);
855    }
856
857    let mut col_names: Vec<String> = vec![];
858    while let Some(mut chunk) = input.advance()? {
859        if let Some(rt) = ctx.runtime.as_ref() {
860            rt.ensure_not_cancelled()?;
861        }
862        // Opaque consumer: expand symbolic multiplicity and compact the
863        // selection so every logical row merges exactly once.
864        chunk.normalize_for_opaque("Aggregate");
865        if col_names.is_empty() {
866            col_names = chunk.col_names();
867        }
868        for row in &chunk.rows {
869            memory_tracker.try_reserve_row(row)?;
870        }
871        for row in chunk.rows {
872            let num_group_keys = group_by_expressions.len();
873            let num_agg_funcs = aggregate_functions.len();
874            let group_key: Vec<Value> = if num_group_keys == 0 {
875                vec![Value::Null(NullType::Null)]
876            } else {
877                row[0..num_group_keys].to_vec()
878            };
879
880            let group_accs = state.group_map.entry(group_key).or_insert_with(|| {
881                aggregate_functions
882                    .iter()
883                    .filter_map(|(f, args)| AggregateAccumulator::for_function(f, args))
884                    .collect()
885            });
886
887            for (i, func) in aggregate_functions.iter().enumerate().take(num_agg_funcs) {
888                if let Some(acc) = group_accs.get_mut(i) {
889                    let acc_col_idx = num_group_keys + i;
890                    let partial_value = row.get(acc_col_idx);
891                    if let Some(val) = partial_value {
892                        let partial_acc = value_to_partial_accumulator(&func.0, &func.1, val);
893                        if let Some(other) = partial_acc {
894                            acc.merge(&other);
895                        }
896                    }
897                }
898            }
899        }
900    }
901
902    let mut result_rows: Vec<Vec<Value>> = Vec::new();
903    for (group_key, accs) in std::mem::take(&mut state.group_map) {
904        let mut row = if group_by_expressions.is_empty() {
905            vec![]
906        } else {
907            group_key
908        };
909        for (_i, _func) in aggregate_functions.iter().enumerate() {
910            if let Some(acc) = accs.get(_i) {
911                row.push(acc.finalize());
912            } else {
913                row.push(Value::Null(NullType::Null));
914            }
915        }
916        result_rows.push(row);
917    }
918
919    state.result_batch.clear();
920    state.emitted_offset = 0;
921    for row in result_rows {
922        state.result_batch.append_row(row);
923    }
924
925    if state.result_batch.is_empty() {
926        Ok(None)
927    } else {
928        Ok(Some(emit_batch_slice(
929            &state.result_batch,
930            &mut state.emitted_offset,
931            ctx,
932        )))
933    }
934}
935
936pub(super) fn close_aggregate(state: &mut Option<AggregateState>) {
937    if let Some(ref s) = state {
938        for r in s.spilled_runs.iter().flatten() {
939            let _ = std::fs::remove_file(&r.path);
940        }
941    }
942    *state = None;
943}
944
945pub(super) fn close_groupby(state: &mut Option<GroupByState>) {
946    if let Some(ref s) = state {
947        for r in s.spilled_runs.iter().flatten() {
948            let _ = std::fs::remove_file(&r.path);
949        }
950    }
951    *state = None;
952}
953
954pub(super) fn close_partial_aggregate(state: &mut Option<PartialAggregateState>) {
955    *state = None;
956}
957
958pub(super) fn close_final_aggregate(state: &mut Option<FinalAggregateState>) {
959    *state = None;
960}
961
962pub(super) fn spill_aggregate(
963    state: &mut AggregateState,
964    memory_tracker: &mut MemoryTracker,
965    sm: &SpillManager,
966) -> Result<(), QueryError> {
967    if state.partition_spiller.is_none() && !state.group_map.is_empty() {
968        let config = HashPartitionConfig::default();
969        let num_partitions = config.num_partitions;
970        let mut spiller = HashPartitionSpiller::new(config, sm, 0)?;
971        for (key, accs) in std::mem::take(&mut state.group_map) {
972            let p = crate::executor::streaming::spill::hash_row_partition(&key, num_partitions)
973                as usize;
974            let mut partial_row = key.clone();
975            for acc in &accs {
976                partial_row.push(accumulator_to_value(acc));
977            }
978            spiller.insert_row_to_partition(&partial_row, p, sm)?;
979            memory_tracker
980                .release(MemoryBudget::estimate_row_memory(&key) + state.accumulator_overhead);
981        }
982        state.partition_spiller = Some(spiller);
983        state.has_spilled = true;
984    }
985    Ok(())
986}
987
988pub(super) fn spill_groupby(
989    state: &mut GroupByState,
990    memory_tracker: &mut MemoryTracker,
991    group_by_expressions: &[Expression],
992    sm: &SpillManager,
993) -> Result<(), QueryError> {
994    if state.partition_spiller.is_none() && !state.batch.is_empty() {
995        let config = HashPartitionConfig::default();
996        let num_partitions = config.num_partitions;
997        let mut spiller = HashPartitionSpiller::new(config, sm, 0)?;
998        for row in state.batch.into_rows() {
999            let mut group_key = Vec::new();
1000            for expr in group_by_expressions.iter() {
1001                let mut ctx = ValueRowContext::from_names(row.clone(), state.col_names.clone());
1002                group_key.push(
1003                    ExpressionEvaluator::evaluate(expr, &mut ctx)
1004                        .unwrap_or(Value::Null(NullType::Null)),
1005                );
1006            }
1007            let p =
1008                crate::executor::streaming::spill::hash_row_partition(&group_key, num_partitions)
1009                    as usize;
1010            spiller.insert_row_to_partition(&row, p, sm)?;
1011            memory_tracker.release(MemoryBudget::estimate_row_memory(&row));
1012        }
1013        state.partition_spiller = Some(spiller);
1014        state.has_spilled = true;
1015    }
1016    Ok(())
1017}