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