Skip to main content

mz_compute/render/
reduce.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Reduction dataflow construction.
11//!
12//! Consult [ReducePlan] documentation for details.
13
14use std::collections::BTreeMap;
15
16use columnar::Columnar;
17use columnation::{Columnation, CopyRegion};
18use differential_dataflow::Diff as _;
19use differential_dataflow::collection::AsCollection;
20use differential_dataflow::columnar::layout::Coltainer;
21use differential_dataflow::consolidation::ConsolidatingContainerBuilder;
22use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
23use differential_dataflow::hashable::Hashable;
24use differential_dataflow::operators::arrange::{Arranged, TraceAgent};
25use differential_dataflow::trace::cursor::{BatchCursor, BatchDiff, BatchValOwn};
26use differential_dataflow::trace::implementations::BatchContainer;
27use differential_dataflow::trace::{Builder, Cursor, Navigable, Trace};
28use differential_dataflow::{Data, VecCollection};
29use itertools::Itertools;
30use mz_compute_types::dyncfgs::{ENABLE_COLUMNAR_ACCUMULABLE_DIFF, TEMPORAL_BUCKETING_SUMMARY};
31use mz_compute_types::plan::ArrangementStrategy;
32use mz_compute_types::plan::reduce::{
33    AccumulablePlan, BasicPlan, BucketedPlan, HierarchicalPlan, KeyValPlan, LirAggregateExpr,
34    MonotonicPlan, ReducePlan, ReductionType, SingleBasicPlan, reduction_type,
35};
36use mz_compute_types::plan::scalar::LirScalarExpr;
37use mz_expr::{AggregateFunc, EvalError, SafeMfpPlan};
38use mz_ore::cast::CastLossy;
39use mz_repr::adt::interval::Interval;
40use mz_repr::adt::numeric::{self, Numeric, NumericAgg, OrderedNumericAgg};
41use mz_repr::fixed_length::ExtendDatums;
42use mz_repr::{Datum, DatumVec, Diff, Row, RowArena, SharedRow};
43use mz_row_spine::{
44    DatumContainer, DatumSeq, RowBatcher, RowBuilder, RowRowBatcher, RowRowBuilder, RowValBatcher,
45    RowValBuilder,
46};
47use mz_timely_util::operator::CollectionExt;
48use num_traits::Float;
49use serde::{Deserialize, Serialize};
50use timely::Container;
51use timely::container::{CapacityContainerBuilder, PushInto};
52use tracing::warn;
53
54use crate::extensions::arrange::{ArrangementSize, KeyCollection, MzArrange};
55use crate::extensions::reduce::{ClearContainer, MzReduce};
56use crate::render::context::{CollectionBundle, Context};
57use crate::render::errors::DataflowErrorSer;
58use crate::render::errors::MaybeValidatingRow;
59use crate::render::reduce::monoids::{ReductionMonoid, get_monoid};
60use crate::render::{ArrangementFlavor, Pairer, RenderTimestamp};
61use crate::typedefs::ConsolidateBatcher;
62use crate::typedefs::{
63    ErrBatcher, RowAgent, RowErrBuilder, RowErrSpine, RowRowAgent, RowRowArrangement, RowRowSpine,
64    RowSpine, RowValSpine,
65};
66
67/// Key container of trace `Tr`'s batch cursor.
68type BatchKeyContainer<Tr> = <BatchCursor<Tr> as Cursor>::KeyContainer;
69
70impl<'scope, T: RenderTimestamp> Context<'scope, T> {
71    /// Renders a `MirRelationExpr::Reduce` using various non-obvious techniques to
72    /// minimize worst-case incremental update times and memory footprint.
73    pub fn render_reduce(
74        &self,
75        input_key: Option<Vec<LirScalarExpr>>,
76        input: CollectionBundle<'scope, T>,
77        key_val_plan: KeyValPlan,
78        reduce_plan: ReducePlan,
79        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
80        temporal_bucketing_strategy: ArrangementStrategy,
81    ) -> CollectionBundle<'scope, T>
82    where
83        T: crate::render::MaybeBucketByTime,
84    {
85        input.scope().region_named("Reduce", |inner| {
86            let KeyValPlan {
87                mut key_plan,
88                mut val_plan,
89            } = key_val_plan;
90            let key_arity = key_plan.projection.len();
91            let mut datums = DatumVec::new();
92
93            // Determine the columns we'll need from the row.
94            let mut demand = Vec::new();
95            demand.extend(key_plan.demand());
96            demand.extend(val_plan.demand());
97            demand.sort();
98            demand.dedup();
99
100            // remap column references to the subset we use.
101            let mut demand_map = BTreeMap::new();
102            for column in demand.iter() {
103                demand_map.insert(*column, demand_map.len());
104            }
105            let demand_map_len = demand_map.len();
106            key_plan.permute_fn(|c| demand_map[&c], demand_map_len);
107            val_plan.permute_fn(|c| demand_map[&c], demand_map_len);
108            let max_demand = demand.iter().max().map(|x| *x + 1).unwrap_or(0);
109            let skips = mz_compute_types::plan::reduce::convert_indexes_to_skips(demand);
110
111            let (key_val_input, err) = input
112                .enter_region(inner)
113                .flat_map::<ConsolidatingContainerBuilder<Vec<((Row, Row), T, Diff)>>, _>(
114                    input_key.map(|k| (k, None)),
115                    max_demand,
116                    move |row_datums, time, diff, ok_session, err_session| {
117                        let mut row_builder = SharedRow::get();
118                        let temp_storage = RowArena::new();
119
120                        let mut row_iter = row_datums.drain(..);
121                        let mut datums_local = datums.borrow();
122                        // Unpack only the demanded columns.
123                        for skip in skips.iter() {
124                            datums_local.push(row_iter.nth(*skip).unwrap());
125                        }
126
127                        // Evaluate the key expressions.
128                        let key = key_plan.evaluate_into(
129                            &mut datums_local,
130                            &temp_storage,
131                            &mut row_builder,
132                        );
133                        let key = match key {
134                            Err(e) => {
135                                err_session.give((e.into(), time, diff));
136                                return 1;
137                            }
138                            Ok(Some(key)) => key.clone(),
139                            Ok(None) => panic!("Row expected as no predicate was used"),
140                        };
141
142                        // Evaluate the value expressions.
143                        // The prior evaluation may have left additional columns we should delete.
144                        datums_local.truncate(skips.len());
145                        let val = val_plan.evaluate_into(
146                            &mut datums_local,
147                            &temp_storage,
148                            &mut row_builder,
149                        );
150                        let val = match val {
151                            Err(e) => {
152                                err_session.give((e.into(), time, diff));
153                                return 1;
154                            }
155                            Ok(Some(val)) => val.clone(),
156                            Ok(None) => panic!("Row expected as no predicate was used"),
157                        };
158
159                        ok_session.give(((key, val), time, diff));
160                        1
161                    },
162                );
163
164            // Bucket the keyed `(key, val)` stream when lowering chose `TemporalBucketing`.
165            // `Reduce` builds its own arrangement via `KeyValPlan`, bypassing
166            // `ensure_collections`, so the strategy is plumbed through `PlanNode::Reduce`
167            // rather than inferred at the arrangement site. No-op for `Direct`.
168            let key_val_collection = key_val_input.as_collection();
169            let key_val_collection = if matches!(
170                temporal_bucketing_strategy,
171                ArrangementStrategy::TemporalBucketing
172            ) {
173                let summary: mz_repr::Timestamp = TEMPORAL_BUCKETING_SUMMARY
174                    .get(&self.config_set)
175                    .try_into()
176                    .expect("must fit");
177                T::maybe_apply_temporal_bucketing_vec(
178                    key_val_collection.inner,
179                    self.as_of_frontier.clone(),
180                    summary,
181                )
182            } else {
183                key_val_collection
184            };
185
186            // Render the reduce plan
187            self.render_reduce_plan(reduce_plan, key_val_collection, err, key_arity, mfp_after)
188                .leave_region(self.scope)
189        })
190    }
191
192    /// Render a dataflow based on the provided plan.
193    ///
194    /// The output will be an arrangements that looks the same as if
195    /// we just had a single reduce operator computing everything together, and
196    /// this arrangement can also be re-used.
197    fn render_reduce_plan<'s>(
198        &self,
199        plan: ReducePlan,
200        collection: VecCollection<'s, T, (Row, Row), Diff>,
201        err_input: VecCollection<'s, T, DataflowErrorSer, Diff>,
202        key_arity: usize,
203        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
204    ) -> CollectionBundle<'s, T> {
205        let mut errors = Default::default();
206        let arrangement =
207            self.render_reduce_plan_inner(plan, collection, &mut errors, key_arity, mfp_after);
208        let errs: KeyCollection<_, _, _> = err_input.concatenate(errors).into();
209        CollectionBundle::from_columns(
210            0..key_arity,
211            ArrangementFlavor::Local(
212                arrangement,
213                errs.mz_arrange::<ErrBatcher<_, _>, _>("Arrange bundle err"),
214            ),
215        )
216    }
217
218    fn render_reduce_plan_inner<'s>(
219        &self,
220        plan: ReducePlan,
221        collection: VecCollection<'s, T, (Row, Row), Diff>,
222        errors: &mut Vec<VecCollection<'s, T, DataflowErrorSer, Diff>>,
223        key_arity: usize,
224        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
225    ) -> Arranged<'s, RowRowAgent<T, Diff>> {
226        // TODO(vmarcos): Arrangement specialization here could eventually be extended to keys,
227        // not only values (database-issues#6658).
228        let arrangement = match plan {
229            // If we have no aggregations or just a single type of reduction, we
230            // can go ahead and render them directly.
231            ReducePlan::Distinct => {
232                let (arranged_output, errs) = self.build_distinct(collection, mfp_after);
233                errors.push(errs);
234                arranged_output
235            }
236            ReducePlan::Accumulable(expr) => {
237                let (arranged_output, errs) =
238                    self.build_accumulable(collection, expr, key_arity, mfp_after);
239                errors.push(errs);
240                arranged_output
241            }
242            ReducePlan::Hierarchical(HierarchicalPlan::Monotonic(expr)) => {
243                let (output, errs) = self.build_monotonic(collection, expr, mfp_after);
244                errors.push(errs);
245                output
246            }
247            ReducePlan::Hierarchical(HierarchicalPlan::Bucketed(expr)) => {
248                let (output, errs) = self.build_bucketed(collection, expr, key_arity, mfp_after);
249                errors.push(errs);
250                output
251            }
252            ReducePlan::Basic(BasicPlan::Single(SingleBasicPlan {
253                expr,
254                fused_unnest_list,
255            })) => {
256                // Note that we skip validating for negative diffs when we have a fused unnest list,
257                // because this is already a CPU-intensive situation due to the non-incrementalness
258                // of window functions.
259                let validating = !fused_unnest_list;
260                let (output, errs) = self.build_basic_aggregate(
261                    collection,
262                    0,
263                    &expr,
264                    validating,
265                    key_arity,
266                    mfp_after,
267                    fused_unnest_list,
268                );
269                if validating {
270                    errors.push(errs.expect("validation should have occurred as it was requested"));
271                }
272                output
273            }
274            ReducePlan::Basic(BasicPlan::Multiple(aggrs)) => {
275                let (output, errs) =
276                    self.build_basic_aggregates(collection, aggrs, key_arity, mfp_after);
277                errors.push(errs);
278                output
279            }
280        };
281        arrangement
282    }
283
284    /// Build the dataflow to compute the set of distinct keys.
285    fn build_distinct<'s>(
286        &self,
287        collection: VecCollection<'s, T, (Row, Row), Diff>,
288        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
289    ) -> (
290        Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
291        VecCollection<'s, T, DataflowErrorSer, Diff>,
292    ) {
293        let error_logger = self.error_logger();
294
295        // Allocations for the two closures.
296        let mut datums1 = DatumVec::new();
297        let mut datums2 = DatumVec::new();
298        let mfp_after1 = mfp_after.clone();
299        let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
300
301        let arranged =
302            collection.mz_arrange::<RowRowBatcher<_, _>, RowRowSpine<_, _>>("Arranged DistinctBy");
303        let output = arranged
304            .clone()
305            .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
306                "DistinctBy",
307                move |key, _input, output| {
308                    let temp_storage = RowArena::new();
309                    let mut datums_local = datums1.borrow();
310                    key.extend_datums(&temp_storage, &mut datums_local, None);
311
312                    // Note that the key contains all the columns in a `Distinct` and that `mfp_after` is
313                    // required to preserve the key. Therefore, if `mfp_after` maps, then it must project
314                    // back to the key. As a consequence, we can treat `mfp_after` as a filter here.
315                    if mfp_after1
316                        .as_ref()
317                        .map(|mfp| mfp.evaluate_inner(&mut datums_local, &temp_storage))
318                        .unwrap_or(Ok(true))
319                        == Ok(true)
320                    {
321                        // We're pushing a unit value here because the key is implicitly added by the
322                        // arrangement, and the permutation logic takes care of using the key part of the
323                        // output.
324                        output.push((Row::default(), Diff::ONE));
325                    }
326                },
327            );
328        let errors = arranged.mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
329            "DistinctByErrorCheck",
330            move |key, input: &[(_, Diff)], output: &mut Vec<(DataflowErrorSer, _)>| {
331                for (_, count) in input.iter() {
332                    if count.is_positive() {
333                        continue;
334                    }
335                    let message = "Non-positive multiplicity in DistinctBy";
336                    error_logger.log(message, &format!("row={key:?}, count={count}"));
337                    output.push((EvalError::Internal(message.into()).into(), Diff::ONE));
338                    return;
339                }
340                // If `mfp_after` can error, then evaluate it here.
341                let Some(mfp) = &mfp_after2 else { return };
342                let temp_storage = RowArena::new();
343                let mut datums_local = datums2.borrow();
344                key.extend_datums(&temp_storage, &mut datums_local, None);
345
346                if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
347                    output.push((e.into(), Diff::ONE));
348                }
349            },
350        );
351        (output, errors.as_collection(|_k, v| v.clone()))
352    }
353
354    /// Build the dataflow to compute and arrange multiple non-accumulable,
355    /// non-hierarchical aggregations on `input`.
356    ///
357    /// This function assumes that we are explicitly rendering multiple basic aggregations.
358    /// For each aggregate, we render a different reduce operator, and then fuse
359    /// results together into a final arrangement that presents all the results
360    /// in the order specified by `aggrs`.
361    fn build_basic_aggregates<'s>(
362        &self,
363        input: VecCollection<'s, T, (Row, Row), Diff>,
364        aggrs: Vec<LirAggregateExpr>,
365        key_arity: usize,
366        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
367    ) -> (
368        RowRowArrangement<'s, T>,
369        VecCollection<'s, T, DataflowErrorSer, Diff>,
370    ) {
371        // We are only using this function to render multiple basic aggregates and
372        // stitch them together. If that's not true we should complain.
373        if aggrs.len() <= 1 {
374            self.error_logger().soft_panic_or_log(
375                "Too few aggregations when building basic aggregates",
376                &format!("len={}", aggrs.len()),
377            )
378        }
379        let mut err_output = None;
380        let mut to_collect = Vec::new();
381        for (index, aggr) in aggrs.into_iter().enumerate() {
382            let (result, errs) = self.build_basic_aggregate(
383                input.clone(),
384                index,
385                &aggr,
386                err_output.is_none(),
387                key_arity,
388                None,
389                false,
390            );
391            if errs.is_some() {
392                err_output = errs
393            }
394            to_collect
395                .push(result.as_collection(move |key, val| (key.to_row(), (index, val.to_row()))));
396        }
397
398        // Allocations for the two closures.
399        let mut datums1 = DatumVec::new();
400        let mut datums2 = DatumVec::new();
401        let mfp_after1 = mfp_after.clone();
402        let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
403
404        let arranged = differential_dataflow::collection::concatenate(input.scope(), to_collect)
405            .mz_arrange::<RowValBatcher<_, _, _>, RowValSpine<_, _, _>>(
406            "Arranged ReduceFuseBasic input",
407        );
408
409        let output = arranged
410            .clone()
411            .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
412                "ReduceFuseBasic",
413                {
414                    move |key, input, output| {
415                        let temp_storage = RowArena::new();
416                        let mut datums_local = datums1.borrow();
417                        key.extend_datums(&temp_storage, &mut datums_local, None);
418                        let key_len = datums_local.len();
419
420                        for ((_, row), _) in input.iter() {
421                            datums_local.push(row.unpack_first());
422                        }
423
424                        if let Some(row) = evaluate_mfp_after(
425                            &mfp_after1,
426                            &mut datums_local,
427                            &temp_storage,
428                            key_len,
429                        ) {
430                            output.push((row, Diff::ONE));
431                        }
432                    }
433                },
434            );
435        // If `mfp_after` can error, then we need to render a paired reduction
436        // to scan for these potential errors. Note that we cannot directly use
437        // `mz_timely_util::reduce::ReduceExt::reduce_pair` here because we only
438        // conditionally render the second component of the reduction pair.
439        let validation_errs = err_output.expect("expected to validate in at least one aggregate");
440        if let Some(mfp) = mfp_after2 {
441            let mfp_errs = arranged
442                .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
443                    "ReduceFuseBasic Error Check",
444                    move |key, input, output| {
445                        // Since negative accumulations are checked in at least one component
446                        // aggregate, we only need to look for MFP errors here.
447                        let temp_storage = RowArena::new();
448                        let mut datums_local = datums2.borrow();
449                        key.extend_datums(&temp_storage, &mut datums_local, None);
450
451                        for ((_, row), _) in input.iter() {
452                            datums_local.push(row.unpack_first());
453                        }
454
455                        if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
456                            output.push((e.into(), Diff::ONE));
457                        }
458                    },
459                )
460                .as_collection(|_, v| v.clone());
461            (output, validation_errs.concat(mfp_errs))
462        } else {
463            (output, validation_errs)
464        }
465    }
466
467    /// Build the dataflow to compute a single basic aggregation.
468    ///
469    /// This method also applies distinctness if required.
470    fn build_basic_aggregate<'s>(
471        &self,
472        input: VecCollection<'s, T, (Row, Row), Diff>,
473        index: usize,
474        aggr: &LirAggregateExpr,
475        validating: bool,
476        key_arity: usize,
477        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
478        fused_unnest_list: bool,
479    ) -> (
480        RowRowArrangement<'s, T>,
481        Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
482    ) {
483        let LirAggregateExpr {
484            func,
485            expr: _,
486            distinct,
487        } = aggr.clone();
488
489        // Extract the value we were asked to aggregate over.
490        let mut partial = input.map(move |(key, row)| {
491            let mut row_builder = SharedRow::get();
492            let value = row.iter().nth(index).unwrap();
493            row_builder.packer().push(value);
494            (key, row_builder.clone())
495        });
496
497        let mut err_output = None;
498
499        // If `distinct` is set, we restrict ourselves to the distinct `(key, val)`.
500        if distinct {
501            // We map `(Row, Row)` to `Row` to take advantage of `Row*Spine` types.
502            let pairer = Pairer::new(key_arity);
503            let keyed = partial.map(move |(key, val)| pairer.merge(&key, &val));
504            if validating {
505                let (oks, errs) = self
506                    .build_reduce_inaccumulable_distinct::<
507                        RowValBuilder<Result<(), String>, _, _>,
508                        RowValSpine<Result<(), String>, _, _>,
509                    >(keyed, None)
510                    .as_collection(|k, v| {
511                        (
512                            k.to_row(),
513                            v.as_ref()
514                                .map(|&()| ())
515                                .map_err(|m| m.as_str().into()),
516                        )
517                    })
518                    .map_fallible::<
519                        CapacityContainerBuilder<_>,
520                        CapacityContainerBuilder<_>,
521                        _,
522                        _,
523                        _,
524                    >(
525                        "Demux Errors",
526                        move |(key_val, result)| match result {
527                            Ok(()) => Ok(pairer.split(&key_val)),
528                            Err(m) => {
529                                Err(EvalError::Internal(m).into())
530                            }
531                        },
532                    );
533                err_output = Some(errs);
534                partial = oks;
535            } else {
536                partial = self
537                    .build_reduce_inaccumulable_distinct::<RowBuilder<_, _>, RowSpine<_, _>>(
538                        keyed,
539                        Some(" [val: empty]"),
540                    )
541                    .as_collection(move |key_val_iter, _| pairer.split(key_val_iter));
542            }
543        }
544
545        // Allocations for the two closures.
546        let mut datums1 = DatumVec::new();
547        let mut datums2 = DatumVec::new();
548        let mut datums_key_1 = DatumVec::new();
549        let mut datums_key_2 = DatumVec::new();
550        // Scratch buffers for decoding each input value's (single) datum into the
551        // arena, so the aggregates iterate arena-resident datums rather than the
552        // packed value bytes — a prerequisite for compressed value representations.
553        let mut vals1 = DatumVec::new();
554        let mut vals2 = DatumVec::new();
555        let mut vals_key_1 = DatumVec::new();
556        let mut vals_key_2 = DatumVec::new();
557        let mfp_after1 = mfp_after.clone();
558        let func2 = func.clone();
559
560        let name = if !fused_unnest_list {
561            "ReduceInaccumulable"
562        } else {
563            "FusedReduceUnnestList"
564        };
565        let arranged = partial
566            .mz_arrange::<RowRowBatcher<_, _>, RowRowSpine<_, _>>(&format!("Arranged {name}"));
567        let oks = if !fused_unnest_list {
568            arranged
569                .clone()
570                .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(name, {
571                    move |key, source, target| {
572                        let temp_storage = RowArena::new();
573                        // Decode each input value's single datum into the arena, reusing one
574                        // scratch buffer; the datum is `Copy` and is copied out before the
575                        // buffer is overwritten on the next row. We pass the multiplicity
576                        // through (unlike in hierarchical aggregation) because we don't know
577                        // that the aggregation method is not sensitive to the number of
578                        // records. The aggregate decides how to consume it.
579                        let mut val_scratch = vals1.borrow();
580                        let iter = source.iter().map(|(v, w)| {
581                            val_scratch.clear();
582                            v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
583                            (val_scratch[0], *w)
584                        });
585
586                        let mut datums_local = datums1.borrow();
587                        key.extend_datums(&temp_storage, &mut datums_local, None);
588                        let key_len = datums_local.len();
589                        datums_local.push(
590                        // Note that this is not necessarily a window aggregation, in which case
591                        // `eval_with_fast_window_agg` delegates to the normal `eval`.
592                        func.eval_with_fast_window_agg::<_, window_agg_helpers::OneByOneAggrImpls>(
593                            iter,
594                            &temp_storage,
595                        ),
596                    );
597
598                        if let Some(row) = evaluate_mfp_after(
599                            &mfp_after1,
600                            &mut datums_local,
601                            &temp_storage,
602                            key_len,
603                        ) {
604                            target.push((row, Diff::ONE));
605                        }
606                    }
607                })
608        } else {
609            arranged
610                .clone()
611                .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(name, {
612                    move |key, source, target| {
613                        // This part is the same as in the `!fused_unnest_list` if branch above.
614                        let temp_storage = RowArena::new();
615                        let mut val_scratch = vals_key_1.borrow();
616                        let iter = source.iter().map(|(v, w)| {
617                            val_scratch.clear();
618                            v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
619                            (val_scratch[0], *w)
620                        });
621
622                        // This is the part that is specific to the `fused_unnest_list` branch.
623                        let mut datums_local = datums_key_1.borrow();
624                        key.extend_datums(&temp_storage, &mut datums_local, None);
625                        let key_len = datums_local.len();
626                        for datum in func
627                            .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
628                                iter,
629                                &temp_storage,
630                            )
631                        {
632                            datums_local.truncate(key_len);
633                            datums_local.push(datum);
634                            if let Some(row) = evaluate_mfp_after(
635                                &mfp_after1,
636                                &mut datums_local,
637                                &temp_storage,
638                                key_len,
639                            ) {
640                                target.push((row, Diff::ONE));
641                            }
642                        }
643                    }
644                })
645        };
646
647        // Note that we would prefer to use `mz_timely_util::reduce::ReduceExt::reduce_pair` here, but
648        // we then wouldn't be able to do this error check conditionally.  See its documentation for the
649        // rationale around using a second reduction here.
650        let must_validate = validating && err_output.is_none();
651        let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
652        if must_validate || mfp_after2.is_some() {
653            let error_logger = self.error_logger();
654
655            let errs = if !fused_unnest_list {
656                arranged
657                    .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
658                        &format!("{name} Error Check"),
659                        move |key, source, target| {
660                            // Negative counts would be surprising, but until we are 100% certain we won't
661                            // see them, we should report when we do. We may want to bake even more info
662                            // in here in the future.
663                            if must_validate {
664                                for (value, count) in source.iter() {
665                                    if count.is_positive() {
666                                        continue;
667                                    }
668                                    let value = value.to_row();
669                                    let message =
670                                        "Non-positive accumulation in ReduceInaccumulable";
671                                    error_logger
672                                        .log(message, &format!("value={value:?}, count={count}"));
673                                    let err = EvalError::Internal(message.into());
674                                    target.push((err.into(), Diff::ONE));
675                                    return;
676                                }
677                            }
678
679                            // We know that `mfp_after` can error if it exists, so try to evaluate it here.
680                            let Some(mfp) = &mfp_after2 else { return };
681                            let temp_storage = RowArena::new();
682                            let mut val_scratch = vals2.borrow();
683                            let iter = source.iter().map(|(v, w)| {
684                                val_scratch.clear();
685                                v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
686                                (val_scratch[0], *w)
687                            });
688
689                            let mut datums_local = datums2.borrow();
690                            key.extend_datums(&temp_storage, &mut datums_local, None);
691                            datums_local.push(
692                                func2.eval_with_fast_window_agg::<
693                                    _,
694                                    window_agg_helpers::OneByOneAggrImpls,
695                                >(
696                                    iter, &temp_storage
697                                ),
698                            );
699                            if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
700                                target.push((e.into(), Diff::ONE));
701                            }
702                        },
703                    )
704                    .as_collection(|_, v| v.clone())
705            } else {
706                // `render_reduce_plan_inner` doesn't request validation when `fused_unnest_list`.
707                assert!(!must_validate);
708                // We couldn't have got into this if branch due to `must_validate`, so it must be
709                // because of the `mfp_after2.is_some()`.
710                let Some(mfp) = mfp_after2 else {
711                    unreachable!()
712                };
713                arranged
714                    .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
715                        &format!("{name} Error Check"),
716                        move |key, source, target| {
717                            let temp_storage = RowArena::new();
718                            let mut val_scratch = vals_key_2.borrow();
719                            let iter = source.iter().map(|(v, w)| {
720                                val_scratch.clear();
721                                v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
722                                (val_scratch[0], *w)
723                            });
724
725                            let mut datums_local = datums_key_2.borrow();
726                            key.extend_datums(&temp_storage, &mut datums_local, None);
727                            let key_len = datums_local.len();
728                            for datum in func2
729                                .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
730                                    iter,
731                                    &temp_storage,
732                                )
733                            {
734                                datums_local.truncate(key_len);
735                                datums_local.push(datum);
736                                // We know that `mfp` can error (because of the `could_error` call
737                                // above), so try to evaluate it here.
738                                if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
739                                {
740                                    target.push((e.into(), Diff::ONE));
741                                }
742                            }
743                        },
744                    )
745                    .as_collection(|_, v| v.clone())
746            };
747
748            if let Some(e) = err_output {
749                err_output = Some(e.concat(errs));
750            } else {
751                err_output = Some(errs);
752            }
753        }
754        (oks, err_output)
755    }
756
757    fn build_reduce_inaccumulable_distinct<'s, Bu, Tr>(
758        &self,
759        input: VecCollection<'s, T, Row, Diff>,
760        name_tag: Option<&str>,
761    ) -> Arranged<'s, TraceAgent<Tr>>
762    where
763        Tr: Trace<Batch: Navigable, Time = T> + 'static,
764        for<'a> BatchCursor<Tr>: Cursor<
765                Key<'a> = DatumSeq<'a>,
766                KeyContainer = DatumContainer,
767                Time = T,
768                Diff = Diff,
769                ValOwn: Data + MaybeValidatingRow<(), String>,
770            >,
771        Bu: Builder<
772                Time = T,
773                Input: Container
774                           + ClearContainer
775                           + PushInto<((Row, BatchValOwn<Tr>), Tr::Time, BatchDiff<Tr>)>,
776                Output = Tr::Batch,
777            > + 'static,
778        Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
779    {
780        let error_logger = self.error_logger();
781
782        let output_name = format!(
783            "ReduceInaccumulable Distinct{}",
784            name_tag.unwrap_or_default()
785        );
786
787        let input: KeyCollection<_, _, _> = input.into();
788        let arranged = input.mz_arrange::<RowBatcher<_, _>, RowSpine<_, _>>(
789            "Arranged ReduceInaccumulable Distinct [val: empty]",
790        );
791        arranged.mz_reduce_abelian::<_, Bu, Tr, _>(&output_name, move |_, source, t| {
792            if let Some(err) = BatchValOwn::<Tr>::into_error() {
793                for (value, count) in source.iter() {
794                    if count.is_positive() {
795                        continue;
796                    }
797
798                    let message = "Non-positive accumulation in ReduceInaccumulable DISTINCT";
799                    error_logger.log(message, &format!("value={value:?}, count={count}"));
800                    t.push((err(message.to_string()), Diff::ONE));
801                    return;
802                }
803            }
804            t.push((BatchValOwn::<Tr>::ok(()), Diff::ONE))
805        })
806    }
807
808    /// Build the dataflow to compute and arrange multiple hierarchical aggregations
809    /// on non-monotonic inputs.
810    ///
811    /// This function renders a single reduction tree that computes aggregations with
812    /// a priority queue implemented with a series of reduce operators that partition
813    /// the input into buckets, and compute the aggregation over very small buckets
814    /// and feed the results up to larger buckets.
815    ///
816    /// Note that this implementation currently ignores the distinct bit because we
817    /// currently only perform min / max hierarchically and the reduction tree
818    /// efficiently suppresses non-distinct updates.
819    ///
820    /// `buckets` indicates the number of buckets in this stage. We do some non-obvious
821    /// trickery here to limit the memory usage per layer by internally
822    /// holding only the elements that were rejected by this stage. However, the
823    /// output collection maintains the `((key, bucket), (passing value)` for this
824    /// stage.
825    fn build_bucketed<'s>(
826        &self,
827        input: VecCollection<'s, T, (Row, Row), Diff>,
828        BucketedPlan {
829            aggr_funcs,
830            buckets,
831        }: BucketedPlan,
832        key_arity: usize,
833        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
834    ) -> (
835        RowRowArrangement<'s, T>,
836        VecCollection<'s, T, DataflowErrorSer, Diff>,
837    ) {
838        let mut err_output: Option<VecCollection<'s, T, _, _>> = None;
839        let outer_scope = input.scope();
840        let arranged_output = outer_scope
841            .clone()
842            .region_named("ReduceHierarchical", |inner| {
843                let input = input.enter(inner);
844
845                // The first mod to apply to the hash.
846                let first_mod = buckets.get(0).copied().unwrap_or(1);
847                let aggregations = aggr_funcs.len();
848
849                // Gather the relevant keys with their hashes along with values ordered by aggregation_index.
850                let mut stage = input.map(move |(key, row)| {
851                    let mut row_builder = SharedRow::get();
852                    let mut row_packer = row_builder.packer();
853                    row_packer.extend(row.iter().take(aggregations));
854                    let values = row_builder.clone();
855
856                    // Apply the initial mod here.
857                    let hash = values.hashed() % first_mod;
858                    let hash_key =
859                        row_builder.pack_using(std::iter::once(Datum::from(hash)).chain(&key));
860                    (hash_key, values)
861                });
862
863                // Repeatedly apply hierarchical reduction with a progressively coarser key.
864                for (index, b) in buckets.into_iter().enumerate() {
865                    // Apply subsequent bucket mods for all but the first round.
866                    let input = if index == 0 {
867                        stage
868                    } else {
869                        stage.map(move |(hash_key, values)| {
870                            let mut hash_key_iter = hash_key.iter();
871                            let hash = hash_key_iter.next().unwrap().unwrap_uint64() % b;
872                            // TODO: Convert the `chain(hash_key_iter...)` into a memcpy.
873                            let hash_key = SharedRow::pack(
874                                std::iter::once(Datum::from(hash))
875                                    .chain(hash_key_iter.take(key_arity)),
876                            );
877                            (hash_key, values)
878                        })
879                    };
880
881                    // We only want the first stage to perform validation of whether invalid accumulations
882                    // were observed in the input. Subsequently, we will either produce an error in the error
883                    // stream or produce correct data in the output stream.
884                    let validating = err_output.is_none();
885
886                    let (oks, errs) = self.build_bucketed_stage(&aggr_funcs, input, validating);
887                    if let Some(errs) = errs {
888                        err_output = Some(errs.leave_region(outer_scope));
889                    }
890                    stage = oks
891                }
892
893                // Discard the hash from the key and return to the format of the input data.
894                let partial = stage.map(move |(hash_key, values)| {
895                    let mut hash_key_iter = hash_key.iter();
896                    let _hash = hash_key_iter.next();
897                    (SharedRow::pack(hash_key_iter.take(key_arity)), values)
898                });
899
900                // Allocations for the two closures.
901                let mut datums1 = DatumVec::new();
902                let mut datums2 = DatumVec::new();
903                // Scratch buffers for decoding the input values (one column per aggregate)
904                // into the arena, so the aggregates iterate arena-resident datums rather
905                // than the packed value bytes.
906                let mut vals1 = DatumVec::new();
907                let mut vals2 = DatumVec::new();
908                let mfp_after1 = mfp_after.clone();
909                let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
910                let aggr_funcs2 = aggr_funcs.clone();
911
912                // Build a series of stages for the reduction
913                // Arrange the final result into (key, Row)
914                let error_logger = self.error_logger();
915                // NOTE(vmarcos): The input operator name below is used in the tuning advice built-in
916                // view mz_introspection.mz_expected_group_size_advice.
917                let arranged = partial.mz_arrange::<RowRowBatcher<_, _>, RowRowSpine<_, _>>(
918                    "Arrange ReduceMinsMaxes",
919                );
920                // Note that we would prefer to use `mz_timely_util::reduce::ReduceExt::reduce_pair` here,
921                // but we then wouldn't be able to do this error check conditionally.  See its documentation
922                // for the rationale around using a second reduction here.
923                let must_validate = err_output.is_none();
924                if must_validate || mfp_after2.is_some() {
925                    let errs = arranged
926                        .clone()
927                        .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
928                            "ReduceMinsMaxes Error Check",
929                            move |key, source, target| {
930                                // Negative counts would be surprising, but until we are 100% certain we wont
931                                // see them, we should report when we do. We may want to bake even more info
932                                // in here in the future.
933                                if must_validate {
934                                    for (val, count) in source.iter() {
935                                        if count.is_positive() {
936                                            continue;
937                                        }
938                                        let val = val.to_row();
939                                        let message =
940                                            "Non-positive accumulation in ReduceMinsMaxes";
941                                        error_logger
942                                            .log(message, &format!("val={val:?}, count={count}"));
943                                        target.push((
944                                            EvalError::Internal(message.into()).into(),
945                                            Diff::ONE,
946                                        ));
947                                        return;
948                                    }
949                                }
950
951                                // We know that `mfp_after` can error if it exists, so try to evaluate it here.
952                                let Some(mfp) = &mfp_after2 else { return };
953                                let temp_storage = RowArena::new();
954                                let mut datums_local = datums2.borrow();
955                                key.extend_datums(&temp_storage, &mut datums_local, None);
956
957                                // Decode every value row's datums into the arena, one column
958                                // per aggregate, then iterate them column-major below. Min/max
959                                // hierarchical aggregates are multiplicity-insensitive, so each
960                                // row contributes once (`Diff::ONE`) regardless of `_cnt`.
961                                let arity = aggr_funcs2.len();
962                                let mut decoded = vals2.borrow();
963                                for (values, _cnt) in source.iter() {
964                                    values.extend_datums(&temp_storage, &mut decoded, None);
965                                }
966                                assert_eq!(decoded.len(), source.len() * arity);
967                                for (col, func) in aggr_funcs2.iter().enumerate() {
968                                    let column_iter = (0..source.len())
969                                        .map(|r| (decoded[r * arity + col], Diff::ONE));
970                                    datums_local.push(func.eval(column_iter, &temp_storage));
971                                }
972                                if let Result::Err(e) =
973                                    mfp.evaluate_inner(&mut datums_local, &temp_storage)
974                                {
975                                    target.push((e.into(), Diff::ONE));
976                                }
977                            },
978                        )
979                        .as_collection(|_, v| v.clone())
980                        .leave_region(outer_scope);
981                    if let Some(e) = err_output.take() {
982                        err_output = Some(e.concat(errs));
983                    } else {
984                        err_output = Some(errs);
985                    }
986                }
987                arranged
988                    .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
989                        "ReduceMinsMaxes",
990                        move |key, source, target| {
991                            let temp_storage = RowArena::new();
992                            let mut datums_local = datums1.borrow();
993                            key.extend_datums(&temp_storage, &mut datums_local, None);
994                            let key_len = datums_local.len();
995
996                            // Decode every value row's datums into the arena, one column
997                            // per aggregate, then iterate them column-major below. Min/max
998                            // hierarchical aggregates are multiplicity-insensitive, so each
999                            // row contributes once (`Diff::ONE`) regardless of `_cnt`.
1000                            let arity = aggr_funcs.len();
1001                            let mut decoded = vals1.borrow();
1002                            for (values, _cnt) in source.iter() {
1003                                values.extend_datums(&temp_storage, &mut decoded, None);
1004                            }
1005                            assert_eq!(decoded.len(), source.len() * arity);
1006                            for (col, func) in aggr_funcs.iter().enumerate() {
1007                                let column_iter = (0..source.len())
1008                                    .map(|r| (decoded[r * arity + col], Diff::ONE));
1009                                datums_local.push(func.eval(column_iter, &temp_storage));
1010                            }
1011
1012                            if let Some(row) = evaluate_mfp_after(
1013                                &mfp_after1,
1014                                &mut datums_local,
1015                                &temp_storage,
1016                                key_len,
1017                            ) {
1018                                target.push((row, Diff::ONE));
1019                            }
1020                        },
1021                    )
1022                    .leave_region(outer_scope)
1023            });
1024        (
1025            arranged_output,
1026            err_output.expect("expected to validate in one level of the hierarchy"),
1027        )
1028    }
1029
1030    /// Build a bucketed stage fragment that wraps [`Self::build_bucketed_negated_output`], and
1031    /// adds validation if `validating` is true. It returns the consolidated inputs concatenated
1032    /// with the reduction's output, which is the winner of each key.
1033    /// `validating` indicates whether we want this stage to perform error detection
1034    /// for invalid accumulations. Once a stage is clean of such errors, subsequent
1035    /// stages can skip validation.
1036    fn build_bucketed_stage<'s>(
1037        &self,
1038        aggr_funcs: &Vec<AggregateFunc>,
1039        input: VecCollection<'s, T, (Row, Row), Diff>,
1040        validating: bool,
1041    ) -> (
1042        VecCollection<'s, T, (Row, Row), Diff>,
1043        Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
1044    ) {
1045        let (input, negated_output, errs) = if validating {
1046            let (input, reduced) = self
1047                .build_bucketed_negated_output::<
1048                    RowValBuilder<_, _, _>,
1049                    RowValSpine<Result<Row, Row>, _, _>,
1050                >(
1051                    input.clone(),
1052                    aggr_funcs.clone(),
1053                );
1054            let (oks, errs) = reduced
1055                .as_collection(|k, v| (k.to_row(), v.clone()))
1056                .map_fallible::<CapacityContainerBuilder<_>, CapacityContainerBuilder<_>, _, _, _>(
1057                "Checked Invalid Accumulations",
1058                |(hash_key, result)| match result {
1059                    Err(hash_key) => {
1060                        let mut hash_key_iter = hash_key.iter();
1061                        let _hash = hash_key_iter.next();
1062                        let key = SharedRow::pack(hash_key_iter);
1063                        let message = format!(
1064                            "Invalid data in source, saw non-positive accumulation \
1065                                         for key {key:?} in hierarchical mins-maxes aggregate"
1066                        );
1067                        Err(EvalError::Internal(message.into()).into())
1068                    }
1069                    Ok(values) => Ok((hash_key, values)),
1070                },
1071            );
1072            (input, oks, Some(errs))
1073        } else {
1074            let (input, reduced) = self
1075                .build_bucketed_negated_output::<RowRowBuilder<_, _>, RowRowSpine<_, _>>(
1076                    input,
1077                    aggr_funcs.clone(),
1078                );
1079            // TODO: Here is a good moment where we could apply the next `mod` calculation. Note
1080            // that we need to apply the mod on both input and oks.
1081            let oks = reduced.as_collection(|k, v| (k.to_row(), v.to_row()));
1082            (input, oks, None)
1083        };
1084
1085        let input = input.as_collection(|k, v| (k.to_row(), v.to_row()));
1086        let oks = negated_output.concat(input);
1087        (oks, errs)
1088    }
1089
1090    /// Build a dataflow fragment for one stage of a reduction tree for multiple hierarchical
1091    /// aggregates to arrange and reduce the inputs. Returns the arranged input and the reduction,
1092    /// whose output for each key is the key's winner minus its input.
1093    fn build_bucketed_negated_output<'s, Bu, Tr>(
1094        &self,
1095        input: VecCollection<'s, T, (Row, Row), Diff>,
1096        aggrs: Vec<AggregateFunc>,
1097    ) -> (
1098        Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
1099        Arranged<'s, TraceAgent<Tr>>,
1100    )
1101    where
1102        Tr: Trace<Batch: Navigable, Time = T> + 'static,
1103        for<'a> BatchCursor<Tr>: Cursor<
1104                Key<'a> = DatumSeq<'a>,
1105                KeyContainer = DatumContainer,
1106                ValOwn: Data + MaybeValidatingRow<Row, Row>,
1107                Time = T,
1108                Diff = Diff,
1109            >,
1110        Bu: Builder<
1111                Time = T,
1112                Input: Container
1113                           + ClearContainer
1114                           + PushInto<((Row, BatchValOwn<Tr>), Tr::Time, BatchDiff<Tr>)>,
1115                Output = Tr::Batch,
1116            > + 'static,
1117        Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
1118    {
1119        let error_logger = self.error_logger();
1120        // NOTE(vmarcos): The input operator name below is used in the tuning advice built-in
1121        // view mz_introspection.mz_expected_group_size_advice.
1122        let arranged_input = input.mz_arrange::<RowRowBatcher<_, _>, RowRowSpine<_, _>>(
1123            "Arranged MinsMaxesHierarchical input",
1124        );
1125
1126        // Scratch buffer for decoding the input values (one column per aggregate) into the
1127        // arena, so the aggregates iterate arena-resident datums rather than the packed bytes.
1128        let mut value_datums = DatumVec::new();
1129        let reduced = arranged_input.clone().mz_reduce_abelian::<_, Bu, Tr, _>(
1130            "Reduced Fallibly MinsMaxesHierarchical",
1131            move |key, source, target| {
1132                if let Some(err) = BatchValOwn::<Tr>::into_error() {
1133                    // Should negative accumulations reach us, we should loudly complain.
1134                    for (value, count) in source.iter() {
1135                        if count.is_positive() {
1136                            continue;
1137                        }
1138                        error_logger.log(
1139                            "Non-positive accumulation in MinsMaxesHierarchical",
1140                            &format!("key={key:?}, value={value:?}, count={count}"),
1141                        );
1142                        // After complaining, output an error here so that we can eventually
1143                        // report it in an error stream.
1144                        let key = <BatchKeyContainer<Tr> as BatchContainer>::into_owned(key);
1145                        target.push((err(key), Diff::ONE));
1146                        return;
1147                    }
1148                }
1149
1150                // Decode every value row's datums into the arena, one column per aggregate,
1151                // then iterate them column-major below.
1152                let temp_storage = RowArena::new();
1153                let arity = aggrs.len();
1154                let mut decoded = value_datums.borrow();
1155                for (values, _cnt) in source.iter() {
1156                    values.extend_datums(&temp_storage, &mut decoded, None);
1157                }
1158                assert_eq!(decoded.len(), source.len() * arity);
1159
1160                let mut row_builder = SharedRow::get();
1161                let mut row_packer = row_builder.packer();
1162                for (col, func) in aggrs.iter().enumerate() {
1163                    // Min/max hierarchical aggregates are multiplicity-insensitive, so each
1164                    // row contributes once (`Diff::ONE`) regardless of `_cnt`.
1165                    let column_iter =
1166                        (0..source.len()).map(|r| (decoded[r * arity + col], Diff::ONE));
1167                    row_packer.push(func.eval(column_iter, &temp_storage));
1168                }
1169                // We only want to arrange the parts of the input that are not part of the output.
1170                // The reduction emits the winner with a positive diff and every input with its diff
1171                // negated, so `negated_output.concat(input)` in the caller holds one copy of the
1172                // winner, regardless of its multiplicity in the input. The winner cancels against an
1173                // input row only if it equals one, which holds for a single aggregate but not in
1174                // general for several aggregates whose results come from different rows.
1175                target.reserve(source.len().saturating_add(1));
1176                target.push((BatchValOwn::<Tr>::ok(row_builder.clone()), Diff::ONE));
1177                target.extend(source.iter().map(|(values, cnt)| {
1178                    let mut cnt = *cnt;
1179                    cnt.negate();
1180                    (BatchValOwn::<Tr>::ok(values.to_row()), cnt)
1181                }));
1182            },
1183        );
1184        (arranged_input, reduced)
1185    }
1186
1187    /// Build the dataflow to compute and arrange multiple hierarchical aggregations
1188    /// on monotonic inputs.
1189    fn build_monotonic<'s>(
1190        &self,
1191        collection: VecCollection<'s, T, (Row, Row), Diff>,
1192        MonotonicPlan {
1193            aggr_funcs,
1194            must_consolidate,
1195        }: MonotonicPlan,
1196        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1197    ) -> (
1198        RowRowArrangement<'s, T>,
1199        VecCollection<'s, T, DataflowErrorSer, Diff>,
1200    ) {
1201        let aggregations = aggr_funcs.len();
1202        // Gather the relevant values into a vec of rows ordered by aggregation_index
1203        let collection = collection
1204            .map(move |(key, row)| {
1205                let mut row_builder = SharedRow::get();
1206                let mut values = Vec::with_capacity(aggregations);
1207                values.extend(
1208                    row.iter()
1209                        .take(aggregations)
1210                        .map(|v| row_builder.pack_using(std::iter::once(v))),
1211                );
1212
1213                (key, values)
1214            })
1215            .consolidate_named_if::<ConsolidateBatcher<_, _, _>>(
1216                must_consolidate,
1217                "Consolidated ReduceMonotonic input",
1218            );
1219
1220        // It should be now possible to ensure that we have a monotonic collection.
1221        let error_logger = self.error_logger();
1222        let (partial, validation_errs) = collection.ensure_monotonic(move |data, diff| {
1223            error_logger.log(
1224                "Non-monotonic input to ReduceMonotonic",
1225                &format!("data={data:?}, diff={diff}"),
1226            );
1227            let m = "tried to build a monotonic reduction on non-monotonic input".into();
1228            (EvalError::Internal(m).into(), Diff::ONE)
1229        });
1230        // We can place our rows directly into the diff field, and
1231        // only keep the relevant one corresponding to evaluating our
1232        // aggregate, instead of having to do a hierarchical reduction.
1233        let partial = partial.explode_one(move |(key, values)| {
1234            let mut output = Vec::new();
1235            for (row, func) in values.into_iter().zip_eq(aggr_funcs.iter()) {
1236                output.push(monoids::get_monoid(row, func).expect(
1237                    "hierarchical aggregations are expected to have monoid implementations",
1238                ));
1239            }
1240            (key, output)
1241        });
1242
1243        // Allocations for the two closures.
1244        let mut datums1 = DatumVec::new();
1245        let mut datums2 = DatumVec::new();
1246        let mfp_after1 = mfp_after.clone();
1247        let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1248
1249        let partial: KeyCollection<_, _, _> = partial.into();
1250        type MonoidSpine<T> = RowSpine<T, Vec<ReductionMonoid>>;
1251        let arranged =
1252            partial.mz_arrange::<RowBatcher<_, _>, MonoidSpine<_>>("ArrangeMonotonic [val: empty]");
1253        let output = arranged
1254            .clone()
1255            .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1256                "ReduceMonotonic",
1257                {
1258                    move |key, input, output| {
1259                        let temp_storage = RowArena::new();
1260                        let mut datums_local = datums1.borrow();
1261                        key.extend_datums(&temp_storage, &mut datums_local, None);
1262                        let key_len = datums_local.len();
1263                        let accum = &input[0].1;
1264                        for monoid in accum.iter() {
1265                            datums_local.extend(monoid.finalize().iter());
1266                        }
1267
1268                        if let Some(row) = evaluate_mfp_after(
1269                            &mfp_after1,
1270                            &mut datums_local,
1271                            &temp_storage,
1272                            key_len,
1273                        ) {
1274                            output.push((row, Diff::ONE));
1275                        }
1276                    }
1277                },
1278            );
1279
1280        // If `mfp_after` can error, then we need to render a paired reduction
1281        // to scan for these potential errors. Note that we cannot directly use
1282        // `mz_timely_util::reduce::ReduceExt::reduce_pair` here because we only
1283        // conditionally render the second component of the reduction pair.
1284        if let Some(mfp) = mfp_after2 {
1285            let mfp_errs = arranged
1286                .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
1287                    "ReduceMonotonic Error Check",
1288                    move |key, input, output| {
1289                        let temp_storage = RowArena::new();
1290                        let mut datums_local = datums2.borrow();
1291                        key.extend_datums(&temp_storage, &mut datums_local, None);
1292                        let accum = &input[0].1;
1293                        for monoid in accum.iter() {
1294                            datums_local.extend(monoid.finalize().iter());
1295                        }
1296                        if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
1297                        {
1298                            output.push((e.into(), Diff::ONE));
1299                        }
1300                    },
1301                )
1302                .as_collection(|_k, v| v.clone());
1303            (output, validation_errs.concat(mfp_errs))
1304        } else {
1305            (output, validation_errs)
1306        }
1307    }
1308
1309    /// Build the dataflow to compute and arrange multiple accumulable aggregations.
1310    ///
1311    /// The incoming values are moved to the update's "difference" field, at which point
1312    /// they can be accumulated in place. The `count` operator promotes the accumulated
1313    /// values to data, at which point a final map applies operator-specific logic to
1314    /// yield the final aggregate.
1315    fn build_accumulable<'s>(
1316        &self,
1317        collection: VecCollection<'s, T, (Row, Row), Diff>,
1318        AccumulablePlan {
1319            full_aggrs,
1320            simple_aggrs,
1321            distinct_aggrs,
1322        }: AccumulablePlan,
1323        key_arity: usize,
1324        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1325    ) -> (
1326        RowRowArrangement<'s, T>,
1327        VecCollection<'s, T, DataflowErrorSer, Diff>,
1328    ) {
1329        let collection_scope = collection.scope();
1330
1331        // we must have called this function with something to reduce
1332        if full_aggrs.len() == 0 || simple_aggrs.len() + distinct_aggrs.len() != full_aggrs.len() {
1333            self.error_logger().soft_panic_or_log(
1334                "Incorrect numbers of aggregates in accummulable reduction rendering",
1335                &format!(
1336                    "full_aggrs={}, simple_aggrs={}, distinct_aggrs={}",
1337                    full_aggrs.len(),
1338                    simple_aggrs.len(),
1339                    distinct_aggrs.len(),
1340                ),
1341            );
1342        }
1343
1344        // Some of the aggregations may have the `distinct` bit set, which means that they'll
1345        // need to be extracted from `collection` and be subjected to `distinct` with `key`.
1346        // Other aggregations can be directly moved in to the `diff` field.
1347        //
1348        // In each case, the resulting collection should have `data` shaped as `(key, ())`
1349        // and a `diff` that is a vector with length `3 * aggrs.len()`. The three values are
1350        // generally the count, and then two aggregation-specific values. The size could be
1351        // reduced if we want to specialize for the aggregations.
1352
1353        // Instantiate a default vector for diffs with the correct types at each
1354        // position.
1355        let zero_diffs: (Vec<_>, Diff) = (
1356            full_aggrs
1357                .iter()
1358                .map(|f| accumulable_zero(&f.func))
1359                .collect(),
1360            Diff::ZERO,
1361        );
1362
1363        let mut to_aggregate = Vec::new();
1364        if simple_aggrs.len() > 0 {
1365            // First, collect all non-distinct aggregations in one pass.
1366            let collection = collection.clone();
1367            let easy_cases = collection.explode_one({
1368                let zero_diffs = zero_diffs.clone();
1369                move |(key, row)| {
1370                    let mut diffs = zero_diffs.clone();
1371                    // Try to unpack only the datums we need. Unfortunately, since we
1372                    // can't random access into a Row, we have to iterate through one by one.
1373                    // TODO: Even though we don't have random access, we could still avoid unpacking
1374                    // everything that we don't care about, and it might be worth it to extend the
1375                    // Row API to do that.
1376                    let mut row_iter = row.iter().enumerate();
1377                    for (datum_index, aggr) in simple_aggrs.iter() {
1378                        let mut datum = row_iter.next().unwrap();
1379                        while datum_index != &datum.0 {
1380                            datum = row_iter.next().unwrap();
1381                        }
1382                        let datum = datum.1;
1383                        diffs.0[*datum_index] = datum_to_accumulator(&aggr.func, datum);
1384                        diffs.1 = Diff::ONE;
1385                    }
1386                    ((key, ()), diffs)
1387                }
1388            });
1389            to_aggregate.push(easy_cases);
1390        }
1391
1392        // Next, collect all aggregations that require distinctness.
1393        for (datum_index, aggr) in distinct_aggrs.into_iter() {
1394            let pairer = Pairer::new(key_arity);
1395            let collection = collection
1396                .clone()
1397                .map(move |(key, row)| {
1398                    let value = row.iter().nth(datum_index).unwrap();
1399                    (pairer.merge(&key, std::iter::once(value)), ())
1400                })
1401                .mz_arrange::<RowBatcher<_, _>, RowSpine<_, _>>(
1402                    "Arranged Accumulable Distinct [val: empty]",
1403                )
1404                .mz_reduce_abelian::<_, RowBuilder<_, _>, RowSpine<_, _>, _>(
1405                    "Reduced Accumulable Distinct [val: empty]",
1406                    move |_k, _s, t| t.push(((), Diff::ONE)),
1407                )
1408                .as_collection(move |key_val_iter, _| pairer.split(key_val_iter))
1409                .explode_one({
1410                    let zero_diffs = zero_diffs.clone();
1411                    move |(key, row)| {
1412                        let datum = row.iter().next().unwrap();
1413                        let mut diffs = zero_diffs.clone();
1414                        diffs.0[datum_index] = datum_to_accumulator(&aggr.func, datum);
1415                        diffs.1 = Diff::ONE;
1416                        ((key, ()), diffs)
1417                    }
1418                });
1419            to_aggregate.push(collection);
1420        }
1421
1422        // now concatenate, if necessary, multiple aggregations
1423        let collection = if to_aggregate.len() == 1 {
1424            to_aggregate.remove(0)
1425        } else {
1426            differential_dataflow::collection::concatenate(collection_scope, to_aggregate)
1427        };
1428
1429        // The accumulators travel in the arrangement's diffs. A columnar diff container
1430        // lays each `Accum` out by variant, so it occupies only its own variant's
1431        // columns rather than the footprint of the largest variant. Both layouts feed
1432        // the same reduce operators.
1433        if ENABLE_COLUMNAR_ACCUMULABLE_DIFF.get(&self.config_set) {
1434            let arranged = collection.mz_arrange::<
1435                RowBatcher<_, _, Coltainer<_>>,
1436                RowSpine<_, (Vec<Accum>, Diff), Coltainer<_>>,
1437            >("ArrangeAccumulable [val: empty]");
1438            self.reduce_accumulable(arranged, full_aggrs, mfp_after)
1439        } else {
1440            let arranged = collection
1441                .mz_arrange::<RowBatcher<_, _>, RowSpine<_, (Vec<Accum>, Diff)>>(
1442                    "ArrangeAccumulable [val: empty]",
1443                );
1444            self.reduce_accumulable(arranged, full_aggrs, mfp_after)
1445        }
1446    }
1447
1448    /// Reduces arranged accumulators to output rows, and to the errors the accumulated
1449    /// values can reveal. Generic over the container holding the diffs, so both diff
1450    /// layouts share one rendering of the reduce operators.
1451    fn reduce_accumulable<'s, DC>(
1452        &self,
1453        arranged: Arranged<'s, RowAgent<T, (Vec<Accum>, Diff), DC>>,
1454        full_aggrs: Vec<LirAggregateExpr>,
1455        mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1456    ) -> (
1457        RowRowArrangement<'s, T>,
1458        VecCollection<'s, T, DataflowErrorSer, Diff>,
1459    )
1460    where
1461        DC: BatchContainer<Owned = (Vec<Accum>, Diff)>,
1462    {
1463        // Allocations for the two closures.
1464        let mut datums1 = DatumVec::new();
1465        let mut datums2 = DatumVec::new();
1466        let mfp_after1 = mfp_after.clone();
1467        let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1468        let full_aggrs2 = full_aggrs.clone();
1469
1470        let error_logger = self.error_logger();
1471        let err_full_aggrs = full_aggrs.clone();
1472        let arranged_output = arranged
1473            .clone()
1474            .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1475                "ReduceAccumulable",
1476                {
1477                    move |key, input, output| {
1478                        let (ref accums, total) = input[0].1;
1479
1480                        let temp_storage = RowArena::new();
1481                        let mut datums_local = datums1.borrow();
1482                        key.extend_datums(&temp_storage, &mut datums_local, None);
1483                        let key_len = datums_local.len();
1484                        for (aggr, accum) in full_aggrs.iter().zip_eq(accums) {
1485                            datums_local.push(finalize_accum(&aggr.func, accum, total));
1486                        }
1487
1488                        if let Some(row) = evaluate_mfp_after(
1489                            &mfp_after1,
1490                            &mut datums_local,
1491                            &temp_storage,
1492                            key_len,
1493                        ) {
1494                            output.push((row, Diff::ONE));
1495                        }
1496                    }
1497                },
1498            );
1499        let arranged_errs = arranged
1500            .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
1501                "AccumulableErrorCheck",
1502                move |key, input, output| {
1503                    let (ref accums, total) = input[0].1;
1504                    for (aggr, accum) in err_full_aggrs.iter().zip_eq(accums) {
1505                        // We first test here if inputs without net-positive records are present,
1506                        // producing an error to the logs and to the query output if that is the case.
1507                        if total == Diff::ZERO && !accum.is_zero() {
1508                            error_logger.log(
1509                                "Net-zero records with non-zero accumulation in ReduceAccumulable",
1510                                &format!("aggr={aggr:?}, accum={accum:?}"),
1511                            );
1512                            let key = key.to_row();
1513                            let message = format!(
1514                                "Invalid data in source, saw net-zero records for key {key} \
1515                                 with non-zero accumulation in accumulable aggregate"
1516                            );
1517                            output.push((EvalError::Internal(message.into()).into(), Diff::ONE));
1518                        }
1519                        match (&aggr.func, &accum) {
1520                            (AggregateFunc::SumUInt16, Accum::SimpleNumber { accum, .. })
1521                            | (AggregateFunc::SumUInt32, Accum::SimpleNumber { accum, .. })
1522                            | (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1523                                if accum.is_negative() {
1524                                    error_logger.log(
1525                                    "Invalid negative unsigned aggregation in ReduceAccumulable",
1526                                    &format!("aggr={aggr:?}, accum={accum:?}"),
1527                                );
1528                                    let key = key.to_row();
1529                                    let message = format!(
1530                                        "Invalid data in source, saw negative accumulation with \
1531                                         unsigned type for key {key}"
1532                                    );
1533                                    let err = EvalError::Internal(message.into());
1534                                    output.push((err.into(), Diff::ONE));
1535                                }
1536                            }
1537                            (
1538                                AggregateFunc::SumInterval,
1539                                Accum::Interval {
1540                                    months,
1541                                    days,
1542                                    micros,
1543                                    ..
1544                                },
1545                            ) => {
1546                                // PostgreSQL reports a sum that leaves the
1547                                // `Interval` field widths as `interval out of
1548                                // range`. So do we.
1549                                if Interval::try_new(
1550                                    months.into_inner(),
1551                                    days.into_inner(),
1552                                    micros.into_inner(),
1553                                )
1554                                .is_none()
1555                                {
1556                                    let err = EvalError::IntervalOutOfRange(
1557                                        format!(
1558                                            "{months} months {days} days {micros} microseconds"
1559                                        )
1560                                        .into(),
1561                                    );
1562                                    output.push((err.into(), Diff::ONE));
1563                                }
1564                            }
1565                            _ => (), // no more errors to check for at this point!
1566                        }
1567                    }
1568
1569                    // If `mfp_after` can error, then evaluate it here.
1570                    let Some(mfp) = &mfp_after2 else { return };
1571                    let temp_storage = RowArena::new();
1572                    let mut datums_local = datums2.borrow();
1573                    key.extend_datums(&temp_storage, &mut datums_local, None);
1574                    for (aggr, accum) in full_aggrs2.iter().zip_eq(accums) {
1575                        datums_local.push(finalize_accum(&aggr.func, accum, total));
1576                    }
1577
1578                    if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
1579                        output.push((e.into(), Diff::ONE));
1580                    }
1581                },
1582            );
1583        (
1584            arranged_output,
1585            arranged_errs.as_collection(|_key, error| error.clone()),
1586        )
1587    }
1588}
1589
1590/// Evaluates the fused MFP, if one exists, on a reconstructed `DatumVecBorrow`
1591/// containing key and aggregate values, then returns a result `Row` or `None`
1592/// if the MFP filters the result out.
1593fn evaluate_mfp_after<'a, 'b>(
1594    mfp_after: &'a Option<SafeMfpPlan<LirScalarExpr>>,
1595    datums_local: &'b mut mz_repr::DatumVecBorrow<'a>,
1596    temp_storage: &'a RowArena,
1597    key_len: usize,
1598) -> Option<Row> {
1599    let mut row_builder = SharedRow::get();
1600    // Apply MFP if it exists and pack a Row of
1601    // aggregate values from `datums_local`.
1602    if let Some(mfp) = mfp_after {
1603        // It must ignore errors here, but they are scanned
1604        // for elsewhere if the MFP can error.
1605        if let Ok(Some(iter)) = mfp.evaluate_iter(datums_local, temp_storage) {
1606            // The `mfp_after` must preserve the key columns,
1607            // so we can skip them to form aggregation results.
1608            Some(row_builder.pack_using(iter.skip(key_len)))
1609        } else {
1610            None
1611        }
1612    } else {
1613        Some(row_builder.pack_using(&datums_local[key_len..]))
1614    }
1615}
1616
1617fn accumulable_zero(aggr_func: &AggregateFunc) -> Accum {
1618    match aggr_func {
1619        AggregateFunc::Any | AggregateFunc::All => Accum::Bool {
1620            trues: Diff::ZERO,
1621            falses: Diff::ZERO,
1622        },
1623        AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => Accum::Float {
1624            accum: AccumCount::ZERO,
1625            pos_infs: Diff::ZERO,
1626            neg_infs: Diff::ZERO,
1627            nans: Diff::ZERO,
1628            non_nulls: Diff::ZERO,
1629        },
1630        AggregateFunc::SumNumeric => Accum::Numeric {
1631            accum: OrderedNumericAgg(NumericAgg::zero()),
1632            pos_infs: Diff::ZERO,
1633            neg_infs: Diff::ZERO,
1634            nans: Diff::ZERO,
1635            non_nulls: Diff::ZERO,
1636        },
1637        AggregateFunc::SumInterval => Accum::Interval {
1638            months: AccumCount::ZERO,
1639            days: AccumCount::ZERO,
1640            micros: AccumCount::ZERO,
1641            non_nulls: Diff::ZERO,
1642        },
1643        _ => Accum::SimpleNumber {
1644            accum: AccumCount::ZERO,
1645            non_nulls: Diff::ZERO,
1646        },
1647    }
1648}
1649
1650/// The number of fractional bits of binary precision retained by the
1651/// fixed-point representation used to accumulate float sums. The fixed-point
1652/// scale is `FLOAT_SCALE == 2^FLOAT_SCALE_EXP`.
1653const FLOAT_SCALE_EXP: u32 = 24;
1654
1655/// The fixed-point scale applied to float sums, i.e. `2^FLOAT_SCALE_EXP`.
1656#[allow(clippy::as_conversions)] // Integer-to-float cast, exact and const-evaluable.
1657const FLOAT_SCALE: f64 = (1_u64 << FLOAT_SCALE_EXP) as f64;
1658
1659/// Maps a finite `f64` onto the fixed-point `i128` domain used to accumulate
1660/// float sums, i.e. computes `trunc(n * FLOAT_SCALE)` reduced modulo `2^128`.
1661///
1662/// Conceptually this multiplies `n` by `FLOAT_SCALE` and truncates towards zero,
1663/// but it does so using *wrapping* (modulo `2^128`) rather than *saturating*
1664/// semantics, and it never forms the intermediate product `n * FLOAT_SCALE` as
1665/// an `f64` (which could itself overflow to infinity for very large `n`).
1666///
1667/// Wrapping is what makes this conversion a group homomorphism into the additive
1668/// group of `i128` (mod `2^128`), matching the wrapping arithmetic used when
1669/// accumulators are combined and retracted. As a result, a set of large finite
1670/// values whose *sum* is representable produces the correct result even when the
1671/// individual values fall outside the representable fixed-point range. Saturating
1672/// instead breaks this: e.g. `1.1e31` and `-1.1e31` both overflow the domain and
1673/// would saturate to `i128::MAX` and `i128::MIN`, which sum to `-1` rather than
1674/// `0` (see database-issues#11265).
1675fn float_to_fixed_point(n: f64) -> i128 {
1676    mz_ore::soft_assert_no_log!(n.is_finite());
1677
1678    // Decompose `n` into integer parts such that `n == sign * mantissa *
1679    // 2^exponent`. Folding in the `* 2^FLOAT_SCALE_EXP` scaling then amounts to
1680    // shifting `mantissa` left by `exponent + FLOAT_SCALE_EXP` bits.
1681    let (mantissa, exponent, sign) = Float::integer_decode(n);
1682    let significand = u128::from(mantissa);
1683    let exp = i64::from(exponent) + i64::from(FLOAT_SCALE_EXP);
1684
1685    let magnitude: u128 = if exp >= 0 {
1686        // Left shifts of 128 or more bits leave nothing within the 128-bit
1687        // window; smaller shifts keep only the low 128 bits (i.e. mod `2^128`).
1688        match u32::try_from(exp) {
1689            Ok(shift) if shift < 128 => significand << shift,
1690            _ => 0,
1691        }
1692    } else {
1693        // Right shift truncates the fractional part towards zero. Subnormals
1694        // (and zero) shift entirely out of the window and become zero.
1695        match u32::try_from(-exp) {
1696            Ok(shift) if shift < 128 => significand >> shift,
1697            _ => 0,
1698        }
1699    };
1700
1701    // Reinterpret the magnitude as a signed `i128` (wrapping into the signed
1702    // domain) and apply the sign of `n`.
1703    let magnitude = magnitude.cast_signed();
1704    if sign < 0 {
1705        magnitude.wrapping_neg()
1706    } else {
1707        magnitude
1708    }
1709}
1710
1711fn datum_to_accumulator(aggregate_func: &AggregateFunc, datum: Datum) -> Accum {
1712    match aggregate_func {
1713        AggregateFunc::Count => Accum::SimpleNumber {
1714            accum: AccumCount::ZERO, // unused for AggregateFunc::Count
1715            non_nulls: if datum.is_null() {
1716                Diff::ZERO
1717            } else {
1718                Diff::ONE
1719            },
1720        },
1721        AggregateFunc::Any | AggregateFunc::All => match datum {
1722            Datum::True => Accum::Bool {
1723                trues: Diff::ONE,
1724                falses: Diff::ZERO,
1725            },
1726            Datum::Null => Accum::Bool {
1727                trues: Diff::ZERO,
1728                falses: Diff::ZERO,
1729            },
1730            Datum::False => Accum::Bool {
1731                trues: Diff::ZERO,
1732                falses: Diff::ONE,
1733            },
1734            x => panic!("Invalid argument to AggregateFunc::Any: {x:?}"),
1735        },
1736        AggregateFunc::Dummy => match datum {
1737            Datum::Dummy => Accum::SimpleNumber {
1738                accum: AccumCount::ZERO,
1739                non_nulls: Diff::ZERO,
1740            },
1741            x => panic!("Invalid argument to AggregateFunc::Dummy: {x:?}"),
1742        },
1743        AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => {
1744            let n = match datum {
1745                Datum::Float32(n) => f64::from(*n),
1746                Datum::Float64(n) => *n,
1747                Datum::Null => 0f64,
1748                x => panic!("Invalid argument to AggregateFunc::{aggregate_func:?}: {x:?}"),
1749            };
1750
1751            let nans = Diff::from(n.is_nan());
1752            let pos_infs = Diff::from(n == f64::INFINITY);
1753            let neg_infs = Diff::from(n == f64::NEG_INFINITY);
1754            let non_nulls = Diff::from(datum != Datum::Null);
1755
1756            // Map the floating point value onto a fixed precision domain
1757            // All special values should map to zero, since they are tracked separately
1758            let accum = if nans.is_positive() || pos_infs.is_positive() || neg_infs.is_positive() {
1759                AccumCount::ZERO
1760            } else {
1761                // Wrap (rather than saturate) on overflow, so that the mapping is
1762                // a group homomorphism and large finite values whose sum is in
1763                // range still produce correct results (database-issues#11265).
1764                float_to_fixed_point(n).into()
1765            };
1766
1767            Accum::Float {
1768                accum,
1769                pos_infs,
1770                neg_infs,
1771                nans,
1772                non_nulls,
1773            }
1774        }
1775        AggregateFunc::SumNumeric => match datum {
1776            Datum::Numeric(n) => {
1777                let (accum, pos_infs, neg_infs, nans) = if n.0.is_infinite() {
1778                    if n.0.is_negative() {
1779                        (NumericAgg::zero(), Diff::ZERO, Diff::ONE, Diff::ZERO)
1780                    } else {
1781                        (NumericAgg::zero(), Diff::ONE, Diff::ZERO, Diff::ZERO)
1782                    }
1783                } else if n.0.is_nan() {
1784                    (NumericAgg::zero(), Diff::ZERO, Diff::ZERO, Diff::ONE)
1785                } else {
1786                    // Take a narrow decimal (datum) into a wide decimal
1787                    // (aggregator).
1788                    let mut cx_agg = numeric::cx_agg();
1789                    (cx_agg.to_width(n.0), Diff::ZERO, Diff::ZERO, Diff::ZERO)
1790                };
1791
1792                Accum::Numeric {
1793                    accum: OrderedNumericAgg(accum),
1794                    pos_infs,
1795                    neg_infs,
1796                    nans,
1797                    non_nulls: Diff::ONE,
1798                }
1799            }
1800            Datum::Null => Accum::Numeric {
1801                accum: OrderedNumericAgg(NumericAgg::zero()),
1802                pos_infs: Diff::ZERO,
1803                neg_infs: Diff::ZERO,
1804                nans: Diff::ZERO,
1805                non_nulls: Diff::ZERO,
1806            },
1807            x => panic!("Invalid argument to AggregateFunc::SumNumeric: {x:?}"),
1808        },
1809        AggregateFunc::SumInterval => match datum {
1810            Datum::Interval(i) => Accum::Interval {
1811                months: i.months.into(),
1812                days: i.days.into(),
1813                micros: i.micros.into(),
1814                non_nulls: Diff::ONE,
1815            },
1816            Datum::Null => Accum::Interval {
1817                months: AccumCount::ZERO,
1818                days: AccumCount::ZERO,
1819                micros: AccumCount::ZERO,
1820                non_nulls: Diff::ZERO,
1821            },
1822            x => panic!("Invalid argument to AggregateFunc::SumInterval: {x:?}"),
1823        },
1824        _ => {
1825            // Other accumulations need to disentangle the accumulable
1826            // value from its NULL-ness, which is not quite as easily
1827            // accumulated.
1828            match datum {
1829                Datum::Int16(i) => Accum::SimpleNumber {
1830                    accum: i.into(),
1831                    non_nulls: Diff::ONE,
1832                },
1833                Datum::Int32(i) => Accum::SimpleNumber {
1834                    accum: i.into(),
1835                    non_nulls: Diff::ONE,
1836                },
1837                Datum::Int64(i) => Accum::SimpleNumber {
1838                    accum: i.into(),
1839                    non_nulls: Diff::ONE,
1840                },
1841                Datum::UInt16(u) => Accum::SimpleNumber {
1842                    accum: u.into(),
1843                    non_nulls: Diff::ONE,
1844                },
1845                Datum::UInt32(u) => Accum::SimpleNumber {
1846                    accum: u.into(),
1847                    non_nulls: Diff::ONE,
1848                },
1849                Datum::UInt64(u) => Accum::SimpleNumber {
1850                    accum: u.into(),
1851                    non_nulls: Diff::ONE,
1852                },
1853                Datum::MzTimestamp(t) => Accum::SimpleNumber {
1854                    accum: u64::from(t).into(),
1855                    non_nulls: Diff::ONE,
1856                },
1857                Datum::Null => Accum::SimpleNumber {
1858                    accum: AccumCount::ZERO,
1859                    non_nulls: Diff::ZERO,
1860                },
1861                x => panic!("Accumulating non-integer data: {x:?}"),
1862            }
1863        }
1864    }
1865}
1866
1867fn finalize_accum<'a>(aggr_func: &'a AggregateFunc, accum: &'a Accum, total: Diff) -> Datum<'a> {
1868    // The finished value depends on the aggregation function in a variety of ways.
1869    // For all aggregates but count, if only null values were
1870    // accumulated, then the output is null.
1871    if total.is_positive() && accum.is_zero() && *aggr_func != AggregateFunc::Count {
1872        Datum::Null
1873    } else {
1874        match (&aggr_func, &accum) {
1875            (AggregateFunc::Count, Accum::SimpleNumber { non_nulls, .. }) => {
1876                Datum::Int64(non_nulls.into_inner())
1877            }
1878            (AggregateFunc::All, Accum::Bool { falses, trues }) => {
1879                // If any false, else if all true, else must be no false and some nulls.
1880                if falses.is_positive() {
1881                    Datum::False
1882                } else if *trues == total {
1883                    Datum::True
1884                } else {
1885                    Datum::Null
1886                }
1887            }
1888            (AggregateFunc::Any, Accum::Bool { falses, trues }) => {
1889                // If any true, else if all false, else must be no true and some nulls.
1890                if trues.is_positive() {
1891                    Datum::True
1892                } else if *falses == total {
1893                    Datum::False
1894                } else {
1895                    Datum::Null
1896                }
1897            }
1898            (AggregateFunc::Dummy, _) => Datum::Dummy,
1899            // If any non-nulls, just report the aggregate.
1900            (AggregateFunc::SumInt16, Accum::SimpleNumber { accum, .. })
1901            | (AggregateFunc::SumInt32, Accum::SimpleNumber { accum, .. }) => {
1902                // This conversion is safe, as long as we have less than 2^32
1903                // summands.
1904                // TODO(benesch): are we guaranteed to have less than 2^32 summands?
1905                // If so, rewrite to avoid `as`.
1906                #[allow(clippy::as_conversions)]
1907                Datum::Int64(accum.into_inner() as i64)
1908            }
1909            (AggregateFunc::SumInt64, Accum::SimpleNumber { accum, .. }) => Datum::from(*accum),
1910            (AggregateFunc::SumUInt16, Accum::SimpleNumber { accum, .. })
1911            | (AggregateFunc::SumUInt32, Accum::SimpleNumber { accum, .. }) => {
1912                if !accum.is_negative() {
1913                    // Our semantics of overflow are not clearly articulated wrt.
1914                    // unsigned vs. signed types (database-issues#5172). We adopt an
1915                    // unsigned wrapping behavior to match what we do above for
1916                    // signed types.
1917                    // TODO(vmarcos): remove potentially dangerous usage of `as`.
1918                    #[allow(clippy::as_conversions)]
1919                    Datum::UInt64(accum.into_inner() as u64)
1920                } else {
1921                    // Note that we return a value here, but an error in the other
1922                    // operator of the reduce_pair. Therefore, we expect that this
1923                    // value will never be exposed as an output.
1924                    Datum::Null
1925                }
1926            }
1927            (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1928                if !accum.is_negative() {
1929                    Datum::from(*accum)
1930                } else {
1931                    // Note that we return a value here, but an error in the other
1932                    // operator of the reduce_pair. Therefore, we expect that this
1933                    // value will never be exposed as an output.
1934                    Datum::Null
1935                }
1936            }
1937            (
1938                AggregateFunc::SumFloat32,
1939                Accum::Float {
1940                    accum,
1941                    pos_infs,
1942                    neg_infs,
1943                    nans,
1944                    non_nulls: _,
1945                },
1946            ) => {
1947                if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
1948                    // NaNs are NaNs and cases where we've seen a
1949                    // mixture of positive and negative infinities.
1950                    Datum::from(f32::NAN)
1951                } else if pos_infs.is_positive() {
1952                    Datum::from(f32::INFINITY)
1953                } else if neg_infs.is_positive() {
1954                    Datum::from(f32::NEG_INFINITY)
1955                } else {
1956                    let sum = f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE;
1957                    Datum::from(f32::cast_lossy(sum))
1958                }
1959            }
1960            (
1961                AggregateFunc::SumFloat64,
1962                Accum::Float {
1963                    accum,
1964                    pos_infs,
1965                    neg_infs,
1966                    nans,
1967                    non_nulls: _,
1968                },
1969            ) => {
1970                if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
1971                    // NaNs are NaNs and cases where we've seen a
1972                    // mixture of positive and negative infinities.
1973                    Datum::from(f64::NAN)
1974                } else if pos_infs.is_positive() {
1975                    Datum::from(f64::INFINITY)
1976                } else if neg_infs.is_positive() {
1977                    Datum::from(f64::NEG_INFINITY)
1978                } else {
1979                    Datum::from(f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE)
1980                }
1981            }
1982            (
1983                AggregateFunc::SumNumeric,
1984                Accum::Numeric {
1985                    accum,
1986                    pos_infs,
1987                    neg_infs,
1988                    nans,
1989                    non_nulls: _,
1990                },
1991            ) => {
1992                let mut cx_datum = numeric::cx_datum();
1993                let d = cx_datum.to_width(accum.0);
1994                // Take a wide decimal (aggregator) into a
1995                // narrow decimal (datum). If this operation
1996                // overflows the datum, this new value will be
1997                // +/- infinity. However, the aggregator tracks
1998                // the amount of overflow, making it invertible.
1999                let inf_d = d.is_infinite();
2000                let neg_d = d.is_negative();
2001                let pos_inf = pos_infs.is_positive() || (inf_d && !neg_d);
2002                let neg_inf = neg_infs.is_positive() || (inf_d && neg_d);
2003                if nans.is_positive() || (pos_inf && neg_inf) {
2004                    // NaNs are NaNs and cases where we've seen a
2005                    // mixture of positive and negative infinities.
2006                    Datum::from(Numeric::nan())
2007                } else if pos_inf {
2008                    Datum::from(Numeric::infinity())
2009                } else if neg_inf {
2010                    let mut cx = numeric::cx_datum();
2011                    let mut d = Numeric::infinity();
2012                    cx.neg(&mut d);
2013                    Datum::from(d)
2014                } else {
2015                    Datum::from(d)
2016                }
2017            }
2018            (
2019                AggregateFunc::SumInterval,
2020                Accum::Interval {
2021                    months,
2022                    days,
2023                    micros,
2024                    non_nulls: _,
2025                },
2026            ) => {
2027                match Interval::try_new(months.into_inner(), days.into_inner(), micros.into_inner())
2028                {
2029                    Some(interval) => Datum::Interval(interval),
2030                    // The sum overflows an `Interval`. In the accumulable
2031                    // reduce pair the sibling operator raises the error, so
2032                    // this value is never exposed. `AccumulableOneByOneAggr`
2033                    // has no sibling, so a window aggregate does surface this
2034                    // NULL. See `mz_expr::sum_interval_counted`.
2035                    None => Datum::Null,
2036                }
2037            }
2038            _ => panic!(
2039                "Unexpected accumulation (aggr={:?}, accum={accum:?})",
2040                aggr_func
2041            ),
2042        }
2043    }
2044}
2045
2046/// The type for accumulator counting. Set to [`Overflowing<u128>`](mz_ore::Overflowing).
2047type AccumCount = mz_ore::Overflowing<i128>;
2048
2049/// Accumulates values for the various types of accumulable aggregations.
2050///
2051/// We assume that there are not more than 2^32 elements for the aggregation.
2052/// Thus we can perform a summation over i32 in an i64 accumulator
2053/// and not worry about exceeding its bounds.
2054///
2055/// The float accumulator performs accumulation in fixed point arithmetic. The fixed
2056/// point representation has less precision than a double. It is entirely possible
2057/// that the values of the accumulator overflow, thus we have to use wrapping arithmetic
2058/// to preserve group guarantees.
2059#[derive(
2060    Debug,
2061    Clone,
2062    Copy,
2063    PartialEq,
2064    Eq,
2065    PartialOrd,
2066    Ord,
2067    Serialize,
2068    Deserialize,
2069    Columnar
2070)]
2071// The columnar container orders references with this derived `Ord`, which must agree with
2072// the owned `Ord`. It does because every field's reference type is its owned type.
2073#[columnar(derive(PartialEq, Eq, PartialOrd, Ord))]
2074enum Accum {
2075    /// Accumulates boolean values.
2076    Bool {
2077        /// The number of `true` values observed.
2078        trues: Diff,
2079        /// The number of `false` values observed.
2080        falses: Diff,
2081    },
2082    /// Accumulates simple numeric values.
2083    SimpleNumber {
2084        /// The accumulation of all non-NULL values observed.
2085        accum: AccumCount,
2086        /// The number of non-NULL values observed.
2087        non_nulls: Diff,
2088    },
2089    /// Accumulates float values.
2090    Float {
2091        /// Accumulates non-special float values, mapped to a fixed precision i128 domain to
2092        /// preserve associativity and commutativity
2093        accum: AccumCount,
2094        /// Counts +inf
2095        pos_infs: Diff,
2096        /// Counts -inf
2097        neg_infs: Diff,
2098        /// Counts NaNs
2099        nans: Diff,
2100        /// Counts non-NULL values
2101        non_nulls: Diff,
2102    },
2103    /// Accumulates intervals as three independent component sums, matching
2104    /// PostgreSQL's interval addition: nothing is carried from a coarser
2105    /// component into a finer one. Each component accumulates in the same
2106    /// `AccumCount` the other variants use, so a sum that overflows an
2107    /// `Interval` field still consolidates and retracts correctly, and the
2108    /// narrowing at finalization is the single place that decides whether the
2109    /// result is representable.
2110    Interval {
2111        /// The accumulation of all non-NULL month counts observed.
2112        months: AccumCount,
2113        /// The accumulation of all non-NULL day counts observed.
2114        days: AccumCount,
2115        /// The accumulation of all non-NULL microsecond counts observed.
2116        micros: AccumCount,
2117        /// The number of non-NULL values observed.
2118        non_nulls: Diff,
2119    },
2120    /// Accumulates arbitrary precision decimals.
2121    Numeric {
2122        /// Accumulates non-special values
2123        accum: OrderedNumericAgg,
2124        /// Counts +inf
2125        pos_infs: Diff,
2126        /// Counts -inf
2127        neg_infs: Diff,
2128        /// Counts NaNs
2129        nans: Diff,
2130        /// Counts non-NULL values
2131        non_nulls: Diff,
2132    },
2133}
2134
2135impl IsZero for Accum {
2136    fn is_zero(&self) -> bool {
2137        match self {
2138            Accum::Bool { trues, falses } => trues.is_zero() && falses.is_zero(),
2139            Accum::SimpleNumber { accum, non_nulls } => accum.is_zero() && non_nulls.is_zero(),
2140            Accum::Float {
2141                accum,
2142                pos_infs,
2143                neg_infs,
2144                nans,
2145                non_nulls,
2146            } => {
2147                accum.is_zero()
2148                    && pos_infs.is_zero()
2149                    && neg_infs.is_zero()
2150                    && nans.is_zero()
2151                    && non_nulls.is_zero()
2152            }
2153            Accum::Interval {
2154                months,
2155                days,
2156                micros,
2157                non_nulls,
2158            } => months.is_zero() && days.is_zero() && micros.is_zero() && non_nulls.is_zero(),
2159            Accum::Numeric {
2160                accum,
2161                pos_infs,
2162                neg_infs,
2163                nans,
2164                non_nulls,
2165            } => {
2166                accum.0.is_zero()
2167                    && pos_infs.is_zero()
2168                    && neg_infs.is_zero()
2169                    && nans.is_zero()
2170                    && non_nulls.is_zero()
2171            }
2172        }
2173    }
2174}
2175
2176impl Semigroup for Accum {
2177    fn plus_equals(&mut self, other: &Accum) {
2178        match (&mut *self, other) {
2179            (
2180                Accum::Bool { trues, falses },
2181                Accum::Bool {
2182                    trues: other_trues,
2183                    falses: other_falses,
2184                },
2185            ) => {
2186                *trues += other_trues;
2187                *falses += other_falses;
2188            }
2189            (
2190                Accum::SimpleNumber { accum, non_nulls },
2191                Accum::SimpleNumber {
2192                    accum: other_accum,
2193                    non_nulls: other_non_nulls,
2194                },
2195            ) => {
2196                *accum += other_accum;
2197                *non_nulls += other_non_nulls;
2198            }
2199            (
2200                Accum::Float {
2201                    accum,
2202                    pos_infs,
2203                    neg_infs,
2204                    nans,
2205                    non_nulls,
2206                },
2207                Accum::Float {
2208                    accum: other_accum,
2209                    pos_infs: other_pos_infs,
2210                    neg_infs: other_neg_infs,
2211                    nans: other_nans,
2212                    non_nulls: other_non_nulls,
2213                },
2214            ) => {
2215                *accum = accum.checked_add(*other_accum).unwrap_or_else(|| {
2216                    warn!("Float accumulator overflow. Incorrect results possible");
2217                    accum.wrapping_add(*other_accum)
2218                });
2219                *pos_infs += other_pos_infs;
2220                *neg_infs += other_neg_infs;
2221                *nans += other_nans;
2222                *non_nulls += other_non_nulls;
2223            }
2224            (
2225                Accum::Interval {
2226                    months,
2227                    days,
2228                    micros,
2229                    non_nulls,
2230                },
2231                Accum::Interval {
2232                    months: other_months,
2233                    days: other_days,
2234                    micros: other_micros,
2235                    non_nulls: other_non_nulls,
2236                },
2237            ) => {
2238                *months += other_months;
2239                *days += other_days;
2240                *micros += other_micros;
2241                *non_nulls += other_non_nulls;
2242            }
2243            (
2244                Accum::Numeric {
2245                    accum,
2246                    pos_infs,
2247                    neg_infs,
2248                    nans,
2249                    non_nulls,
2250                },
2251                Accum::Numeric {
2252                    accum: other_accum,
2253                    pos_infs: other_pos_infs,
2254                    neg_infs: other_neg_infs,
2255                    nans: other_nans,
2256                    non_nulls: other_non_nulls,
2257                },
2258            ) => {
2259                let mut cx_agg = numeric::cx_agg();
2260                cx_agg.add(&mut accum.0, &other_accum.0);
2261                // `rounded` signals we have exceeded the aggregator's max
2262                // precision, which means we've lost commutativity and
2263                // associativity; nothing to be done here, so panic. For more
2264                // context, see the DEC_Rounded definition at
2265                // http://speleotrove.com/decimal/dncont.html
2266                assert!(!cx_agg.status().rounded(), "Accum::Numeric overflow");
2267                // Reduce to reclaim unused decimal precision. Note that this
2268                // reduction must happen somewhere to make the following
2269                // invertible:
2270                // ```
2271                // CREATE TABLE a (a numeric);
2272                // CREATE MATERIALIZED VIEW t as SELECT sum(a) FROM a;
2273                // INSERT INTO a VALUES ('9e39'), ('9e-39');
2274                // ```
2275                // This will now return infinity. However, we can retract the
2276                // value that blew up its precision:
2277                // ```
2278                // INSERT INTO a VALUES ('-9e-39');
2279                // ```
2280                // This leaves `t`'s aggregator with a value of 9e39. However,
2281                // without doing a reduction, `libdecnum` will store the value
2282                // as 9e39+0e-39, which still exceeds the narrower context's
2283                // precision. By doing the reduction, we can "reclaim" the 39
2284                // digits of precision.
2285                cx_agg.reduce(&mut accum.0);
2286                *pos_infs += other_pos_infs;
2287                *neg_infs += other_neg_infs;
2288                *nans += other_nans;
2289                *non_nulls += other_non_nulls;
2290            }
2291            (l, r) => unreachable!(
2292                "Accumulator::plus_equals called with non-matching variants: {l:?} vs {r:?}"
2293            ),
2294        }
2295    }
2296}
2297
2298impl Multiply<Diff> for Accum {
2299    type Output = Accum;
2300
2301    fn multiply(self, factor: &Diff) -> Accum {
2302        let factor = *factor;
2303        match self {
2304            Accum::Bool { trues, falses } => Accum::Bool {
2305                trues: trues * factor,
2306                falses: falses * factor,
2307            },
2308            Accum::SimpleNumber { accum, non_nulls } => Accum::SimpleNumber {
2309                accum: accum * AccumCount::from(factor),
2310                non_nulls: non_nulls * factor,
2311            },
2312            Accum::Float {
2313                accum,
2314                pos_infs,
2315                neg_infs,
2316                nans,
2317                non_nulls,
2318            } => Accum::Float {
2319                accum: accum
2320                    .checked_mul(AccumCount::from(factor))
2321                    .unwrap_or_else(|| {
2322                        warn!("Float accumulator overflow. Incorrect results possible");
2323                        accum.wrapping_mul(AccumCount::from(factor))
2324                    }),
2325                pos_infs: pos_infs * factor,
2326                neg_infs: neg_infs * factor,
2327                nans: nans * factor,
2328                non_nulls: non_nulls * factor,
2329            },
2330            Accum::Interval {
2331                months,
2332                days,
2333                micros,
2334                non_nulls,
2335            } => Accum::Interval {
2336                months: months * AccumCount::from(factor),
2337                days: days * AccumCount::from(factor),
2338                micros: micros * AccumCount::from(factor),
2339                non_nulls: non_nulls * factor,
2340            },
2341            Accum::Numeric {
2342                accum,
2343                pos_infs,
2344                neg_infs,
2345                nans,
2346                non_nulls,
2347            } => {
2348                let mut cx = numeric::cx_agg();
2349                let mut f = NumericAgg::from(factor.into_inner());
2350                // Unlike `plus_equals`, not necessary to reduce after this operation because `f` will
2351                // always be an integer, i.e. we are never increasing the
2352                // values' scale.
2353                cx.mul(&mut f, &accum.0);
2354                // `rounded` signals we have exceeded the aggregator's max
2355                // precision, which means we've lost commutativity and
2356                // associativity; nothing to be done here, so panic. For more
2357                // context, see the DEC_Rounded definition at
2358                // http://speleotrove.com/decimal/dncont.html
2359                assert!(!cx.status().rounded(), "Accum::Numeric multiply overflow");
2360                Accum::Numeric {
2361                    accum: OrderedNumericAgg(f),
2362                    pos_infs: pos_infs * factor,
2363                    neg_infs: neg_infs * factor,
2364                    nans: nans * factor,
2365                    non_nulls: non_nulls * factor,
2366                }
2367            }
2368        }
2369    }
2370}
2371
2372// The batcher stages updates in columnation chunks before they reach the arrangement,
2373// which stores `Accum` in its columnar form.
2374impl Columnation for Accum {
2375    type InnerRegion = CopyRegion<Self>;
2376}
2377
2378/// Monoids for in-place compaction of monotonic streams.
2379mod monoids {
2380
2381    // We can improve the performance of some aggregations through the use of algebra.
2382    // In particular, we can move some of the aggregations in to the `diff` field of
2383    // updates, by changing `diff` from integers to a different algebraic structure.
2384    //
2385    // The one we use is called a "semigroup", and it means that the structure has a
2386    // symmetric addition operator. The trait we use also allows the semigroup elements
2387    // to present as "zero", meaning they always act as the identity under +. Here,
2388    // `Datum::Null` acts as the identity under +, _but_ we don't want to make this
2389    // known to DD by the `is_zero` method, see comment there. So, from the point of view
2390    // of DD, this Semigroup should _not_ have a zero.
2391    //
2392    // WARNING: `Datum::Null` should continue to act as the identity of our + (even if we
2393    // add a new enum variant here), because other code (e.g., `HierarchicalOneByOneAggr`)
2394    // assumes this.
2395
2396    use columnation::{Columnation, Region};
2397    use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
2398    use mz_expr::AggregateFunc;
2399    use mz_ore::soft_panic_or_log;
2400    use mz_repr::{Datum, Diff, Row};
2401    use serde::{Deserialize, Serialize};
2402
2403    /// A monoid containing a single-datum row.
2404    #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
2405    pub enum ReductionMonoid {
2406        Min(Row),
2407        Max(Row),
2408    }
2409
2410    impl ReductionMonoid {
2411        pub fn finalize(&self) -> &Row {
2412            use ReductionMonoid::*;
2413            match self {
2414                Min(row) | Max(row) => row,
2415            }
2416        }
2417    }
2418
2419    impl Clone for ReductionMonoid {
2420        fn clone(&self) -> Self {
2421            use ReductionMonoid::*;
2422            match self {
2423                Min(row) => Min(row.clone()),
2424                Max(row) => Max(row.clone()),
2425            }
2426        }
2427
2428        fn clone_from(&mut self, source: &Self) {
2429            use ReductionMonoid::*;
2430
2431            let mut row = std::mem::take(match self {
2432                Min(row) | Max(row) => row,
2433            });
2434
2435            let source_row = match source {
2436                Min(row) | Max(row) => row,
2437            };
2438
2439            row.clone_from(source_row);
2440
2441            match source {
2442                Min(_) => *self = Min(row),
2443                Max(_) => *self = Max(row),
2444            }
2445        }
2446    }
2447
2448    impl Multiply<Diff> for ReductionMonoid {
2449        type Output = Self;
2450
2451        fn multiply(self, factor: &Diff) -> Self {
2452            // Multiplication in ReductionMonoid is idempotent, and
2453            // its users must ascertain its monotonicity beforehand
2454            // (typically with ensure_monotonic) since it has no zero
2455            // value for us to use here.
2456            assert!(factor.is_positive());
2457            self
2458        }
2459    }
2460
2461    impl Semigroup for ReductionMonoid {
2462        fn plus_equals(&mut self, rhs: &Self) {
2463            match (self, rhs) {
2464                (ReductionMonoid::Min(lhs), ReductionMonoid::Min(rhs)) => {
2465                    let swap = {
2466                        let lhs_val = lhs.unpack_first();
2467                        let rhs_val = rhs.unpack_first();
2468                        // Datum::Null is the identity, not a small element.
2469                        match (lhs_val, rhs_val) {
2470                            (_, Datum::Null) => false,
2471                            (Datum::Null, _) => true,
2472                            (lhs, rhs) => rhs < lhs,
2473                        }
2474                    };
2475                    if swap {
2476                        lhs.clone_from(rhs);
2477                    }
2478                }
2479                (ReductionMonoid::Max(lhs), ReductionMonoid::Max(rhs)) => {
2480                    let swap = {
2481                        let lhs_val = lhs.unpack_first();
2482                        let rhs_val = rhs.unpack_first();
2483                        // Datum::Null is the identity, not a large element.
2484                        match (lhs_val, rhs_val) {
2485                            (_, Datum::Null) => false,
2486                            (Datum::Null, _) => true,
2487                            (lhs, rhs) => rhs > lhs,
2488                        }
2489                    };
2490                    if swap {
2491                        lhs.clone_from(rhs);
2492                    }
2493                }
2494                (lhs, rhs) => {
2495                    soft_panic_or_log!(
2496                        "Mismatched monoid variants in reduction! lhs: {lhs:?} rhs: {rhs:?}"
2497                    );
2498                }
2499            }
2500        }
2501    }
2502
2503    impl IsZero for ReductionMonoid {
2504        fn is_zero(&self) -> bool {
2505            // It totally looks like we could return true here for `Datum::Null`, but don't do this!
2506            // DD uses true results of this method to make stuff disappear. This makes sense when
2507            // diffs mean really just diffs, but for `ReductionMonoid` diffs hold reduction results.
2508            // We don't want funny stuff, like disappearing, happening to reduction results even
2509            // when they are null. (This would confuse, e.g., `ReduceCollation` for null inputs.)
2510            false
2511        }
2512    }
2513
2514    impl Columnation for ReductionMonoid {
2515        type InnerRegion = ReductionMonoidRegion;
2516    }
2517
2518    /// Region for [`ReductionMonoid`]. This region is special in that it stores both enum variants
2519    /// in the same backing region. Alternatively, it could store it in two regions, but we select
2520    /// the former for simplicity reasons.
2521    #[derive(Default)]
2522    pub struct ReductionMonoidRegion {
2523        inner: <Row as Columnation>::InnerRegion,
2524    }
2525
2526    impl Region for ReductionMonoidRegion {
2527        type Item = ReductionMonoid;
2528
2529        unsafe fn copy(&mut self, item: &Self::Item) -> Self::Item {
2530            use ReductionMonoid::*;
2531            match item {
2532                Min(row) => Min(unsafe { self.inner.copy(row) }),
2533                Max(row) => Max(unsafe { self.inner.copy(row) }),
2534            }
2535        }
2536
2537        fn clear(&mut self) {
2538            self.inner.clear();
2539        }
2540
2541        fn reserve_items<'a, I>(&mut self, items: I)
2542        where
2543            Self: 'a,
2544            I: Iterator<Item = &'a Self::Item> + Clone,
2545        {
2546            self.inner
2547                .reserve_items(items.map(ReductionMonoid::finalize));
2548        }
2549
2550        fn reserve_regions<'a, I>(&mut self, regions: I)
2551        where
2552            Self: 'a,
2553            I: Iterator<Item = &'a Self> + Clone,
2554        {
2555            self.inner.reserve_regions(regions.map(|r| &r.inner));
2556        }
2557
2558        fn heap_size(&self, callback: impl FnMut(usize, usize)) {
2559            self.inner.heap_size(callback);
2560        }
2561    }
2562
2563    /// Get the correct monoid implementation for a given aggregation function. Note that
2564    /// all hierarchical aggregation functions need to supply a monoid implementation.
2565    pub fn get_monoid(row: Row, func: &AggregateFunc) -> Option<ReductionMonoid> {
2566        match func {
2567            AggregateFunc::MaxNumeric
2568            | AggregateFunc::MaxInt16
2569            | AggregateFunc::MaxInt32
2570            | AggregateFunc::MaxInt64
2571            | AggregateFunc::MaxUInt16
2572            | AggregateFunc::MaxUInt32
2573            | AggregateFunc::MaxUInt64
2574            | AggregateFunc::MaxMzTimestamp
2575            | AggregateFunc::MaxFloat32
2576            | AggregateFunc::MaxFloat64
2577            | AggregateFunc::MaxBool
2578            | AggregateFunc::MaxString
2579            | AggregateFunc::MaxDate
2580            | AggregateFunc::MaxTimestamp
2581            | AggregateFunc::MaxTimestampTz
2582            | AggregateFunc::MaxInterval
2583            | AggregateFunc::MaxTime
2584            | AggregateFunc::MaxArray
2585            | AggregateFunc::MaxList => Some(ReductionMonoid::Max(row)),
2586            AggregateFunc::MinNumeric
2587            | AggregateFunc::MinInt16
2588            | AggregateFunc::MinInt32
2589            | AggregateFunc::MinInt64
2590            | AggregateFunc::MinUInt16
2591            | AggregateFunc::MinUInt32
2592            | AggregateFunc::MinUInt64
2593            | AggregateFunc::MinMzTimestamp
2594            | AggregateFunc::MinFloat32
2595            | AggregateFunc::MinFloat64
2596            | AggregateFunc::MinBool
2597            | AggregateFunc::MinString
2598            | AggregateFunc::MinDate
2599            | AggregateFunc::MinTimestamp
2600            | AggregateFunc::MinTimestampTz
2601            | AggregateFunc::MinInterval
2602            | AggregateFunc::MinTime
2603            | AggregateFunc::MinArray
2604            | AggregateFunc::MinList => Some(ReductionMonoid::Min(row)),
2605            AggregateFunc::SumInt16
2606            | AggregateFunc::SumInt32
2607            | AggregateFunc::SumInt64
2608            | AggregateFunc::SumUInt16
2609            | AggregateFunc::SumUInt32
2610            | AggregateFunc::SumUInt64
2611            | AggregateFunc::SumFloat32
2612            | AggregateFunc::SumFloat64
2613            | AggregateFunc::SumNumeric
2614            | AggregateFunc::SumInterval
2615            | AggregateFunc::Count
2616            | AggregateFunc::Any
2617            | AggregateFunc::All
2618            | AggregateFunc::Dummy
2619            | AggregateFunc::JsonbAgg { .. }
2620            | AggregateFunc::JsonbObjectAgg { .. }
2621            | AggregateFunc::MapAgg { .. }
2622            | AggregateFunc::ArrayConcat { .. }
2623            | AggregateFunc::ListConcat { .. }
2624            | AggregateFunc::StringAgg { .. }
2625            | AggregateFunc::RowNumber { .. }
2626            | AggregateFunc::Rank { .. }
2627            | AggregateFunc::DenseRank { .. }
2628            | AggregateFunc::LagLead { .. }
2629            | AggregateFunc::FirstValue { .. }
2630            | AggregateFunc::LastValue { .. }
2631            | AggregateFunc::WindowAggregate { .. }
2632            | AggregateFunc::FusedValueWindowFunc { .. }
2633            | AggregateFunc::FusedWindowAggregate { .. } => None,
2634        }
2635    }
2636}
2637
2638mod window_agg_helpers {
2639    use crate::render::reduce::*;
2640
2641    /// TODO: It would be better for performance to do the branching that is in the methods of this
2642    /// enum at the place where we are calling `eval_fast_window_agg`. Then we wouldn't need an enum
2643    /// here, and would parameterize `eval_fast_window_agg` with one of the implementations
2644    /// directly.
2645    pub enum OneByOneAggrImpls {
2646        Accumulable(AccumulableOneByOneAggr),
2647        Hierarchical(HierarchicalOneByOneAggr),
2648        Basic(mz_expr::NaiveOneByOneAggr),
2649    }
2650
2651    impl mz_expr::OneByOneAggr for OneByOneAggrImpls {
2652        fn new(agg: &AggregateFunc, reverse: bool) -> Self {
2653            match reduction_type(agg) {
2654                ReductionType::Basic => {
2655                    OneByOneAggrImpls::Basic(mz_expr::NaiveOneByOneAggr::new(agg, reverse))
2656                }
2657                ReductionType::Accumulable => {
2658                    OneByOneAggrImpls::Accumulable(AccumulableOneByOneAggr::new(agg))
2659                }
2660                ReductionType::Hierarchical => {
2661                    OneByOneAggrImpls::Hierarchical(HierarchicalOneByOneAggr::new(agg))
2662                }
2663            }
2664        }
2665
2666        fn give(&mut self, d: &Datum) {
2667            match self {
2668                OneByOneAggrImpls::Basic(i) => i.give(d),
2669                OneByOneAggrImpls::Accumulable(i) => i.give(d),
2670                OneByOneAggrImpls::Hierarchical(i) => i.give(d),
2671            }
2672        }
2673
2674        fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2675            // Note that the `reverse` parameter is currently forwarded only for Basic aggregations.
2676            match self {
2677                OneByOneAggrImpls::Basic(i) => i.get_current_aggregate(temp_storage),
2678                OneByOneAggrImpls::Accumulable(i) => i.get_current_aggregate(temp_storage),
2679                OneByOneAggrImpls::Hierarchical(i) => i.get_current_aggregate(temp_storage),
2680            }
2681        }
2682    }
2683
2684    pub struct AccumulableOneByOneAggr {
2685        aggr_func: AggregateFunc,
2686        accum: Accum,
2687        total: Diff,
2688    }
2689
2690    impl AccumulableOneByOneAggr {
2691        fn new(aggr_func: &AggregateFunc) -> Self {
2692            AccumulableOneByOneAggr {
2693                aggr_func: aggr_func.clone(),
2694                accum: accumulable_zero(aggr_func),
2695                total: Diff::ZERO,
2696            }
2697        }
2698
2699        fn give(&mut self, d: &Datum) {
2700            self.accum
2701                .plus_equals(&datum_to_accumulator(&self.aggr_func, d.clone()));
2702            self.total += Diff::ONE;
2703        }
2704
2705        fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2706            temp_storage.make_datum(|packer| {
2707                packer.push(finalize_accum(&self.aggr_func, &self.accum, self.total));
2708            })
2709        }
2710    }
2711
2712    pub struct HierarchicalOneByOneAggr {
2713        aggr_func: AggregateFunc,
2714        // Warning: We are assuming that `Datum::Null` acts as the identity for `ReductionMonoid`'s
2715        // `plus_equals`. (But _not_ relying here on `ReductionMonoid::is_zero`.)
2716        monoid: ReductionMonoid,
2717    }
2718
2719    impl HierarchicalOneByOneAggr {
2720        fn new(aggr_func: &AggregateFunc) -> Self {
2721            let mut row_buf = Row::default();
2722            row_buf.packer().push(Datum::Null);
2723            HierarchicalOneByOneAggr {
2724                aggr_func: aggr_func.clone(),
2725                monoid: get_monoid(row_buf, aggr_func)
2726                    .expect("aggr_func should be a hierarchical aggregation function"),
2727            }
2728        }
2729
2730        fn give(&mut self, d: &Datum) {
2731            let mut row_buf = Row::default();
2732            row_buf.packer().push(d);
2733            let m = get_monoid(row_buf, &self.aggr_func)
2734                .expect("aggr_func should be a hierarchical aggregation function");
2735            self.monoid.plus_equals(&m);
2736        }
2737
2738        fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2739            temp_storage.make_datum(|packer| packer.extend(self.monoid.finalize().iter()))
2740        }
2741    }
2742}
2743
2744#[cfg(test)]
2745mod tests {
2746    use super::*;
2747
2748    /// The saturating conversion that `float_to_fixed_point` replaces. Used to
2749    /// assert that the new wrapping conversion agrees on the in-range values
2750    /// where the old conversion was already correct.
2751    #[allow(clippy::as_conversions)]
2752    fn saturating_convert(n: f64) -> i128 {
2753        (n * FLOAT_SCALE) as i128
2754    }
2755
2756    #[mz_ore::test]
2757    fn float_to_fixed_point_matches_saturating_in_range() {
2758        // For values whose scaled magnitude comfortably fits in an `i128`, the
2759        // wrapping conversion must produce exactly the same result the previous
2760        // saturating cast did.
2761        let cases = [
2762            0.0,
2763            -0.0,
2764            1.0,
2765            -1.0,
2766            0.1,
2767            -0.1,
2768            0.5,
2769            -0.5,
2770            3.25,
2771            -3.25,
2772            123456.789,
2773            -123456.789,
2774            1e10,
2775            -1e10,
2776            1e20,
2777            -1e20,
2778            5e30, // large, but scaled magnitude still fits comfortably in i128
2779            -5e30,
2780        ];
2781        for n in cases {
2782            assert_eq!(
2783                float_to_fixed_point(n),
2784                saturating_convert(n),
2785                "mismatch for n = {n}"
2786            );
2787        }
2788    }
2789
2790    #[mz_ore::test]
2791    fn float_to_fixed_point_truncates_toward_zero() {
2792        // 1.75 * 2^24 = 29360128, exactly representable.
2793        assert_eq!(float_to_fixed_point(1.75), 29_360_128);
2794        assert_eq!(float_to_fixed_point(-1.75), -29_360_128);
2795
2796        // Fractional results truncate toward zero, matching the previous cast.
2797        let frac = 0.123_456_7_f64;
2798        assert_eq!(float_to_fixed_point(frac), saturating_convert(frac));
2799        assert_eq!(float_to_fixed_point(-frac), saturating_convert(-frac));
2800        assert_eq!(float_to_fixed_point(-frac), -float_to_fixed_point(frac));
2801    }
2802
2803    #[mz_ore::test]
2804    fn float_to_fixed_point_subnormals_round_to_zero() {
2805        assert_eq!(float_to_fixed_point(0.0), 0);
2806        assert_eq!(float_to_fixed_point(-0.0), 0);
2807        assert_eq!(float_to_fixed_point(f64::MIN_POSITIVE / 2.0), 0);
2808        assert_eq!(float_to_fixed_point(5e-324), 0); // smallest subnormal
2809    }
2810
2811    #[mz_ore::test]
2812    fn float_to_fixed_point_cancels_large_finite_values() {
2813        // Regression test for database-issues#11265: large finite values that
2814        // individually overflow the fixed-point domain must still sum to the
2815        // correct result when their mathematical sum is representable. The
2816        // previous saturating conversion produced `i128::MAX + i128::MIN == -1`.
2817        for &n in &[1.1e31_f64, 1e32, 5e33, 1e284] {
2818            assert_eq!(
2819                float_to_fixed_point(n).wrapping_add(float_to_fixed_point(-n)),
2820                0,
2821                "n = {n} did not cancel with -n"
2822            );
2823        }
2824    }
2825
2826    #[mz_ore::test]
2827    fn float_to_fixed_point_sum_via_accumulator() {
2828        // Exercise the full accumulate-then-finalize path for the reported case.
2829        let func = AggregateFunc::SumFloat64;
2830        let mut acc = accumulable_zero(&func);
2831        acc.plus_equals(&datum_to_accumulator(&func, Datum::from(1.1e31_f64)));
2832        acc.plus_equals(&datum_to_accumulator(&func, Datum::from(-1.1e31_f64)));
2833        let datum = finalize_accum(&func, &acc, Diff::from(2_i64));
2834        assert_eq!(datum, Datum::from(0.0_f64));
2835    }
2836
2837    /// Accumulators of every variant, in zero, accumulated, and negated states.
2838    fn sample_accums() -> Vec<Accum> {
2839        let mut cx = numeric::cx_datum();
2840        let mut numeric = |s: &str| Datum::from(cx.parse(s).unwrap());
2841        let cases: Vec<(AggregateFunc, Vec<Datum>)> = vec![
2842            (AggregateFunc::Count, vec![Datum::Null, Datum::Int64(5)]),
2843            (
2844                AggregateFunc::SumInt64,
2845                vec![Datum::Int64(-7), Datum::Int64(i64::MAX)],
2846            ),
2847            (
2848                AggregateFunc::SumUInt16,
2849                vec![Datum::UInt16(3), Datum::Null],
2850            ),
2851            (
2852                AggregateFunc::Any,
2853                vec![Datum::True, Datum::False, Datum::Null],
2854            ),
2855            (
2856                AggregateFunc::SumFloat64,
2857                vec![
2858                    Datum::from(1.5_f64),
2859                    Datum::from(f64::NAN),
2860                    Datum::from(f64::NEG_INFINITY),
2861                ],
2862            ),
2863            (
2864                AggregateFunc::SumNumeric,
2865                vec![
2866                    numeric("-12345.678"),
2867                    numeric("9e39"),
2868                    numeric("NaN"),
2869                    numeric("Infinity"),
2870                    Datum::Null,
2871                ],
2872            ),
2873            (
2874                AggregateFunc::SumInterval,
2875                vec![
2876                    Datum::Interval(Interval::new(-13, 40, 1_234_567)),
2877                    Datum::Interval(Interval::new(i32::MAX, i32::MIN, i64::MAX)),
2878                    Datum::Null,
2879                ],
2880            ),
2881        ];
2882        let mut accums = Vec::new();
2883        for (func, datums) in cases {
2884            let mut sum = accumulable_zero(&func);
2885            accums.push(sum);
2886            for datum in datums {
2887                let accum = datum_to_accumulator(&func, datum);
2888                sum.plus_equals(&accum);
2889                accums.push(accum);
2890                accums.push(accum.multiply(&Diff::from(-1_i64)));
2891            }
2892            accums.push(sum);
2893        }
2894        accums
2895    }
2896
2897    #[mz_ore::test]
2898    fn accum_columnar_round_trip() {
2899        use columnar::bytes::indexed::{DecodedStore, encode};
2900        use columnar::{AsBytes, Borrow, BorrowedOf, FromBytes, Index, Len};
2901        use differential_dataflow::trace::implementations::BatchContainer;
2902
2903        let accums = sample_accums();
2904        let container = Accum::as_columns(accums.iter());
2905        assert_eq!(container.len(), accums.len());
2906        let borrowed = container.borrow();
2907        for (index, accum) in accums.iter().enumerate() {
2908            assert_eq!(Accum::into_owned(borrowed.get(index)), *accum);
2909        }
2910        for (i, a) in accums.iter().enumerate() {
2911            for (j, b) in accums.iter().enumerate() {
2912                assert_eq!(borrowed.get(i).cmp(&borrowed.get(j)), a.cmp(b));
2913            }
2914        }
2915
2916        let bytes: Vec<&[u8]> = borrowed.as_bytes().map(|(_align, bytes)| bytes).collect();
2917        let decoded = BorrowedOf::<Accum>::from_bytes(&mut bytes.into_iter());
2918        for (index, accum) in accums.iter().enumerate() {
2919            assert_eq!(Accum::into_owned(decoded.get(index)), *accum);
2920        }
2921        // NOTE: the `i128` columns cannot be `validate`d, see the `Overflowing<i128>` test in
2922        // `mz_ore`, so this only decodes.
2923        let mut words = Vec::new();
2924        encode(&mut words, &borrowed);
2925        let decoded = BorrowedOf::<Accum>::from_store(&DecodedStore::new(&words), &mut 0);
2926        for (index, accum) in accums.iter().enumerate() {
2927            assert_eq!(Accum::into_owned(decoded.get(index)), *accum);
2928        }
2929
2930        // The arrangement's diff container, holding whole `(Vec<Accum>, Diff)` diffs.
2931        let diffs: Vec<(Vec<Accum>, Diff)> = accums
2932            .chunks(3)
2933            .map(|chunk| (chunk.to_vec(), Diff::ONE))
2934            .collect();
2935        let mut coltainer = Coltainer::<(Vec<Accum>, Diff)>::default();
2936        for diff in &diffs {
2937            coltainer.push_own(diff);
2938        }
2939        assert_eq!(coltainer.len(), diffs.len());
2940        for (index, diff) in diffs.iter().enumerate() {
2941            assert_eq!(
2942                <Coltainer<(Vec<Accum>, Diff)>>::into_owned(coltainer.index(index)),
2943                *diff
2944            );
2945        }
2946        let mut sum = <Coltainer<(Vec<Accum>, Diff)>>::into_owned(coltainer.index(0));
2947        sum.plus_equals(&sum.clone().multiply(&Diff::from(-1_i64)));
2948        assert!(sum.is_zero());
2949    }
2950}