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