Skip to main content

mz_compute/render/
reduce.rs

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