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::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
65type BatchKeyContainer<Tr> = <BatchCursor<Tr> as Cursor>::KeyContainer;
67
68impl<'scope, T: RenderTimestamp> Context<'scope, T> {
69 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 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 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 for skip in skips.iter() {
122 datums_local.push(row_iter.nth(*skip).unwrap());
123 }
124
125 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 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 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 self.render_reduce_plan(reduce_plan, key_val_collection, err, key_arity, mfp_after)
187 .leave_region(self.scope)
188 })
189 }
190
191 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 let arrangement = match plan {
230 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 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 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 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 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 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 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 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 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 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 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 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 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 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 {
514 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 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 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 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 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 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 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 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 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 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 assert!(!must_validate);
728 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 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 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 let first_mod = buckets.get(0).copied().unwrap_or(1);
872 let aggregations = aggr_funcs.len();
873
874 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 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 for (index, b) in buckets.into_iter().enumerate() {
890 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 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 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 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 let mut datums1 = DatumVec::new();
927 let mut datums2 = DatumVec::new();
928 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 let error_logger = self.error_logger();
940 let arranged = partial
943 .mz_arrange::<
944 ColumnationChunker<_>,
945 RowRowBatcher<_, _>,
946 RowRowBuilder<_, _>,
947 RowRowSpine<_, _>,
948 >(
949 "Arrange ReduceMinsMaxes",
950 );
951 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 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 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 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 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 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 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 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 let arranged_input = input
1154 .mz_arrange::<
1155 ColumnationChunker<_>,
1156 RowRowBatcher<_, _>,
1157 RowRowBuilder<_, _>,
1158 RowRowSpine<_, _>,
1159 >(
1160 "Arranged MinsMaxesHierarchical input",
1161 );
1162
1163 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 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 let key = <BatchKeyContainer<Tr> as BatchContainer>::into_owned(key);
1182 target.push((err(key), Diff::ONE));
1183 return;
1184 }
1185 }
1186
1187 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 _ => (), }
1561 }
1562
1563 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
1584fn 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 if let Some(mfp) = mfp_after {
1597 if let Ok(Some(iter)) = mfp.evaluate_iter(datums_local, temp_storage) {
1600 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
1638const FLOAT_SCALE_EXP: u32 = 24;
1642
1643#[allow(clippy::as_conversions)] const FLOAT_SCALE: f64 = (1_u64 << FLOAT_SCALE_EXP) as f64;
1646
1647fn float_to_fixed_point(n: f64) -> i128 {
1664 debug_assert!(n.is_finite());
1665
1666 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 match u32::try_from(exp) {
1677 Ok(shift) if shift < 128 => significand << shift,
1678 _ => 0,
1679 }
1680 } else {
1681 match u32::try_from(-exp) {
1684 Ok(shift) if shift < 128 => significand >> shift,
1685 _ => 0,
1686 }
1687 };
1688
1689 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, 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 let accum = if nans.is_positive() || pos_infs.is_positive() || neg_infs.is_positive() {
1747 AccumCount::ZERO
1748 } else {
1749 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 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 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 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 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 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 (AggregateFunc::SumInt16, Accum::SimpleNumber { accum, .. })
1874 | (AggregateFunc::SumInt32, Accum::SimpleNumber { accum, .. }) => {
1875 #[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 #[allow(clippy::as_conversions)]
1892 Datum::UInt64(accum.into_inner() as u64)
1893 } else {
1894 Datum::Null
1898 }
1899 }
1900 (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1901 if !accum.is_negative() {
1902 Datum::from(*accum)
1903 } else {
1904 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 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 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 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 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
1999type AccumCount = mz_ore::Overflowing<i128>;
2001
2002#[derive(
2013 Debug,
2014 Clone,
2015 Copy,
2016 PartialEq,
2017 Eq,
2018 PartialOrd,
2019 Ord,
2020 Serialize,
2021 Deserialize
2022)]
2023enum Accum {
2024 Bool {
2026 trues: Diff,
2028 falses: Diff,
2030 },
2031 SimpleNumber {
2033 accum: AccumCount,
2035 non_nulls: Diff,
2037 },
2038 Float {
2040 accum: AccumCount,
2043 pos_infs: Diff,
2045 neg_infs: Diff,
2047 nans: Diff,
2049 non_nulls: Diff,
2051 },
2052 Numeric {
2054 accum: OrderedDecimal<NumericAgg>,
2056 pos_infs: Diff,
2058 neg_infs: Diff,
2060 nans: Diff,
2062 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 assert!(!cx_agg.status().rounded(), "Accum::Numeric overflow");
2174 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 cx.mul(&mut f, &accum.0);
2250 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
2272mod monoids {
2274
2275 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 #[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 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 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 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 false
2405 }
2406 }
2407
2408 impl Columnation for ReductionMonoid {
2409 type InnerRegion = ReductionMonoidRegion;
2410 }
2411
2412 #[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 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 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 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 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 #[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 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, -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 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 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); }
2699
2700 #[mz_ore::test]
2701 fn float_to_fixed_point_cancels_large_finite_values() {
2702 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 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}