Skip to main content

mz_compute/render/
reduce.rs

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