1use 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::implementations::BatchContainer;
25use differential_dataflow::trace::{Builder, Trace};
26use differential_dataflow::{Data, VecCollection};
27use itertools::Itertools;
28use mz_compute_types::dyncfgs::{ENABLE_COMPUTE_TEMPORAL_BUCKETING, TEMPORAL_BUCKETING_SUMMARY};
29use mz_compute_types::plan::ArrangementStrategy;
30use mz_compute_types::plan::reduce::{
31 AccumulablePlan, BasicPlan, BucketedPlan, HierarchicalPlan, KeyValPlan, LirAggregateExpr,
32 MonotonicPlan, ReducePlan, ReductionType, SingleBasicPlan, reduction_type,
33};
34use mz_compute_types::plan::scalar::LirScalarExpr;
35use mz_expr::{AggregateFunc, EvalError, SafeMfpPlan};
36use mz_ore::cast::CastLossy;
37use mz_repr::adt::numeric::{self, Numeric, NumericAgg};
38use mz_repr::fixed_length::ExtendDatums;
39use mz_repr::{Datum, DatumVec, Diff, Row, RowArena, SharedRow};
40use mz_timely_util::columnation::ColumnationChunker;
41use mz_timely_util::operator::CollectionExt;
42use num_traits::Float;
43use serde::{Deserialize, Serialize};
44use timely::Container;
45use timely::container::{CapacityContainerBuilder, PushInto};
46use tracing::warn;
47
48use crate::extensions::arrange::{ArrangementSize, KeyCollection, MzArrange};
49use crate::extensions::reduce::{ClearContainer, MzReduce};
50use crate::render::context::{CollectionBundle, Context};
51use crate::render::errors::DataflowErrorSer;
52use crate::render::errors::MaybeValidatingRow;
53use crate::render::reduce::monoids::{ReductionMonoid, get_monoid};
54use crate::render::{ArrangementFlavor, Pairer, RenderTimestamp};
55use crate::typedefs::{
56 ErrBatcher, ErrBuilder, KeyBatcher, RowErrBuilder, RowErrSpine, RowRowAgent, RowRowArrangement,
57 RowRowSpine, RowSpine, RowValSpine,
58};
59use mz_row_spine::{
60 DatumSeq, RowBatcher, RowBuilder, RowRowBatcher, RowRowBuilder, RowValBatcher, RowValBuilder,
61};
62
63impl<'scope, T: RenderTimestamp> Context<'scope, T> {
64 pub fn render_reduce(
67 &self,
68 input_key: Option<Vec<LirScalarExpr>>,
69 input: CollectionBundle<'scope, T>,
70 key_val_plan: KeyValPlan,
71 reduce_plan: ReducePlan,
72 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
73 temporal_bucketing_strategy: ArrangementStrategy,
74 ) -> CollectionBundle<'scope, T>
75 where
76 T: crate::render::MaybeBucketByTime,
77 {
78 input.scope().region_named("Reduce", |inner| {
79 let KeyValPlan {
80 mut key_plan,
81 mut val_plan,
82 } = key_val_plan;
83 let key_arity = key_plan.projection.len();
84 let mut datums = DatumVec::new();
85
86 let mut demand = Vec::new();
88 demand.extend(key_plan.demand());
89 demand.extend(val_plan.demand());
90 demand.sort();
91 demand.dedup();
92
93 let mut demand_map = BTreeMap::new();
95 for column in demand.iter() {
96 demand_map.insert(*column, demand_map.len());
97 }
98 let demand_map_len = demand_map.len();
99 key_plan.permute_fn(|c| demand_map[&c], demand_map_len);
100 val_plan.permute_fn(|c| demand_map[&c], demand_map_len);
101 let max_demand = demand.iter().max().map(|x| *x + 1).unwrap_or(0);
102 let skips = mz_compute_types::plan::reduce::convert_indexes_to_skips(demand);
103
104 let (key_val_input, err) = input
105 .enter_region(inner)
106 .flat_map::<_, ConsolidatingContainerBuilder<Vec<((Row, Row), T, Diff)>>, _>(
107 input_key.map(|k| (k, None)),
108 max_demand,
109 move |row_datums, time, diff, ok_session, err_session| {
110 let mut row_builder = SharedRow::get();
111 let temp_storage = RowArena::new();
112
113 let mut row_iter = row_datums.drain(..);
114 let mut datums_local = datums.borrow();
115 for skip in skips.iter() {
117 datums_local.push(row_iter.nth(*skip).unwrap());
118 }
119
120 let key = key_plan.evaluate_into(
122 &mut datums_local,
123 &temp_storage,
124 &mut row_builder,
125 );
126 let key = match key {
127 Err(e) => {
128 err_session.give((e.into(), time, diff));
129 return 1;
130 }
131 Ok(Some(key)) => key.clone(),
132 Ok(None) => panic!("Row expected as no predicate was used"),
133 };
134
135 datums_local.truncate(skips.len());
138 let val = val_plan.evaluate_into(
139 &mut datums_local,
140 &temp_storage,
141 &mut row_builder,
142 );
143 let val = match val {
144 Err(e) => {
145 err_session.give((e.into(), time, diff));
146 return 1;
147 }
148 Ok(Some(val)) => val.clone(),
149 Ok(None) => panic!("Row expected as no predicate was used"),
150 };
151
152 ok_session.give(((key, val), time, diff));
153 1
154 },
155 );
156
157 let key_val_collection = key_val_input.as_collection();
162 let key_val_collection = if matches!(
163 temporal_bucketing_strategy,
164 ArrangementStrategy::TemporalBucketing
165 ) && ENABLE_COMPUTE_TEMPORAL_BUCKETING.get(&self.config_set)
166 {
167 let summary: mz_repr::Timestamp = TEMPORAL_BUCKETING_SUMMARY
168 .get(&self.config_set)
169 .try_into()
170 .expect("must fit");
171 T::maybe_apply_temporal_bucketing(
172 key_val_collection.inner,
173 self.as_of_frontier.clone(),
174 summary,
175 )
176 } else {
177 key_val_collection
178 };
179
180 self.render_reduce_plan(reduce_plan, key_val_collection, err, key_arity, mfp_after)
182 .leave_region(self.scope)
183 })
184 }
185
186 fn render_reduce_plan<'s>(
192 &self,
193 plan: ReducePlan,
194 collection: VecCollection<'s, T, (Row, Row), Diff>,
195 err_input: VecCollection<'s, T, DataflowErrorSer, Diff>,
196 key_arity: usize,
197 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
198 ) -> CollectionBundle<'s, T> {
199 let mut errors = Default::default();
200 let arrangement =
201 self.render_reduce_plan_inner(plan, collection, &mut errors, key_arity, mfp_after);
202 let errs: KeyCollection<_, _, _> = err_input.concatenate(errors).into();
203 CollectionBundle::from_columns(
204 0..key_arity,
205 ArrangementFlavor::Local(
206 arrangement,
207 errs.mz_arrange::<ColumnationChunker<_>, ErrBatcher<_, _>, ErrBuilder<_, _>, _>(
208 "Arrange bundle err",
209 ),
210 ),
211 )
212 }
213
214 fn render_reduce_plan_inner<'s>(
215 &self,
216 plan: ReducePlan,
217 collection: VecCollection<'s, T, (Row, Row), Diff>,
218 errors: &mut Vec<VecCollection<'s, T, DataflowErrorSer, Diff>>,
219 key_arity: usize,
220 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
221 ) -> Arranged<'s, RowRowAgent<T, Diff>> {
222 let arrangement = match plan {
225 ReducePlan::Distinct => {
228 let (arranged_output, errs) = self.build_distinct(collection, mfp_after);
229 errors.push(errs);
230 arranged_output
231 }
232 ReducePlan::Accumulable(expr) => {
233 let (arranged_output, errs) =
234 self.build_accumulable(collection, expr, key_arity, mfp_after);
235 errors.push(errs);
236 arranged_output
237 }
238 ReducePlan::Hierarchical(HierarchicalPlan::Monotonic(expr)) => {
239 let (output, errs) = self.build_monotonic(collection, expr, mfp_after);
240 errors.push(errs);
241 output
242 }
243 ReducePlan::Hierarchical(HierarchicalPlan::Bucketed(expr)) => {
244 let (output, errs) = self.build_bucketed(collection, expr, key_arity, mfp_after);
245 errors.push(errs);
246 output
247 }
248 ReducePlan::Basic(BasicPlan::Single(SingleBasicPlan {
249 expr,
250 fused_unnest_list,
251 })) => {
252 let validating = !fused_unnest_list;
256 let (output, errs) = self.build_basic_aggregate(
257 collection,
258 0,
259 &expr,
260 validating,
261 key_arity,
262 mfp_after,
263 fused_unnest_list,
264 );
265 if validating {
266 errors.push(errs.expect("validation should have occurred as it was requested"));
267 }
268 output
269 }
270 ReducePlan::Basic(BasicPlan::Multiple(aggrs)) => {
271 let (output, errs) =
272 self.build_basic_aggregates(collection, aggrs, key_arity, mfp_after);
273 errors.push(errs);
274 output
275 }
276 };
277 arrangement
278 }
279
280 fn build_distinct<'s>(
282 &self,
283 collection: VecCollection<'s, T, (Row, Row), Diff>,
284 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
285 ) -> (
286 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
287 VecCollection<'s, T, DataflowErrorSer, Diff>,
288 ) {
289 let error_logger = self.error_logger();
290
291 let mut datums1 = DatumVec::new();
293 let mut datums2 = DatumVec::new();
294 let mfp_after1 = mfp_after.clone();
295 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
296
297 let arranged = collection
298 .mz_arrange::<
299 ColumnationChunker<_>,
300 RowRowBatcher<_, _>,
301 RowRowBuilder<_, _>,
302 RowRowSpine<_, _>,
303 >(
304 "Arranged DistinctBy",
305 );
306 let output = arranged
307 .clone()
308 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>>(
309 "DistinctBy",
310 move |key, _input, output| {
311 let temp_storage = RowArena::new();
312 let mut datums_local = datums1.borrow();
313 key.extend_datums(&temp_storage, &mut datums_local, None);
314
315 if mfp_after1
319 .as_ref()
320 .map(|mfp| mfp.evaluate_inner(&mut datums_local, &temp_storage))
321 .unwrap_or(Ok(true))
322 == Ok(true)
323 {
324 output.push((Row::default(), Diff::ONE));
328 }
329 },
330 );
331 let errors = arranged.mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>>(
332 "DistinctByErrorCheck",
333 move |key, input: &[(_, Diff)], output: &mut Vec<(DataflowErrorSer, _)>| {
334 for (_, count) in input.iter() {
335 if count.is_positive() {
336 continue;
337 }
338 let message = "Non-positive multiplicity in DistinctBy";
339 error_logger.log(message, &format!("row={key:?}, count={count}"));
340 output.push((EvalError::Internal(message.into()).into(), Diff::ONE));
341 return;
342 }
343 let Some(mfp) = &mfp_after2 else { return };
345 let temp_storage = RowArena::new();
346 let mut datums_local = datums2.borrow();
347 key.extend_datums(&temp_storage, &mut datums_local, None);
348
349 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
350 output.push((e.into(), Diff::ONE));
351 }
352 },
353 );
354 (output, errors.as_collection(|_k, v| v.clone()))
355 }
356
357 fn build_basic_aggregates<'s>(
365 &self,
366 input: VecCollection<'s, T, (Row, Row), Diff>,
367 aggrs: Vec<LirAggregateExpr>,
368 key_arity: usize,
369 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
370 ) -> (
371 RowRowArrangement<'s, T>,
372 VecCollection<'s, T, DataflowErrorSer, Diff>,
373 ) {
374 if aggrs.len() <= 1 {
377 self.error_logger().soft_panic_or_log(
378 "Too few aggregations when building basic aggregates",
379 &format!("len={}", aggrs.len()),
380 )
381 }
382 let mut err_output = None;
383 let mut to_collect = Vec::new();
384 for (index, aggr) in aggrs.into_iter().enumerate() {
385 let (result, errs) = self.build_basic_aggregate(
386 input.clone(),
387 index,
388 &aggr,
389 err_output.is_none(),
390 key_arity,
391 None,
392 false,
393 );
394 if errs.is_some() {
395 err_output = errs
396 }
397 to_collect
398 .push(result.as_collection(move |key, val| (key.to_row(), (index, val.to_row()))));
399 }
400
401 let mut datums1 = DatumVec::new();
403 let mut datums2 = DatumVec::new();
404 let mfp_after1 = mfp_after.clone();
405 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
406
407 let arranged = differential_dataflow::collection::concatenate(input.scope(), to_collect)
408 .mz_arrange::<
409 ColumnationChunker<_>,
410 RowValBatcher<_, _, _>,
411 RowValBuilder<_, _, _>,
412 RowValSpine<_, _, _>,
413 >(
414 "Arranged ReduceFuseBasic input",
415 );
416
417 let output = arranged
418 .clone()
419 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>>("ReduceFuseBasic", {
420 move |key, input, output| {
421 let temp_storage = RowArena::new();
422 let mut datums_local = datums1.borrow();
423 key.extend_datums(&temp_storage, &mut datums_local, None);
424 let key_len = datums_local.len();
425
426 for ((_, row), _) in input.iter() {
427 datums_local.push(row.unpack_first());
428 }
429
430 if let Some(row) =
431 evaluate_mfp_after(&mfp_after1, &mut datums_local, &temp_storage, key_len)
432 {
433 output.push((row, Diff::ONE));
434 }
435 }
436 });
437 let validation_errs = err_output.expect("expected to validate in at least one aggregate");
442 if let Some(mfp) = mfp_after2 {
443 let mfp_errs = arranged
444 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>>(
445 "ReduceFuseBasic Error Check",
446 move |key, input, output| {
447 let temp_storage = RowArena::new();
450 let mut datums_local = datums2.borrow();
451 key.extend_datums(&temp_storage, &mut datums_local, None);
452
453 for ((_, row), _) in input.iter() {
454 datums_local.push(row.unpack_first());
455 }
456
457 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
458 output.push((e.into(), Diff::ONE));
459 }
460 },
461 )
462 .as_collection(|_, v| v.clone());
463 (output, validation_errs.concat(mfp_errs))
464 } else {
465 (output, validation_errs)
466 }
467 }
468
469 fn build_basic_aggregate<'s>(
473 &self,
474 input: VecCollection<'s, T, (Row, Row), Diff>,
475 index: usize,
476 aggr: &LirAggregateExpr,
477 validating: bool,
478 key_arity: usize,
479 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
480 fused_unnest_list: bool,
481 ) -> (
482 RowRowArrangement<'s, T>,
483 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
484 ) {
485 let LirAggregateExpr {
486 func,
487 expr: _,
488 distinct,
489 } = aggr.clone();
490
491 let mut partial = input.map(move |(key, row)| {
493 let mut row_builder = SharedRow::get();
494 let value = row.iter().nth(index).unwrap();
495 row_builder.packer().push(value);
496 (key, row_builder.clone())
497 });
498
499 let mut err_output = None;
500
501 if distinct {
503 let pairer = Pairer::new(key_arity);
505 let keyed = partial.map(move |(key, val)| pairer.merge(&key, &val));
506 if validating {
507 let (oks, errs) = self
508 .build_reduce_inaccumulable_distinct::<
509 RowValBuilder<Result<(), String>, _, _>,
510 RowValSpine<Result<(), String>, _, _>,
511 >(keyed, None)
512 .as_collection(|k, v| {
513 (
514 k.to_row(),
515 v.as_ref()
516 .map(|&()| ())
517 .map_err(|m| m.as_str().into()),
518 )
519 })
520 .map_fallible::<
521 CapacityContainerBuilder<_>,
522 CapacityContainerBuilder<_>,
523 _,
524 _,
525 _,
526 >(
527 "Demux Errors",
528 move |(key_val, result)| match result {
529 Ok(()) => Ok(pairer.split(&key_val)),
530 Err(m) => {
531 Err(EvalError::Internal(m).into())
532 }
533 },
534 );
535 err_output = Some(errs);
536 partial = oks;
537 } else {
538 partial = self
539 .build_reduce_inaccumulable_distinct::<RowBuilder<_, _>, RowSpine<_, _>>(
540 keyed,
541 Some(" [val: empty]"),
542 )
543 .as_collection(move |key_val_iter, _| pairer.split(key_val_iter));
544 }
545 }
546
547 let mut datums1 = DatumVec::new();
549 let mut datums2 = DatumVec::new();
550 let mut datums_key_1 = DatumVec::new();
551 let mut datums_key_2 = DatumVec::new();
552 let mut vals1 = DatumVec::new();
556 let mut vals2 = DatumVec::new();
557 let mut vals_key_1 = DatumVec::new();
558 let mut vals_key_2 = DatumVec::new();
559 let mfp_after1 = mfp_after.clone();
560 let func2 = func.clone();
561
562 let name = if !fused_unnest_list {
563 "ReduceInaccumulable"
564 } else {
565 "FusedReduceUnnestList"
566 };
567 let arranged = partial
568 .mz_arrange::<
569 ColumnationChunker<_>,
570 RowRowBatcher<_, _>,
571 RowRowBuilder<_, _>,
572 RowRowSpine<_, _>,
573 >(&format!(
574 "Arranged {name}"
575 ));
576 let oks = if !fused_unnest_list {
577 arranged
578 .clone()
579 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>>(name, {
580 move |key, source, target| {
581 let temp_storage = RowArena::new();
582 let mut val_scratch = vals1.borrow();
589 let iter = source.iter().map(|(v, w)| {
590 val_scratch.clear();
591 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
592 (val_scratch[0], *w)
593 });
594
595 let mut datums_local = datums1.borrow();
596 key.extend_datums(&temp_storage, &mut datums_local, None);
597 let key_len = datums_local.len();
598 datums_local.push(
599 func.eval_with_fast_window_agg::<_, window_agg_helpers::OneByOneAggrImpls>(
602 iter,
603 &temp_storage,
604 ),
605 );
606
607 if let Some(row) = evaluate_mfp_after(
608 &mfp_after1,
609 &mut datums_local,
610 &temp_storage,
611 key_len,
612 ) {
613 target.push((row, Diff::ONE));
614 }
615 }
616 })
617 } else {
618 arranged
619 .clone()
620 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>>(name, {
621 move |key, source, target| {
622 let temp_storage = RowArena::new();
624 let mut val_scratch = vals_key_1.borrow();
625 let iter = source.iter().map(|(v, w)| {
626 val_scratch.clear();
627 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
628 (val_scratch[0], *w)
629 });
630
631 let mut datums_local = datums_key_1.borrow();
633 key.extend_datums(&temp_storage, &mut datums_local, None);
634 let key_len = datums_local.len();
635 for datum in func
636 .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
637 iter,
638 &temp_storage,
639 )
640 {
641 datums_local.truncate(key_len);
642 datums_local.push(datum);
643 if let Some(row) = evaluate_mfp_after(
644 &mfp_after1,
645 &mut datums_local,
646 &temp_storage,
647 key_len,
648 ) {
649 target.push((row, Diff::ONE));
650 }
651 }
652 }
653 })
654 };
655
656 let must_validate = validating && err_output.is_none();
660 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
661 if must_validate || mfp_after2.is_some() {
662 let error_logger = self.error_logger();
663
664 let errs = if !fused_unnest_list {
665 arranged
666 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>>(
667 &format!("{name} Error Check"),
668 move |key, source, target| {
669 if must_validate {
673 for (value, count) in source.iter() {
674 if count.is_positive() {
675 continue;
676 }
677 let value = value.to_row();
678 let message =
679 "Non-positive accumulation in ReduceInaccumulable";
680 error_logger
681 .log(message, &format!("value={value:?}, count={count}"));
682 let err = EvalError::Internal(message.into());
683 target.push((err.into(), Diff::ONE));
684 return;
685 }
686 }
687
688 let Some(mfp) = &mfp_after2 else { return };
690 let temp_storage = RowArena::new();
691 let mut val_scratch = vals2.borrow();
692 let iter = source.iter().map(|(v, w)| {
693 val_scratch.clear();
694 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
695 (val_scratch[0], *w)
696 });
697
698 let mut datums_local = datums2.borrow();
699 key.extend_datums(&temp_storage, &mut datums_local, None);
700 datums_local.push(
701 func2.eval_with_fast_window_agg::<
702 _,
703 window_agg_helpers::OneByOneAggrImpls,
704 >(
705 iter, &temp_storage
706 ),
707 );
708 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
709 target.push((e.into(), Diff::ONE));
710 }
711 },
712 )
713 .as_collection(|_, v| v.clone())
714 } else {
715 assert!(!must_validate);
717 let Some(mfp) = mfp_after2 else {
720 unreachable!()
721 };
722 arranged
723 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>>(
724 &format!("{name} Error Check"),
725 move |key, source, target| {
726 let temp_storage = RowArena::new();
727 let mut val_scratch = vals_key_2.borrow();
728 let iter = source.iter().map(|(v, w)| {
729 val_scratch.clear();
730 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
731 (val_scratch[0], *w)
732 });
733
734 let mut datums_local = datums_key_2.borrow();
735 key.extend_datums(&temp_storage, &mut datums_local, None);
736 let key_len = datums_local.len();
737 for datum in func2
738 .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
739 iter,
740 &temp_storage,
741 )
742 {
743 datums_local.truncate(key_len);
744 datums_local.push(datum);
745 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
748 {
749 target.push((e.into(), Diff::ONE));
750 }
751 }
752 },
753 )
754 .as_collection(|_, v| v.clone())
755 };
756
757 if let Some(e) = err_output {
758 err_output = Some(e.concat(errs));
759 } else {
760 err_output = Some(errs);
761 }
762 }
763 (oks, err_output)
764 }
765
766 fn build_reduce_inaccumulable_distinct<'s, Bu, Tr>(
767 &self,
768 input: VecCollection<'s, T, Row, Diff>,
769 name_tag: Option<&str>,
770 ) -> Arranged<'s, TraceAgent<Tr>>
771 where
772 Tr: for<'a> Trace<
773 Key<'a> = DatumSeq<'a>,
774 KeyContainer: BatchContainer<Owned = Row>,
775 Time = T,
776 Diff = Diff,
777 ValOwn: Data + MaybeValidatingRow<(), String>,
778 > + 'static,
779 Bu: Builder<
780 Time = T,
781 Input: Container
782 + ClearContainer
783 + PushInto<((Row, Tr::ValOwn), Tr::Time, Tr::Diff)>,
784 Output = Tr::Batch,
785 >,
786 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
787 {
788 let error_logger = self.error_logger();
789
790 let output_name = format!(
791 "ReduceInaccumulable Distinct{}",
792 name_tag.unwrap_or_default()
793 );
794
795 let input: KeyCollection<_, _, _> = input.into();
796 input
797 .mz_arrange::<
798 ColumnationChunker<_>,
799 RowBatcher<_, _>,
800 RowBuilder<_, _>,
801 RowSpine<_, _>,
802 >(
803 "Arranged ReduceInaccumulable Distinct [val: empty]",
804 )
805 .mz_reduce_abelian::<_, Bu, Tr>(&output_name, move |_, source, t| {
806 if let Some(err) = Tr::ValOwn::into_error() {
807 for (value, count) in source.iter() {
808 if count.is_positive() {
809 continue;
810 }
811
812 let message = "Non-positive accumulation in ReduceInaccumulable DISTINCT";
813 error_logger.log(message, &format!("value={value:?}, count={count}"));
814 t.push((err(message.to_string()), Diff::ONE));
815 return;
816 }
817 }
818 t.push((Tr::ValOwn::ok(()), Diff::ONE))
819 })
820 }
821
822 fn build_bucketed<'s>(
840 &self,
841 input: VecCollection<'s, T, (Row, Row), Diff>,
842 BucketedPlan {
843 aggr_funcs,
844 buckets,
845 }: BucketedPlan,
846 key_arity: usize,
847 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
848 ) -> (
849 RowRowArrangement<'s, T>,
850 VecCollection<'s, T, DataflowErrorSer, Diff>,
851 ) {
852 let mut err_output: Option<VecCollection<'s, T, _, _>> = None;
853 let outer_scope = input.scope();
854 let arranged_output = outer_scope
855 .clone()
856 .region_named("ReduceHierarchical", |inner| {
857 let input = input.enter(inner);
858
859 let first_mod = buckets.get(0).copied().unwrap_or(1);
861 let aggregations = aggr_funcs.len();
862
863 let mut stage = input.map(move |(key, row)| {
865 let mut row_builder = SharedRow::get();
866 let mut row_packer = row_builder.packer();
867 row_packer.extend(row.iter().take(aggregations));
868 let values = row_builder.clone();
869
870 let hash = values.hashed() % first_mod;
872 let hash_key =
873 row_builder.pack_using(std::iter::once(Datum::from(hash)).chain(&key));
874 (hash_key, values)
875 });
876
877 for (index, b) in buckets.into_iter().enumerate() {
879 let input = if index == 0 {
881 stage
882 } else {
883 stage.map(move |(hash_key, values)| {
884 let mut hash_key_iter = hash_key.iter();
885 let hash = hash_key_iter.next().unwrap().unwrap_uint64() % b;
886 let hash_key = SharedRow::pack(
888 std::iter::once(Datum::from(hash))
889 .chain(hash_key_iter.take(key_arity)),
890 );
891 (hash_key, values)
892 })
893 };
894
895 let validating = err_output.is_none();
899
900 let (oks, errs) = self.build_bucketed_stage(&aggr_funcs, input, validating);
901 if let Some(errs) = errs {
902 err_output = Some(errs.leave_region(outer_scope));
903 }
904 stage = oks
905 }
906
907 let partial = stage.map(move |(hash_key, values)| {
909 let mut hash_key_iter = hash_key.iter();
910 let _hash = hash_key_iter.next();
911 (SharedRow::pack(hash_key_iter.take(key_arity)), values)
912 });
913
914 let mut datums1 = DatumVec::new();
916 let mut datums2 = DatumVec::new();
917 let mut vals1 = DatumVec::new();
921 let mut vals2 = DatumVec::new();
922 let mfp_after1 = mfp_after.clone();
923 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
924 let aggr_funcs2 = aggr_funcs.clone();
925
926 let error_logger = self.error_logger();
929 let arranged = partial
932 .mz_arrange::<
933 ColumnationChunker<_>,
934 RowRowBatcher<_, _>,
935 RowRowBuilder<_, _>,
936 RowRowSpine<_, _>,
937 >(
938 "Arrange ReduceMinsMaxes",
939 );
940 let must_validate = err_output.is_none();
944 if must_validate || mfp_after2.is_some() {
945 let errs = arranged
946 .clone()
947 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>>(
948 "ReduceMinsMaxes Error Check",
949 move |key, source, target| {
950 if must_validate {
954 for (val, count) in source.iter() {
955 if count.is_positive() {
956 continue;
957 }
958 let val = val.to_row();
959 let message =
960 "Non-positive accumulation in ReduceMinsMaxes";
961 error_logger
962 .log(message, &format!("val={val:?}, count={count}"));
963 target.push((
964 EvalError::Internal(message.into()).into(),
965 Diff::ONE,
966 ));
967 return;
968 }
969 }
970
971 let Some(mfp) = &mfp_after2 else { return };
973 let temp_storage = RowArena::new();
974 let mut datums_local = datums2.borrow();
975 key.extend_datums(&temp_storage, &mut datums_local, None);
976
977 let arity = aggr_funcs2.len();
982 let mut decoded = vals2.borrow();
983 for (values, _cnt) in source.iter() {
984 values.extend_datums(&temp_storage, &mut decoded, None);
985 }
986 assert_eq!(decoded.len(), source.len() * arity);
987 for (col, func) in aggr_funcs2.iter().enumerate() {
988 let column_iter = (0..source.len())
989 .map(|r| (decoded[r * arity + col], Diff::ONE));
990 datums_local.push(func.eval(column_iter, &temp_storage));
991 }
992 if let Result::Err(e) =
993 mfp.evaluate_inner(&mut datums_local, &temp_storage)
994 {
995 target.push((e.into(), Diff::ONE));
996 }
997 },
998 )
999 .as_collection(|_, v| v.clone())
1000 .leave_region(outer_scope);
1001 if let Some(e) = err_output.take() {
1002 err_output = Some(e.concat(errs));
1003 } else {
1004 err_output = Some(errs);
1005 }
1006 }
1007 arranged
1008 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>>(
1009 "ReduceMinsMaxes",
1010 move |key, source, target| {
1011 let temp_storage = RowArena::new();
1012 let mut datums_local = datums1.borrow();
1013 key.extend_datums(&temp_storage, &mut datums_local, None);
1014 let key_len = datums_local.len();
1015
1016 let arity = aggr_funcs.len();
1021 let mut decoded = vals1.borrow();
1022 for (values, _cnt) in source.iter() {
1023 values.extend_datums(&temp_storage, &mut decoded, None);
1024 }
1025 assert_eq!(decoded.len(), source.len() * arity);
1026 for (col, func) in aggr_funcs.iter().enumerate() {
1027 let column_iter = (0..source.len())
1028 .map(|r| (decoded[r * arity + col], Diff::ONE));
1029 datums_local.push(func.eval(column_iter, &temp_storage));
1030 }
1031
1032 if let Some(row) = evaluate_mfp_after(
1033 &mfp_after1,
1034 &mut datums_local,
1035 &temp_storage,
1036 key_len,
1037 ) {
1038 target.push((row, Diff::ONE));
1039 }
1040 },
1041 )
1042 .leave_region(outer_scope)
1043 });
1044 (
1045 arranged_output,
1046 err_output.expect("expected to validate in one level of the hierarchy"),
1047 )
1048 }
1049
1050 fn build_bucketed_stage<'s>(
1057 &self,
1058 aggr_funcs: &Vec<AggregateFunc>,
1059 input: VecCollection<'s, T, (Row, Row), Diff>,
1060 validating: bool,
1061 ) -> (
1062 VecCollection<'s, T, (Row, Row), Diff>,
1063 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
1064 ) {
1065 let (input, negated_output, errs) = if validating {
1066 let (input, reduced) = self
1067 .build_bucketed_negated_output::<
1068 RowValBuilder<_, _, _>,
1069 RowValSpine<Result<Row, Row>, _, _>,
1070 >(
1071 input.clone(),
1072 aggr_funcs.clone(),
1073 );
1074 let (oks, errs) = reduced
1075 .as_collection(|k, v| (k.to_row(), v.clone()))
1076 .map_fallible::<CapacityContainerBuilder<_>, CapacityContainerBuilder<_>, _, _, _>(
1077 "Checked Invalid Accumulations",
1078 |(hash_key, result)| match result {
1079 Err(hash_key) => {
1080 let mut hash_key_iter = hash_key.iter();
1081 let _hash = hash_key_iter.next();
1082 let key = SharedRow::pack(hash_key_iter);
1083 let message = format!(
1084 "Invalid data in source, saw non-positive accumulation \
1085 for key {key:?} in hierarchical mins-maxes aggregate"
1086 );
1087 Err(EvalError::Internal(message.into()).into())
1088 }
1089 Ok(values) => Ok((hash_key, values)),
1090 },
1091 );
1092 (input, oks, Some(errs))
1093 } else {
1094 let (input, reduced) = self
1095 .build_bucketed_negated_output::<RowRowBuilder<_, _>, RowRowSpine<_, _>>(
1096 input,
1097 aggr_funcs.clone(),
1098 );
1099 let oks = reduced.as_collection(|k, v| (k.to_row(), v.to_row()));
1102 (input, oks, None)
1103 };
1104
1105 let input = input.as_collection(|k, v| (k.to_row(), v.to_row()));
1106 let oks = negated_output.concat(input);
1107 (oks, errs)
1108 }
1109
1110 fn build_bucketed_negated_output<'s, Bu, Tr>(
1114 &self,
1115 input: VecCollection<'s, T, (Row, Row), Diff>,
1116 aggrs: Vec<AggregateFunc>,
1117 ) -> (
1118 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
1119 Arranged<'s, TraceAgent<Tr>>,
1120 )
1121 where
1122 Tr: for<'a> Trace<
1123 Key<'a> = DatumSeq<'a>,
1124 KeyContainer: BatchContainer<Owned = Row>,
1125 ValOwn: Data + MaybeValidatingRow<Row, Row>,
1126 Time = T,
1127 Diff = Diff,
1128 > + 'static,
1129 Bu: Builder<
1130 Time = T,
1131 Input: Container
1132 + ClearContainer
1133 + PushInto<((Row, Tr::ValOwn), Tr::Time, Tr::Diff)>,
1134 Output = Tr::Batch,
1135 >,
1136 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
1137 {
1138 let error_logger = self.error_logger();
1139 let arranged_input = input
1142 .mz_arrange::<
1143 ColumnationChunker<_>,
1144 RowRowBatcher<_, _>,
1145 RowRowBuilder<_, _>,
1146 RowRowSpine<_, _>,
1147 >(
1148 "Arranged MinsMaxesHierarchical input",
1149 );
1150
1151 let mut value_datums = DatumVec::new();
1154 let reduced = arranged_input.clone().mz_reduce_abelian::<_, Bu, Tr>(
1155 "Reduced Fallibly MinsMaxesHierarchical",
1156 move |key, source, target| {
1157 if let Some(err) = Tr::ValOwn::into_error() {
1158 for (value, count) in source.iter() {
1160 if count.is_positive() {
1161 continue;
1162 }
1163 error_logger.log(
1164 "Non-positive accumulation in MinsMaxesHierarchical",
1165 &format!("key={key:?}, value={value:?}, count={count}"),
1166 );
1167 target.push((
1170 err(<Tr::KeyContainer as BatchContainer>::into_owned(key)),
1171 Diff::ONE,
1172 ));
1173 return;
1174 }
1175 }
1176
1177 let temp_storage = RowArena::new();
1180 let arity = aggrs.len();
1181 let mut decoded = value_datums.borrow();
1182 for (values, _cnt) in source.iter() {
1183 values.extend_datums(&temp_storage, &mut decoded, None);
1184 }
1185 assert_eq!(decoded.len(), source.len() * arity);
1186
1187 let mut row_builder = SharedRow::get();
1188 let mut row_packer = row_builder.packer();
1189 for (col, func) in aggrs.iter().enumerate() {
1190 let column_iter =
1193 (0..source.len()).map(|r| (decoded[r * arity + col], Diff::ONE));
1194 row_packer.push(func.eval(column_iter, &temp_storage));
1195 }
1196 target.reserve(source.len().saturating_add(1));
1202 target.push((Tr::ValOwn::ok(row_builder.clone()), Diff::MINUS_ONE));
1203 target.extend(source.iter().map(|(values, cnt)| {
1204 let mut cnt = *cnt;
1205 cnt.negate();
1206 (Tr::ValOwn::ok(values.to_row()), cnt)
1207 }));
1208 },
1209 );
1210 (arranged_input, reduced)
1211 }
1212
1213 fn build_monotonic<'s>(
1216 &self,
1217 collection: VecCollection<'s, T, (Row, Row), Diff>,
1218 MonotonicPlan {
1219 aggr_funcs,
1220 must_consolidate,
1221 }: MonotonicPlan,
1222 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1223 ) -> (
1224 RowRowArrangement<'s, T>,
1225 VecCollection<'s, T, DataflowErrorSer, Diff>,
1226 ) {
1227 let aggregations = aggr_funcs.len();
1228 let collection = collection
1230 .map(move |(key, row)| {
1231 let mut row_builder = SharedRow::get();
1232 let mut values = Vec::with_capacity(aggregations);
1233 values.extend(
1234 row.iter()
1235 .take(aggregations)
1236 .map(|v| row_builder.pack_using(std::iter::once(v))),
1237 );
1238
1239 (key, values)
1240 })
1241 .consolidate_named_if::<KeyBatcher<_, _, _>>(
1242 must_consolidate,
1243 "Consolidated ReduceMonotonic input",
1244 );
1245
1246 let error_logger = self.error_logger();
1248 let (partial, validation_errs) = collection.ensure_monotonic(move |data, diff| {
1249 error_logger.log(
1250 "Non-monotonic input to ReduceMonotonic",
1251 &format!("data={data:?}, diff={diff}"),
1252 );
1253 let m = "tried to build a monotonic reduction on non-monotonic input".into();
1254 (EvalError::Internal(m).into(), Diff::ONE)
1255 });
1256 let partial = partial.explode_one(move |(key, values)| {
1260 let mut output = Vec::new();
1261 for (row, func) in values.into_iter().zip_eq(aggr_funcs.iter()) {
1262 output.push(monoids::get_monoid(row, func).expect(
1263 "hierarchical aggregations are expected to have monoid implementations",
1264 ));
1265 }
1266 (key, output)
1267 });
1268
1269 let mut datums1 = DatumVec::new();
1271 let mut datums2 = DatumVec::new();
1272 let mfp_after1 = mfp_after.clone();
1273 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1274
1275 let partial: KeyCollection<_, _, _> = partial.into();
1276 let arranged = partial
1277 .mz_arrange::<
1278 ColumnationChunker<_>,
1279 RowBatcher<_, _>,
1280 RowBuilder<_, _>,
1281 RowSpine<_, Vec<ReductionMonoid>>,
1282 >(
1283 "ArrangeMonotonic [val: empty]",
1284 );
1285 let output = arranged
1286 .clone()
1287 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>>("ReduceMonotonic", {
1288 move |key, input, output| {
1289 let temp_storage = RowArena::new();
1290 let mut datums_local = datums1.borrow();
1291 key.extend_datums(&temp_storage, &mut datums_local, None);
1292 let key_len = datums_local.len();
1293 let accum = &input[0].1;
1294 for monoid in accum.iter() {
1295 datums_local.extend(monoid.finalize().iter());
1296 }
1297
1298 if let Some(row) =
1299 evaluate_mfp_after(&mfp_after1, &mut datums_local, &temp_storage, key_len)
1300 {
1301 output.push((row, Diff::ONE));
1302 }
1303 }
1304 });
1305
1306 if let Some(mfp) = mfp_after2 {
1311 let mfp_errs = arranged
1312 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>>(
1313 "ReduceMonotonic Error Check",
1314 move |key, input, output| {
1315 let temp_storage = RowArena::new();
1316 let mut datums_local = datums2.borrow();
1317 key.extend_datums(&temp_storage, &mut datums_local, None);
1318 let accum = &input[0].1;
1319 for monoid in accum.iter() {
1320 datums_local.extend(monoid.finalize().iter());
1321 }
1322 if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
1323 {
1324 output.push((e.into(), Diff::ONE));
1325 }
1326 },
1327 )
1328 .as_collection(|_k, v| v.clone());
1329 (output, validation_errs.concat(mfp_errs))
1330 } else {
1331 (output, validation_errs)
1332 }
1333 }
1334
1335 fn build_accumulable<'s>(
1342 &self,
1343 collection: VecCollection<'s, T, (Row, Row), Diff>,
1344 AccumulablePlan {
1345 full_aggrs,
1346 simple_aggrs,
1347 distinct_aggrs,
1348 }: AccumulablePlan,
1349 key_arity: usize,
1350 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1351 ) -> (
1352 RowRowArrangement<'s, T>,
1353 VecCollection<'s, T, DataflowErrorSer, Diff>,
1354 ) {
1355 let collection_scope = collection.scope();
1356
1357 if full_aggrs.len() == 0 || simple_aggrs.len() + distinct_aggrs.len() != full_aggrs.len() {
1359 self.error_logger().soft_panic_or_log(
1360 "Incorrect numbers of aggregates in accummulable reduction rendering",
1361 &format!(
1362 "full_aggrs={}, simple_aggrs={}, distinct_aggrs={}",
1363 full_aggrs.len(),
1364 simple_aggrs.len(),
1365 distinct_aggrs.len(),
1366 ),
1367 );
1368 }
1369
1370 let zero_diffs: (Vec<_>, Diff) = (
1382 full_aggrs
1383 .iter()
1384 .map(|f| accumulable_zero(&f.func))
1385 .collect(),
1386 Diff::ZERO,
1387 );
1388
1389 let mut to_aggregate = Vec::new();
1390 if simple_aggrs.len() > 0 {
1391 let collection = collection.clone();
1393 let easy_cases = collection.explode_one({
1394 let zero_diffs = zero_diffs.clone();
1395 move |(key, row)| {
1396 let mut diffs = zero_diffs.clone();
1397 let mut row_iter = row.iter().enumerate();
1403 for (datum_index, aggr) in simple_aggrs.iter() {
1404 let mut datum = row_iter.next().unwrap();
1405 while datum_index != &datum.0 {
1406 datum = row_iter.next().unwrap();
1407 }
1408 let datum = datum.1;
1409 diffs.0[*datum_index] = datum_to_accumulator(&aggr.func, datum);
1410 diffs.1 = Diff::ONE;
1411 }
1412 ((key, ()), diffs)
1413 }
1414 });
1415 to_aggregate.push(easy_cases);
1416 }
1417
1418 for (datum_index, aggr) in distinct_aggrs.into_iter() {
1420 let pairer = Pairer::new(key_arity);
1421 let collection = collection
1422 .clone()
1423 .map(move |(key, row)| {
1424 let value = row.iter().nth(datum_index).unwrap();
1425 (pairer.merge(&key, std::iter::once(value)), ())
1426 })
1427 .mz_arrange::<
1428 ColumnationChunker<_>,
1429 RowBatcher<_, _>,
1430 RowBuilder<_, _>,
1431 RowSpine<_, _>,
1432 >(
1433 "Arranged Accumulable Distinct [val: empty]",
1434 )
1435 .mz_reduce_abelian::<_, RowBuilder<_, _>, RowSpine<_, _>>(
1436 "Reduced Accumulable Distinct [val: empty]",
1437 move |_k, _s, t| t.push(((), Diff::ONE)),
1438 )
1439 .as_collection(move |key_val_iter, _| pairer.split(key_val_iter))
1440 .explode_one({
1441 let zero_diffs = zero_diffs.clone();
1442 move |(key, row)| {
1443 let datum = row.iter().next().unwrap();
1444 let mut diffs = zero_diffs.clone();
1445 diffs.0[datum_index] = datum_to_accumulator(&aggr.func, datum);
1446 diffs.1 = Diff::ONE;
1447 ((key, ()), diffs)
1448 }
1449 });
1450 to_aggregate.push(collection);
1451 }
1452
1453 let collection = if to_aggregate.len() == 1 {
1455 to_aggregate.remove(0)
1456 } else {
1457 differential_dataflow::collection::concatenate(collection_scope, to_aggregate)
1458 };
1459
1460 let mut datums1 = DatumVec::new();
1462 let mut datums2 = DatumVec::new();
1463 let mfp_after1 = mfp_after.clone();
1464 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1465 let full_aggrs2 = full_aggrs.clone();
1466
1467 let error_logger = self.error_logger();
1468 let err_full_aggrs = full_aggrs.clone();
1469 let arranged = collection
1470 .mz_arrange::<
1471 ColumnationChunker<_>,
1472 RowBatcher<_, _>,
1473 RowBuilder<_, _>,
1474 RowSpine<_, (Vec<Accum>, Diff)>,
1475 >(
1476 "ArrangeAccumulable [val: empty]",
1477 );
1478 let arranged_output = arranged
1479 .clone()
1480 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>>("ReduceAccumulable", {
1481 move |key, input, output| {
1482 let (ref accums, total) = input[0].1;
1483
1484 let temp_storage = RowArena::new();
1485 let mut datums_local = datums1.borrow();
1486 key.extend_datums(&temp_storage, &mut datums_local, None);
1487 let key_len = datums_local.len();
1488 for (aggr, accum) in full_aggrs.iter().zip_eq(accums) {
1489 datums_local.push(finalize_accum(&aggr.func, accum, total));
1490 }
1491
1492 if let Some(row) =
1493 evaluate_mfp_after(&mfp_after1, &mut datums_local, &temp_storage, key_len)
1494 {
1495 output.push((row, Diff::ONE));
1496 }
1497 }
1498 });
1499 let arranged_errs = arranged
1500 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>>(
1501 "AccumulableErrorCheck",
1502 move |key, input, output| {
1503 let (ref accums, total) = input[0].1;
1504 for (aggr, accum) in err_full_aggrs.iter().zip_eq(accums) {
1505 if total == Diff::ZERO && !accum.is_zero() {
1508 error_logger.log(
1509 "Net-zero records with non-zero accumulation in ReduceAccumulable",
1510 &format!("aggr={aggr:?}, accum={accum:?}"),
1511 );
1512 let key = key.to_row();
1513 let message = format!(
1514 "Invalid data in source, saw net-zero records for key {key} \
1515 with non-zero accumulation in accumulable aggregate"
1516 );
1517 output.push((EvalError::Internal(message.into()).into(), Diff::ONE));
1518 }
1519 match (&aggr.func, &accum) {
1520 (AggregateFunc::SumUInt16, Accum::SimpleNumber { accum, .. })
1521 | (AggregateFunc::SumUInt32, Accum::SimpleNumber { accum, .. })
1522 | (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1523 if accum.is_negative() {
1524 error_logger.log(
1525 "Invalid negative unsigned aggregation in ReduceAccumulable",
1526 &format!("aggr={aggr:?}, accum={accum:?}"),
1527 );
1528 let key = key.to_row();
1529 let message = format!(
1530 "Invalid data in source, saw negative accumulation with \
1531 unsigned type for key {key}"
1532 );
1533 let err = EvalError::Internal(message.into());
1534 output.push((err.into(), Diff::ONE));
1535 }
1536 }
1537 _ => (), }
1539 }
1540
1541 let Some(mfp) = &mfp_after2 else { return };
1543 let temp_storage = RowArena::new();
1544 let mut datums_local = datums2.borrow();
1545 key.extend_datums(&temp_storage, &mut datums_local, None);
1546 for (aggr, accum) in full_aggrs2.iter().zip_eq(accums) {
1547 datums_local.push(finalize_accum(&aggr.func, accum, total));
1548 }
1549
1550 if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
1551 output.push((e.into(), Diff::ONE));
1552 }
1553 },
1554 );
1555 (
1556 arranged_output,
1557 arranged_errs.as_collection(|_key, error| error.clone()),
1558 )
1559 }
1560}
1561
1562fn evaluate_mfp_after<'a, 'b>(
1566 mfp_after: &'a Option<SafeMfpPlan<LirScalarExpr>>,
1567 datums_local: &'b mut mz_repr::DatumVecBorrow<'a>,
1568 temp_storage: &'a RowArena,
1569 key_len: usize,
1570) -> Option<Row> {
1571 let mut row_builder = SharedRow::get();
1572 if let Some(mfp) = mfp_after {
1575 if let Ok(Some(iter)) = mfp.evaluate_iter(datums_local, temp_storage) {
1578 Some(row_builder.pack_using(iter.skip(key_len)))
1581 } else {
1582 None
1583 }
1584 } else {
1585 Some(row_builder.pack_using(&datums_local[key_len..]))
1586 }
1587}
1588
1589fn accumulable_zero(aggr_func: &AggregateFunc) -> Accum {
1590 match aggr_func {
1591 AggregateFunc::Any | AggregateFunc::All => Accum::Bool {
1592 trues: Diff::ZERO,
1593 falses: Diff::ZERO,
1594 },
1595 AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => Accum::Float {
1596 accum: AccumCount::ZERO,
1597 pos_infs: Diff::ZERO,
1598 neg_infs: Diff::ZERO,
1599 nans: Diff::ZERO,
1600 non_nulls: Diff::ZERO,
1601 },
1602 AggregateFunc::SumNumeric => Accum::Numeric {
1603 accum: OrderedDecimal(NumericAgg::zero()),
1604 pos_infs: Diff::ZERO,
1605 neg_infs: Diff::ZERO,
1606 nans: Diff::ZERO,
1607 non_nulls: Diff::ZERO,
1608 },
1609 _ => Accum::SimpleNumber {
1610 accum: AccumCount::ZERO,
1611 non_nulls: Diff::ZERO,
1612 },
1613 }
1614}
1615
1616const FLOAT_SCALE_EXP: u32 = 24;
1620
1621#[allow(clippy::as_conversions)] const FLOAT_SCALE: f64 = (1_u64 << FLOAT_SCALE_EXP) as f64;
1624
1625fn float_to_fixed_point(n: f64) -> i128 {
1642 debug_assert!(n.is_finite());
1643
1644 let (mantissa, exponent, sign) = Float::integer_decode(n);
1648 let significand = u128::from(mantissa);
1649 let exp = i64::from(exponent) + i64::from(FLOAT_SCALE_EXP);
1650
1651 let magnitude: u128 = if exp >= 0 {
1652 match u32::try_from(exp) {
1655 Ok(shift) if shift < 128 => significand << shift,
1656 _ => 0,
1657 }
1658 } else {
1659 match u32::try_from(-exp) {
1662 Ok(shift) if shift < 128 => significand >> shift,
1663 _ => 0,
1664 }
1665 };
1666
1667 let magnitude = magnitude.cast_signed();
1670 if sign < 0 {
1671 magnitude.wrapping_neg()
1672 } else {
1673 magnitude
1674 }
1675}
1676
1677fn datum_to_accumulator(aggregate_func: &AggregateFunc, datum: Datum) -> Accum {
1678 match aggregate_func {
1679 AggregateFunc::Count => Accum::SimpleNumber {
1680 accum: AccumCount::ZERO, non_nulls: if datum.is_null() {
1682 Diff::ZERO
1683 } else {
1684 Diff::ONE
1685 },
1686 },
1687 AggregateFunc::Any | AggregateFunc::All => match datum {
1688 Datum::True => Accum::Bool {
1689 trues: Diff::ONE,
1690 falses: Diff::ZERO,
1691 },
1692 Datum::Null => Accum::Bool {
1693 trues: Diff::ZERO,
1694 falses: Diff::ZERO,
1695 },
1696 Datum::False => Accum::Bool {
1697 trues: Diff::ZERO,
1698 falses: Diff::ONE,
1699 },
1700 x => panic!("Invalid argument to AggregateFunc::Any: {x:?}"),
1701 },
1702 AggregateFunc::Dummy => match datum {
1703 Datum::Dummy => Accum::SimpleNumber {
1704 accum: AccumCount::ZERO,
1705 non_nulls: Diff::ZERO,
1706 },
1707 x => panic!("Invalid argument to AggregateFunc::Dummy: {x:?}"),
1708 },
1709 AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => {
1710 let n = match datum {
1711 Datum::Float32(n) => f64::from(*n),
1712 Datum::Float64(n) => *n,
1713 Datum::Null => 0f64,
1714 x => panic!("Invalid argument to AggregateFunc::{aggregate_func:?}: {x:?}"),
1715 };
1716
1717 let nans = Diff::from(n.is_nan());
1718 let pos_infs = Diff::from(n == f64::INFINITY);
1719 let neg_infs = Diff::from(n == f64::NEG_INFINITY);
1720 let non_nulls = Diff::from(datum != Datum::Null);
1721
1722 let accum = if nans.is_positive() || pos_infs.is_positive() || neg_infs.is_positive() {
1725 AccumCount::ZERO
1726 } else {
1727 float_to_fixed_point(n).into()
1731 };
1732
1733 Accum::Float {
1734 accum,
1735 pos_infs,
1736 neg_infs,
1737 nans,
1738 non_nulls,
1739 }
1740 }
1741 AggregateFunc::SumNumeric => match datum {
1742 Datum::Numeric(n) => {
1743 let (accum, pos_infs, neg_infs, nans) = if n.0.is_infinite() {
1744 if n.0.is_negative() {
1745 (NumericAgg::zero(), Diff::ZERO, Diff::ONE, Diff::ZERO)
1746 } else {
1747 (NumericAgg::zero(), Diff::ONE, Diff::ZERO, Diff::ZERO)
1748 }
1749 } else if n.0.is_nan() {
1750 (NumericAgg::zero(), Diff::ZERO, Diff::ZERO, Diff::ONE)
1751 } else {
1752 let mut cx_agg = numeric::cx_agg();
1755 (cx_agg.to_width(n.0), Diff::ZERO, Diff::ZERO, Diff::ZERO)
1756 };
1757
1758 Accum::Numeric {
1759 accum: OrderedDecimal(accum),
1760 pos_infs,
1761 neg_infs,
1762 nans,
1763 non_nulls: Diff::ONE,
1764 }
1765 }
1766 Datum::Null => Accum::Numeric {
1767 accum: OrderedDecimal(NumericAgg::zero()),
1768 pos_infs: Diff::ZERO,
1769 neg_infs: Diff::ZERO,
1770 nans: Diff::ZERO,
1771 non_nulls: Diff::ZERO,
1772 },
1773 x => panic!("Invalid argument to AggregateFunc::SumNumeric: {x:?}"),
1774 },
1775 _ => {
1776 match datum {
1780 Datum::Int16(i) => Accum::SimpleNumber {
1781 accum: i.into(),
1782 non_nulls: Diff::ONE,
1783 },
1784 Datum::Int32(i) => Accum::SimpleNumber {
1785 accum: i.into(),
1786 non_nulls: Diff::ONE,
1787 },
1788 Datum::Int64(i) => Accum::SimpleNumber {
1789 accum: i.into(),
1790 non_nulls: Diff::ONE,
1791 },
1792 Datum::UInt16(u) => Accum::SimpleNumber {
1793 accum: u.into(),
1794 non_nulls: Diff::ONE,
1795 },
1796 Datum::UInt32(u) => Accum::SimpleNumber {
1797 accum: u.into(),
1798 non_nulls: Diff::ONE,
1799 },
1800 Datum::UInt64(u) => Accum::SimpleNumber {
1801 accum: u.into(),
1802 non_nulls: Diff::ONE,
1803 },
1804 Datum::MzTimestamp(t) => Accum::SimpleNumber {
1805 accum: u64::from(t).into(),
1806 non_nulls: Diff::ONE,
1807 },
1808 Datum::Null => Accum::SimpleNumber {
1809 accum: AccumCount::ZERO,
1810 non_nulls: Diff::ZERO,
1811 },
1812 x => panic!("Accumulating non-integer data: {x:?}"),
1813 }
1814 }
1815 }
1816}
1817
1818fn finalize_accum<'a>(aggr_func: &'a AggregateFunc, accum: &'a Accum, total: Diff) -> Datum<'a> {
1819 if total.is_positive() && accum.is_zero() && *aggr_func != AggregateFunc::Count {
1823 Datum::Null
1824 } else {
1825 match (&aggr_func, &accum) {
1826 (AggregateFunc::Count, Accum::SimpleNumber { non_nulls, .. }) => {
1827 Datum::Int64(non_nulls.into_inner())
1828 }
1829 (AggregateFunc::All, Accum::Bool { falses, trues }) => {
1830 if falses.is_positive() {
1832 Datum::False
1833 } else if *trues == total {
1834 Datum::True
1835 } else {
1836 Datum::Null
1837 }
1838 }
1839 (AggregateFunc::Any, Accum::Bool { falses, trues }) => {
1840 if trues.is_positive() {
1842 Datum::True
1843 } else if *falses == total {
1844 Datum::False
1845 } else {
1846 Datum::Null
1847 }
1848 }
1849 (AggregateFunc::Dummy, _) => Datum::Dummy,
1850 (AggregateFunc::SumInt16, Accum::SimpleNumber { accum, .. })
1852 | (AggregateFunc::SumInt32, Accum::SimpleNumber { accum, .. }) => {
1853 #[allow(clippy::as_conversions)]
1858 Datum::Int64(accum.into_inner() as i64)
1859 }
1860 (AggregateFunc::SumInt64, Accum::SimpleNumber { accum, .. }) => Datum::from(*accum),
1861 (AggregateFunc::SumUInt16, Accum::SimpleNumber { accum, .. })
1862 | (AggregateFunc::SumUInt32, Accum::SimpleNumber { accum, .. }) => {
1863 if !accum.is_negative() {
1864 #[allow(clippy::as_conversions)]
1870 Datum::UInt64(accum.into_inner() as u64)
1871 } else {
1872 Datum::Null
1876 }
1877 }
1878 (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1879 if !accum.is_negative() {
1880 Datum::from(*accum)
1881 } else {
1882 Datum::Null
1886 }
1887 }
1888 (
1889 AggregateFunc::SumFloat32,
1890 Accum::Float {
1891 accum,
1892 pos_infs,
1893 neg_infs,
1894 nans,
1895 non_nulls: _,
1896 },
1897 ) => {
1898 if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
1899 Datum::from(f32::NAN)
1902 } else if pos_infs.is_positive() {
1903 Datum::from(f32::INFINITY)
1904 } else if neg_infs.is_positive() {
1905 Datum::from(f32::NEG_INFINITY)
1906 } else {
1907 let sum = f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE;
1908 Datum::from(f32::cast_lossy(sum))
1909 }
1910 }
1911 (
1912 AggregateFunc::SumFloat64,
1913 Accum::Float {
1914 accum,
1915 pos_infs,
1916 neg_infs,
1917 nans,
1918 non_nulls: _,
1919 },
1920 ) => {
1921 if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
1922 Datum::from(f64::NAN)
1925 } else if pos_infs.is_positive() {
1926 Datum::from(f64::INFINITY)
1927 } else if neg_infs.is_positive() {
1928 Datum::from(f64::NEG_INFINITY)
1929 } else {
1930 Datum::from(f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE)
1931 }
1932 }
1933 (
1934 AggregateFunc::SumNumeric,
1935 Accum::Numeric {
1936 accum,
1937 pos_infs,
1938 neg_infs,
1939 nans,
1940 non_nulls: _,
1941 },
1942 ) => {
1943 let mut cx_datum = numeric::cx_datum();
1944 let d = cx_datum.to_width(accum.0);
1945 let inf_d = d.is_infinite();
1951 let neg_d = d.is_negative();
1952 let pos_inf = pos_infs.is_positive() || (inf_d && !neg_d);
1953 let neg_inf = neg_infs.is_positive() || (inf_d && neg_d);
1954 if nans.is_positive() || (pos_inf && neg_inf) {
1955 Datum::from(Numeric::nan())
1958 } else if pos_inf {
1959 Datum::from(Numeric::infinity())
1960 } else if neg_inf {
1961 let mut cx = numeric::cx_datum();
1962 let mut d = Numeric::infinity();
1963 cx.neg(&mut d);
1964 Datum::from(d)
1965 } else {
1966 Datum::from(d)
1967 }
1968 }
1969 _ => panic!(
1970 "Unexpected accumulation (aggr={:?}, accum={accum:?})",
1971 aggr_func
1972 ),
1973 }
1974 }
1975}
1976
1977type AccumCount = mz_ore::Overflowing<i128>;
1979
1980#[derive(
1991 Debug,
1992 Clone,
1993 Copy,
1994 PartialEq,
1995 Eq,
1996 PartialOrd,
1997 Ord,
1998 Serialize,
1999 Deserialize
2000)]
2001enum Accum {
2002 Bool {
2004 trues: Diff,
2006 falses: Diff,
2008 },
2009 SimpleNumber {
2011 accum: AccumCount,
2013 non_nulls: Diff,
2015 },
2016 Float {
2018 accum: AccumCount,
2021 pos_infs: Diff,
2023 neg_infs: Diff,
2025 nans: Diff,
2027 non_nulls: Diff,
2029 },
2030 Numeric {
2032 accum: OrderedDecimal<NumericAgg>,
2034 pos_infs: Diff,
2036 neg_infs: Diff,
2038 nans: Diff,
2040 non_nulls: Diff,
2042 },
2043}
2044
2045impl IsZero for Accum {
2046 fn is_zero(&self) -> bool {
2047 match self {
2048 Accum::Bool { trues, falses } => trues.is_zero() && falses.is_zero(),
2049 Accum::SimpleNumber { accum, non_nulls } => accum.is_zero() && non_nulls.is_zero(),
2050 Accum::Float {
2051 accum,
2052 pos_infs,
2053 neg_infs,
2054 nans,
2055 non_nulls,
2056 } => {
2057 accum.is_zero()
2058 && pos_infs.is_zero()
2059 && neg_infs.is_zero()
2060 && nans.is_zero()
2061 && non_nulls.is_zero()
2062 }
2063 Accum::Numeric {
2064 accum,
2065 pos_infs,
2066 neg_infs,
2067 nans,
2068 non_nulls,
2069 } => {
2070 accum.0.is_zero()
2071 && pos_infs.is_zero()
2072 && neg_infs.is_zero()
2073 && nans.is_zero()
2074 && non_nulls.is_zero()
2075 }
2076 }
2077 }
2078}
2079
2080impl Semigroup for Accum {
2081 fn plus_equals(&mut self, other: &Accum) {
2082 match (&mut *self, other) {
2083 (
2084 Accum::Bool { trues, falses },
2085 Accum::Bool {
2086 trues: other_trues,
2087 falses: other_falses,
2088 },
2089 ) => {
2090 *trues += other_trues;
2091 *falses += other_falses;
2092 }
2093 (
2094 Accum::SimpleNumber { accum, non_nulls },
2095 Accum::SimpleNumber {
2096 accum: other_accum,
2097 non_nulls: other_non_nulls,
2098 },
2099 ) => {
2100 *accum += other_accum;
2101 *non_nulls += other_non_nulls;
2102 }
2103 (
2104 Accum::Float {
2105 accum,
2106 pos_infs,
2107 neg_infs,
2108 nans,
2109 non_nulls,
2110 },
2111 Accum::Float {
2112 accum: other_accum,
2113 pos_infs: other_pos_infs,
2114 neg_infs: other_neg_infs,
2115 nans: other_nans,
2116 non_nulls: other_non_nulls,
2117 },
2118 ) => {
2119 *accum = accum.checked_add(*other_accum).unwrap_or_else(|| {
2120 warn!("Float accumulator overflow. Incorrect results possible");
2121 accum.wrapping_add(*other_accum)
2122 });
2123 *pos_infs += other_pos_infs;
2124 *neg_infs += other_neg_infs;
2125 *nans += other_nans;
2126 *non_nulls += other_non_nulls;
2127 }
2128 (
2129 Accum::Numeric {
2130 accum,
2131 pos_infs,
2132 neg_infs,
2133 nans,
2134 non_nulls,
2135 },
2136 Accum::Numeric {
2137 accum: other_accum,
2138 pos_infs: other_pos_infs,
2139 neg_infs: other_neg_infs,
2140 nans: other_nans,
2141 non_nulls: other_non_nulls,
2142 },
2143 ) => {
2144 let mut cx_agg = numeric::cx_agg();
2145 cx_agg.add(&mut accum.0, &other_accum.0);
2146 assert!(!cx_agg.status().rounded(), "Accum::Numeric overflow");
2152 cx_agg.reduce(&mut accum.0);
2171 *pos_infs += other_pos_infs;
2172 *neg_infs += other_neg_infs;
2173 *nans += other_nans;
2174 *non_nulls += other_non_nulls;
2175 }
2176 (l, r) => unreachable!(
2177 "Accumulator::plus_equals called with non-matching variants: {l:?} vs {r:?}"
2178 ),
2179 }
2180 }
2181}
2182
2183impl Multiply<Diff> for Accum {
2184 type Output = Accum;
2185
2186 fn multiply(self, factor: &Diff) -> Accum {
2187 let factor = *factor;
2188 match self {
2189 Accum::Bool { trues, falses } => Accum::Bool {
2190 trues: trues * factor,
2191 falses: falses * factor,
2192 },
2193 Accum::SimpleNumber { accum, non_nulls } => Accum::SimpleNumber {
2194 accum: accum * AccumCount::from(factor),
2195 non_nulls: non_nulls * factor,
2196 },
2197 Accum::Float {
2198 accum,
2199 pos_infs,
2200 neg_infs,
2201 nans,
2202 non_nulls,
2203 } => Accum::Float {
2204 accum: accum
2205 .checked_mul(AccumCount::from(factor))
2206 .unwrap_or_else(|| {
2207 warn!("Float accumulator overflow. Incorrect results possible");
2208 accum.wrapping_mul(AccumCount::from(factor))
2209 }),
2210 pos_infs: pos_infs * factor,
2211 neg_infs: neg_infs * factor,
2212 nans: nans * factor,
2213 non_nulls: non_nulls * factor,
2214 },
2215 Accum::Numeric {
2216 accum,
2217 pos_infs,
2218 neg_infs,
2219 nans,
2220 non_nulls,
2221 } => {
2222 let mut cx = numeric::cx_agg();
2223 let mut f = NumericAgg::from(factor.into_inner());
2224 cx.mul(&mut f, &accum.0);
2228 assert!(!cx.status().rounded(), "Accum::Numeric multiply overflow");
2234 Accum::Numeric {
2235 accum: OrderedDecimal(f),
2236 pos_infs: pos_infs * factor,
2237 neg_infs: neg_infs * factor,
2238 nans: nans * factor,
2239 non_nulls: non_nulls * factor,
2240 }
2241 }
2242 }
2243 }
2244}
2245
2246impl Columnation for Accum {
2247 type InnerRegion = CopyRegion<Self>;
2248}
2249
2250mod monoids {
2252
2253 use columnation::{Columnation, Region};
2269 use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
2270 use mz_expr::AggregateFunc;
2271 use mz_ore::soft_panic_or_log;
2272 use mz_repr::{Datum, Diff, Row};
2273 use serde::{Deserialize, Serialize};
2274
2275 #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
2277 pub enum ReductionMonoid {
2278 Min(Row),
2279 Max(Row),
2280 }
2281
2282 impl ReductionMonoid {
2283 pub fn finalize(&self) -> &Row {
2284 use ReductionMonoid::*;
2285 match self {
2286 Min(row) | Max(row) => row,
2287 }
2288 }
2289 }
2290
2291 impl Clone for ReductionMonoid {
2292 fn clone(&self) -> Self {
2293 use ReductionMonoid::*;
2294 match self {
2295 Min(row) => Min(row.clone()),
2296 Max(row) => Max(row.clone()),
2297 }
2298 }
2299
2300 fn clone_from(&mut self, source: &Self) {
2301 use ReductionMonoid::*;
2302
2303 let mut row = std::mem::take(match self {
2304 Min(row) | Max(row) => row,
2305 });
2306
2307 let source_row = match source {
2308 Min(row) | Max(row) => row,
2309 };
2310
2311 row.clone_from(source_row);
2312
2313 match source {
2314 Min(_) => *self = Min(row),
2315 Max(_) => *self = Max(row),
2316 }
2317 }
2318 }
2319
2320 impl Multiply<Diff> for ReductionMonoid {
2321 type Output = Self;
2322
2323 fn multiply(self, factor: &Diff) -> Self {
2324 assert!(factor.is_positive());
2329 self
2330 }
2331 }
2332
2333 impl Semigroup for ReductionMonoid {
2334 fn plus_equals(&mut self, rhs: &Self) {
2335 match (self, rhs) {
2336 (ReductionMonoid::Min(lhs), ReductionMonoid::Min(rhs)) => {
2337 let swap = {
2338 let lhs_val = lhs.unpack_first();
2339 let rhs_val = rhs.unpack_first();
2340 match (lhs_val, rhs_val) {
2342 (_, Datum::Null) => false,
2343 (Datum::Null, _) => true,
2344 (lhs, rhs) => rhs < lhs,
2345 }
2346 };
2347 if swap {
2348 lhs.clone_from(rhs);
2349 }
2350 }
2351 (ReductionMonoid::Max(lhs), ReductionMonoid::Max(rhs)) => {
2352 let swap = {
2353 let lhs_val = lhs.unpack_first();
2354 let rhs_val = rhs.unpack_first();
2355 match (lhs_val, rhs_val) {
2357 (_, Datum::Null) => false,
2358 (Datum::Null, _) => true,
2359 (lhs, rhs) => rhs > lhs,
2360 }
2361 };
2362 if swap {
2363 lhs.clone_from(rhs);
2364 }
2365 }
2366 (lhs, rhs) => {
2367 soft_panic_or_log!(
2368 "Mismatched monoid variants in reduction! lhs: {lhs:?} rhs: {rhs:?}"
2369 );
2370 }
2371 }
2372 }
2373 }
2374
2375 impl IsZero for ReductionMonoid {
2376 fn is_zero(&self) -> bool {
2377 false
2383 }
2384 }
2385
2386 impl Columnation for ReductionMonoid {
2387 type InnerRegion = ReductionMonoidRegion;
2388 }
2389
2390 #[derive(Default)]
2394 pub struct ReductionMonoidRegion {
2395 inner: <Row as Columnation>::InnerRegion,
2396 }
2397
2398 impl Region for ReductionMonoidRegion {
2399 type Item = ReductionMonoid;
2400
2401 unsafe fn copy(&mut self, item: &Self::Item) -> Self::Item {
2402 use ReductionMonoid::*;
2403 match item {
2404 Min(row) => Min(unsafe { self.inner.copy(row) }),
2405 Max(row) => Max(unsafe { self.inner.copy(row) }),
2406 }
2407 }
2408
2409 fn clear(&mut self) {
2410 self.inner.clear();
2411 }
2412
2413 fn reserve_items<'a, I>(&mut self, items: I)
2414 where
2415 Self: 'a,
2416 I: Iterator<Item = &'a Self::Item> + Clone,
2417 {
2418 self.inner
2419 .reserve_items(items.map(ReductionMonoid::finalize));
2420 }
2421
2422 fn reserve_regions<'a, I>(&mut self, regions: I)
2423 where
2424 Self: 'a,
2425 I: Iterator<Item = &'a Self> + Clone,
2426 {
2427 self.inner.reserve_regions(regions.map(|r| &r.inner));
2428 }
2429
2430 fn heap_size(&self, callback: impl FnMut(usize, usize)) {
2431 self.inner.heap_size(callback);
2432 }
2433 }
2434
2435 pub fn get_monoid(row: Row, func: &AggregateFunc) -> Option<ReductionMonoid> {
2438 match func {
2439 AggregateFunc::MaxNumeric
2440 | AggregateFunc::MaxInt16
2441 | AggregateFunc::MaxInt32
2442 | AggregateFunc::MaxInt64
2443 | AggregateFunc::MaxUInt16
2444 | AggregateFunc::MaxUInt32
2445 | AggregateFunc::MaxUInt64
2446 | AggregateFunc::MaxMzTimestamp
2447 | AggregateFunc::MaxFloat32
2448 | AggregateFunc::MaxFloat64
2449 | AggregateFunc::MaxBool
2450 | AggregateFunc::MaxString
2451 | AggregateFunc::MaxDate
2452 | AggregateFunc::MaxTimestamp
2453 | AggregateFunc::MaxTimestampTz
2454 | AggregateFunc::MaxInterval
2455 | AggregateFunc::MaxTime => Some(ReductionMonoid::Max(row)),
2456 AggregateFunc::MinNumeric
2457 | AggregateFunc::MinInt16
2458 | AggregateFunc::MinInt32
2459 | AggregateFunc::MinInt64
2460 | AggregateFunc::MinUInt16
2461 | AggregateFunc::MinUInt32
2462 | AggregateFunc::MinUInt64
2463 | AggregateFunc::MinMzTimestamp
2464 | AggregateFunc::MinFloat32
2465 | AggregateFunc::MinFloat64
2466 | AggregateFunc::MinBool
2467 | AggregateFunc::MinString
2468 | AggregateFunc::MinDate
2469 | AggregateFunc::MinTimestamp
2470 | AggregateFunc::MinTimestampTz
2471 | AggregateFunc::MinInterval
2472 | AggregateFunc::MinTime => Some(ReductionMonoid::Min(row)),
2473 AggregateFunc::SumInt16
2474 | AggregateFunc::SumInt32
2475 | AggregateFunc::SumInt64
2476 | AggregateFunc::SumUInt16
2477 | AggregateFunc::SumUInt32
2478 | AggregateFunc::SumUInt64
2479 | AggregateFunc::SumFloat32
2480 | AggregateFunc::SumFloat64
2481 | AggregateFunc::SumNumeric
2482 | AggregateFunc::Count
2483 | AggregateFunc::Any
2484 | AggregateFunc::All
2485 | AggregateFunc::Dummy
2486 | AggregateFunc::JsonbAgg { .. }
2487 | AggregateFunc::JsonbObjectAgg { .. }
2488 | AggregateFunc::MapAgg { .. }
2489 | AggregateFunc::ArrayConcat { .. }
2490 | AggregateFunc::ListConcat { .. }
2491 | AggregateFunc::StringAgg { .. }
2492 | AggregateFunc::RowNumber { .. }
2493 | AggregateFunc::Rank { .. }
2494 | AggregateFunc::DenseRank { .. }
2495 | AggregateFunc::LagLead { .. }
2496 | AggregateFunc::FirstValue { .. }
2497 | AggregateFunc::LastValue { .. }
2498 | AggregateFunc::WindowAggregate { .. }
2499 | AggregateFunc::FusedValueWindowFunc { .. }
2500 | AggregateFunc::FusedWindowAggregate { .. } => None,
2501 }
2502 }
2503}
2504
2505mod window_agg_helpers {
2506 use crate::render::reduce::*;
2507
2508 pub enum OneByOneAggrImpls {
2513 Accumulable(AccumulableOneByOneAggr),
2514 Hierarchical(HierarchicalOneByOneAggr),
2515 Basic(mz_expr::NaiveOneByOneAggr),
2516 }
2517
2518 impl mz_expr::OneByOneAggr for OneByOneAggrImpls {
2519 fn new(agg: &AggregateFunc, reverse: bool) -> Self {
2520 match reduction_type(agg) {
2521 ReductionType::Basic => {
2522 OneByOneAggrImpls::Basic(mz_expr::NaiveOneByOneAggr::new(agg, reverse))
2523 }
2524 ReductionType::Accumulable => {
2525 OneByOneAggrImpls::Accumulable(AccumulableOneByOneAggr::new(agg))
2526 }
2527 ReductionType::Hierarchical => {
2528 OneByOneAggrImpls::Hierarchical(HierarchicalOneByOneAggr::new(agg))
2529 }
2530 }
2531 }
2532
2533 fn give(&mut self, d: &Datum) {
2534 match self {
2535 OneByOneAggrImpls::Basic(i) => i.give(d),
2536 OneByOneAggrImpls::Accumulable(i) => i.give(d),
2537 OneByOneAggrImpls::Hierarchical(i) => i.give(d),
2538 }
2539 }
2540
2541 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2542 match self {
2544 OneByOneAggrImpls::Basic(i) => i.get_current_aggregate(temp_storage),
2545 OneByOneAggrImpls::Accumulable(i) => i.get_current_aggregate(temp_storage),
2546 OneByOneAggrImpls::Hierarchical(i) => i.get_current_aggregate(temp_storage),
2547 }
2548 }
2549 }
2550
2551 pub struct AccumulableOneByOneAggr {
2552 aggr_func: AggregateFunc,
2553 accum: Accum,
2554 total: Diff,
2555 }
2556
2557 impl AccumulableOneByOneAggr {
2558 fn new(aggr_func: &AggregateFunc) -> Self {
2559 AccumulableOneByOneAggr {
2560 aggr_func: aggr_func.clone(),
2561 accum: accumulable_zero(aggr_func),
2562 total: Diff::ZERO,
2563 }
2564 }
2565
2566 fn give(&mut self, d: &Datum) {
2567 self.accum
2568 .plus_equals(&datum_to_accumulator(&self.aggr_func, d.clone()));
2569 self.total += Diff::ONE;
2570 }
2571
2572 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2573 temp_storage.make_datum(|packer| {
2574 packer.push(finalize_accum(&self.aggr_func, &self.accum, self.total));
2575 })
2576 }
2577 }
2578
2579 pub struct HierarchicalOneByOneAggr {
2580 aggr_func: AggregateFunc,
2581 monoid: ReductionMonoid,
2584 }
2585
2586 impl HierarchicalOneByOneAggr {
2587 fn new(aggr_func: &AggregateFunc) -> Self {
2588 let mut row_buf = Row::default();
2589 row_buf.packer().push(Datum::Null);
2590 HierarchicalOneByOneAggr {
2591 aggr_func: aggr_func.clone(),
2592 monoid: get_monoid(row_buf, aggr_func)
2593 .expect("aggr_func should be a hierarchical aggregation function"),
2594 }
2595 }
2596
2597 fn give(&mut self, d: &Datum) {
2598 let mut row_buf = Row::default();
2599 row_buf.packer().push(d);
2600 let m = get_monoid(row_buf, &self.aggr_func)
2601 .expect("aggr_func should be a hierarchical aggregation function");
2602 self.monoid.plus_equals(&m);
2603 }
2604
2605 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2606 temp_storage.make_datum(|packer| packer.extend(self.monoid.finalize().iter()))
2607 }
2608 }
2609}
2610
2611#[cfg(test)]
2612mod tests {
2613 use super::*;
2614
2615 #[allow(clippy::as_conversions)]
2619 fn saturating_convert(n: f64) -> i128 {
2620 (n * FLOAT_SCALE) as i128
2621 }
2622
2623 #[mz_ore::test]
2624 fn float_to_fixed_point_matches_saturating_in_range() {
2625 let cases = [
2629 0.0,
2630 -0.0,
2631 1.0,
2632 -1.0,
2633 0.1,
2634 -0.1,
2635 0.5,
2636 -0.5,
2637 3.25,
2638 -3.25,
2639 123456.789,
2640 -123456.789,
2641 1e10,
2642 -1e10,
2643 1e20,
2644 -1e20,
2645 5e30, -5e30,
2647 ];
2648 for n in cases {
2649 assert_eq!(
2650 float_to_fixed_point(n),
2651 saturating_convert(n),
2652 "mismatch for n = {n}"
2653 );
2654 }
2655 }
2656
2657 #[mz_ore::test]
2658 fn float_to_fixed_point_truncates_toward_zero() {
2659 assert_eq!(float_to_fixed_point(1.75), 29_360_128);
2661 assert_eq!(float_to_fixed_point(-1.75), -29_360_128);
2662
2663 let frac = 0.123_456_7_f64;
2665 assert_eq!(float_to_fixed_point(frac), saturating_convert(frac));
2666 assert_eq!(float_to_fixed_point(-frac), saturating_convert(-frac));
2667 assert_eq!(float_to_fixed_point(-frac), -float_to_fixed_point(frac));
2668 }
2669
2670 #[mz_ore::test]
2671 fn float_to_fixed_point_subnormals_round_to_zero() {
2672 assert_eq!(float_to_fixed_point(0.0), 0);
2673 assert_eq!(float_to_fixed_point(-0.0), 0);
2674 assert_eq!(float_to_fixed_point(f64::MIN_POSITIVE / 2.0), 0);
2675 assert_eq!(float_to_fixed_point(5e-324), 0); }
2677
2678 #[mz_ore::test]
2679 fn float_to_fixed_point_cancels_large_finite_values() {
2680 for &n in &[1.1e31_f64, 1e32, 5e33, 1e284] {
2685 assert_eq!(
2686 float_to_fixed_point(n).wrapping_add(float_to_fixed_point(-n)),
2687 0,
2688 "n = {n} did not cancel with -n"
2689 );
2690 }
2691 }
2692
2693 #[mz_ore::test]
2694 fn float_to_fixed_point_sum_via_accumulator() {
2695 let func = AggregateFunc::SumFloat64;
2697 let mut acc = accumulable_zero(&func);
2698 acc.plus_equals(&datum_to_accumulator(&func, Datum::from(1.1e31_f64)));
2699 acc.plus_equals(&datum_to_accumulator(&func, Datum::from(-1.1e31_f64)));
2700 let datum = finalize_accum(&func, &acc, Diff::from(2_i64));
2701 assert_eq!(datum, Datum::from(0.0_f64));
2702 }
2703}