1use std::collections::BTreeMap;
15
16use columnar::Columnar;
17use columnation::{Columnation, CopyRegion};
18use differential_dataflow::Diff as _;
19use differential_dataflow::collection::AsCollection;
20use differential_dataflow::columnar::layout::Coltainer;
21use differential_dataflow::consolidation::ConsolidatingContainerBuilder;
22use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
23use differential_dataflow::hashable::Hashable;
24use differential_dataflow::operators::arrange::{Arranged, TraceAgent};
25use differential_dataflow::trace::cursor::{BatchCursor, BatchDiff, BatchValOwn};
26use differential_dataflow::trace::implementations::BatchContainer;
27use differential_dataflow::trace::{Builder, Cursor, Navigable, Trace};
28use differential_dataflow::{Data, VecCollection};
29use itertools::Itertools;
30use mz_compute_types::dyncfgs::{ENABLE_COLUMNAR_ACCUMULABLE_DIFF, TEMPORAL_BUCKETING_SUMMARY};
31use mz_compute_types::plan::ArrangementStrategy;
32use mz_compute_types::plan::reduce::{
33 AccumulablePlan, BasicPlan, BucketedPlan, HierarchicalPlan, KeyValPlan, LirAggregateExpr,
34 MonotonicPlan, ReducePlan, ReductionType, SingleBasicPlan, reduction_type,
35};
36use mz_compute_types::plan::scalar::LirScalarExpr;
37use mz_expr::{AggregateFunc, EvalError, SafeMfpPlan};
38use mz_ore::cast::CastLossy;
39use mz_repr::adt::interval::Interval;
40use mz_repr::adt::numeric::{self, Numeric, NumericAgg, OrderedNumericAgg};
41use mz_repr::fixed_length::ExtendDatums;
42use mz_repr::{Datum, DatumVec, Diff, Row, RowArena, SharedRow};
43use mz_row_spine::{
44 DatumContainer, DatumSeq, RowBatcher, RowBuilder, RowRowBatcher, RowRowBuilder, RowValBatcher,
45 RowValBuilder,
46};
47use mz_timely_util::operator::CollectionExt;
48use num_traits::Float;
49use serde::{Deserialize, Serialize};
50use timely::Container;
51use timely::container::{CapacityContainerBuilder, PushInto};
52use tracing::warn;
53
54use crate::extensions::arrange::{ArrangementSize, KeyCollection, MzArrange};
55use crate::extensions::reduce::{ClearContainer, MzReduce};
56use crate::render::context::{CollectionBundle, Context};
57use crate::render::errors::DataflowErrorSer;
58use crate::render::errors::MaybeValidatingRow;
59use crate::render::reduce::monoids::{ReductionMonoid, get_monoid};
60use crate::render::{ArrangementFlavor, Pairer, RenderTimestamp};
61use crate::typedefs::ConsolidateBatcher;
62use crate::typedefs::{
63 ErrBatcher, RowAgent, RowErrBuilder, RowErrSpine, RowRowAgent, RowRowArrangement, RowRowSpine,
64 RowSpine, RowValSpine,
65};
66
67type BatchKeyContainer<Tr> = <BatchCursor<Tr> as Cursor>::KeyContainer;
69
70impl<'scope, T: RenderTimestamp> Context<'scope, T> {
71 pub fn render_reduce(
74 &self,
75 input_key: Option<Vec<LirScalarExpr>>,
76 input: CollectionBundle<'scope, T>,
77 key_val_plan: KeyValPlan,
78 reduce_plan: ReducePlan,
79 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
80 temporal_bucketing_strategy: ArrangementStrategy,
81 ) -> CollectionBundle<'scope, T>
82 where
83 T: crate::render::MaybeBucketByTime,
84 {
85 input.scope().region_named("Reduce", |inner| {
86 let KeyValPlan {
87 mut key_plan,
88 mut val_plan,
89 } = key_val_plan;
90 let key_arity = key_plan.projection.len();
91 let mut datums = DatumVec::new();
92
93 let mut demand = Vec::new();
95 demand.extend(key_plan.demand());
96 demand.extend(val_plan.demand());
97 demand.sort();
98 demand.dedup();
99
100 let mut demand_map = BTreeMap::new();
102 for column in demand.iter() {
103 demand_map.insert(*column, demand_map.len());
104 }
105 let demand_map_len = demand_map.len();
106 key_plan.permute_fn(|c| demand_map[&c], demand_map_len);
107 val_plan.permute_fn(|c| demand_map[&c], demand_map_len);
108 let max_demand = demand.iter().max().map(|x| *x + 1).unwrap_or(0);
109 let skips = mz_compute_types::plan::reduce::convert_indexes_to_skips(demand);
110
111 let (key_val_input, err) = input
112 .enter_region(inner)
113 .flat_map::<ConsolidatingContainerBuilder<Vec<((Row, Row), T, Diff)>>, _>(
114 input_key.map(|k| (k, None)),
115 max_demand,
116 move |row_datums, time, diff, ok_session, err_session| {
117 let mut row_builder = SharedRow::get();
118 let temp_storage = RowArena::new();
119
120 let mut row_iter = row_datums.drain(..);
121 let mut datums_local = datums.borrow();
122 for skip in skips.iter() {
124 datums_local.push(row_iter.nth(*skip).unwrap());
125 }
126
127 let key = key_plan.evaluate_into(
129 &mut datums_local,
130 &temp_storage,
131 &mut row_builder,
132 );
133 let key = match key {
134 Err(e) => {
135 err_session.give((e.into(), time, diff));
136 return 1;
137 }
138 Ok(Some(key)) => key.clone(),
139 Ok(None) => panic!("Row expected as no predicate was used"),
140 };
141
142 datums_local.truncate(skips.len());
145 let val = val_plan.evaluate_into(
146 &mut datums_local,
147 &temp_storage,
148 &mut row_builder,
149 );
150 let val = match val {
151 Err(e) => {
152 err_session.give((e.into(), time, diff));
153 return 1;
154 }
155 Ok(Some(val)) => val.clone(),
156 Ok(None) => panic!("Row expected as no predicate was used"),
157 };
158
159 ok_session.give(((key, val), time, diff));
160 1
161 },
162 );
163
164 let key_val_collection = key_val_input.as_collection();
169 let key_val_collection = if matches!(
170 temporal_bucketing_strategy,
171 ArrangementStrategy::TemporalBucketing
172 ) {
173 let summary: mz_repr::Timestamp = TEMPORAL_BUCKETING_SUMMARY
174 .get(&self.config_set)
175 .try_into()
176 .expect("must fit");
177 T::maybe_apply_temporal_bucketing_vec(
178 key_val_collection.inner,
179 self.as_of_frontier.clone(),
180 summary,
181 )
182 } else {
183 key_val_collection
184 };
185
186 self.render_reduce_plan(reduce_plan, key_val_collection, err, key_arity, mfp_after)
188 .leave_region(self.scope)
189 })
190 }
191
192 fn render_reduce_plan<'s>(
198 &self,
199 plan: ReducePlan,
200 collection: VecCollection<'s, T, (Row, Row), Diff>,
201 err_input: VecCollection<'s, T, DataflowErrorSer, Diff>,
202 key_arity: usize,
203 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
204 ) -> CollectionBundle<'s, T> {
205 let mut errors = Default::default();
206 let arrangement =
207 self.render_reduce_plan_inner(plan, collection, &mut errors, key_arity, mfp_after);
208 let errs: KeyCollection<_, _, _> = err_input.concatenate(errors).into();
209 CollectionBundle::from_columns(
210 0..key_arity,
211 ArrangementFlavor::Local(
212 arrangement,
213 errs.mz_arrange::<ErrBatcher<_, _>, _>("Arrange bundle err"),
214 ),
215 )
216 }
217
218 fn render_reduce_plan_inner<'s>(
219 &self,
220 plan: ReducePlan,
221 collection: VecCollection<'s, T, (Row, Row), Diff>,
222 errors: &mut Vec<VecCollection<'s, T, DataflowErrorSer, Diff>>,
223 key_arity: usize,
224 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
225 ) -> Arranged<'s, RowRowAgent<T, Diff>> {
226 let arrangement = match plan {
229 ReducePlan::Distinct => {
232 let (arranged_output, errs) = self.build_distinct(collection, mfp_after);
233 errors.push(errs);
234 arranged_output
235 }
236 ReducePlan::Accumulable(expr) => {
237 let (arranged_output, errs) =
238 self.build_accumulable(collection, expr, key_arity, mfp_after);
239 errors.push(errs);
240 arranged_output
241 }
242 ReducePlan::Hierarchical(HierarchicalPlan::Monotonic(expr)) => {
243 let (output, errs) = self.build_monotonic(collection, expr, mfp_after);
244 errors.push(errs);
245 output
246 }
247 ReducePlan::Hierarchical(HierarchicalPlan::Bucketed(expr)) => {
248 let (output, errs) = self.build_bucketed(collection, expr, key_arity, mfp_after);
249 errors.push(errs);
250 output
251 }
252 ReducePlan::Basic(BasicPlan::Single(SingleBasicPlan {
253 expr,
254 fused_unnest_list,
255 })) => {
256 let validating = !fused_unnest_list;
260 let (output, errs) = self.build_basic_aggregate(
261 collection,
262 0,
263 &expr,
264 validating,
265 key_arity,
266 mfp_after,
267 fused_unnest_list,
268 );
269 if validating {
270 errors.push(errs.expect("validation should have occurred as it was requested"));
271 }
272 output
273 }
274 ReducePlan::Basic(BasicPlan::Multiple(aggrs)) => {
275 let (output, errs) =
276 self.build_basic_aggregates(collection, aggrs, key_arity, mfp_after);
277 errors.push(errs);
278 output
279 }
280 };
281 arrangement
282 }
283
284 fn build_distinct<'s>(
286 &self,
287 collection: VecCollection<'s, T, (Row, Row), Diff>,
288 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
289 ) -> (
290 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
291 VecCollection<'s, T, DataflowErrorSer, Diff>,
292 ) {
293 let error_logger = self.error_logger();
294
295 let mut datums1 = DatumVec::new();
297 let mut datums2 = DatumVec::new();
298 let mfp_after1 = mfp_after.clone();
299 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
300
301 let arranged =
302 collection.mz_arrange::<RowRowBatcher<_, _>, RowRowSpine<_, _>>("Arranged DistinctBy");
303 let output = arranged
304 .clone()
305 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
306 "DistinctBy",
307 move |key, _input, output| {
308 let temp_storage = RowArena::new();
309 let mut datums_local = datums1.borrow();
310 key.extend_datums(&temp_storage, &mut datums_local, None);
311
312 if mfp_after1
316 .as_ref()
317 .map(|mfp| mfp.evaluate_inner(&mut datums_local, &temp_storage))
318 .unwrap_or(Ok(true))
319 == Ok(true)
320 {
321 output.push((Row::default(), Diff::ONE));
325 }
326 },
327 );
328 let errors = arranged.mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
329 "DistinctByErrorCheck",
330 move |key, input: &[(_, Diff)], output: &mut Vec<(DataflowErrorSer, _)>| {
331 for (_, count) in input.iter() {
332 if count.is_positive() {
333 continue;
334 }
335 let message = "Non-positive multiplicity in DistinctBy";
336 error_logger.log(message, &format!("row={key:?}, count={count}"));
337 output.push((EvalError::Internal(message.into()).into(), Diff::ONE));
338 return;
339 }
340 let Some(mfp) = &mfp_after2 else { return };
342 let temp_storage = RowArena::new();
343 let mut datums_local = datums2.borrow();
344 key.extend_datums(&temp_storage, &mut datums_local, None);
345
346 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
347 output.push((e.into(), Diff::ONE));
348 }
349 },
350 );
351 (output, errors.as_collection(|_k, v| v.clone()))
352 }
353
354 fn build_basic_aggregates<'s>(
362 &self,
363 input: VecCollection<'s, T, (Row, Row), Diff>,
364 aggrs: Vec<LirAggregateExpr>,
365 key_arity: usize,
366 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
367 ) -> (
368 RowRowArrangement<'s, T>,
369 VecCollection<'s, T, DataflowErrorSer, Diff>,
370 ) {
371 if aggrs.len() <= 1 {
374 self.error_logger().soft_panic_or_log(
375 "Too few aggregations when building basic aggregates",
376 &format!("len={}", aggrs.len()),
377 )
378 }
379 let mut err_output = None;
380 let mut to_collect = Vec::new();
381 for (index, aggr) in aggrs.into_iter().enumerate() {
382 let (result, errs) = self.build_basic_aggregate(
383 input.clone(),
384 index,
385 &aggr,
386 err_output.is_none(),
387 key_arity,
388 None,
389 false,
390 );
391 if errs.is_some() {
392 err_output = errs
393 }
394 to_collect
395 .push(result.as_collection(move |key, val| (key.to_row(), (index, val.to_row()))));
396 }
397
398 let mut datums1 = DatumVec::new();
400 let mut datums2 = DatumVec::new();
401 let mfp_after1 = mfp_after.clone();
402 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
403
404 let arranged = differential_dataflow::collection::concatenate(input.scope(), to_collect)
405 .mz_arrange::<RowValBatcher<_, _, _>, RowValSpine<_, _, _>>(
406 "Arranged ReduceFuseBasic input",
407 );
408
409 let output = arranged
410 .clone()
411 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
412 "ReduceFuseBasic",
413 {
414 move |key, input, output| {
415 let temp_storage = RowArena::new();
416 let mut datums_local = datums1.borrow();
417 key.extend_datums(&temp_storage, &mut datums_local, None);
418 let key_len = datums_local.len();
419
420 for ((_, row), _) in input.iter() {
421 datums_local.push(row.unpack_first());
422 }
423
424 if let Some(row) = evaluate_mfp_after(
425 &mfp_after1,
426 &mut datums_local,
427 &temp_storage,
428 key_len,
429 ) {
430 output.push((row, Diff::ONE));
431 }
432 }
433 },
434 );
435 let validation_errs = err_output.expect("expected to validate in at least one aggregate");
440 if let Some(mfp) = mfp_after2 {
441 let mfp_errs = arranged
442 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
443 "ReduceFuseBasic Error Check",
444 move |key, input, output| {
445 let temp_storage = RowArena::new();
448 let mut datums_local = datums2.borrow();
449 key.extend_datums(&temp_storage, &mut datums_local, None);
450
451 for ((_, row), _) in input.iter() {
452 datums_local.push(row.unpack_first());
453 }
454
455 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
456 output.push((e.into(), Diff::ONE));
457 }
458 },
459 )
460 .as_collection(|_, v| v.clone());
461 (output, validation_errs.concat(mfp_errs))
462 } else {
463 (output, validation_errs)
464 }
465 }
466
467 fn build_basic_aggregate<'s>(
471 &self,
472 input: VecCollection<'s, T, (Row, Row), Diff>,
473 index: usize,
474 aggr: &LirAggregateExpr,
475 validating: bool,
476 key_arity: usize,
477 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
478 fused_unnest_list: bool,
479 ) -> (
480 RowRowArrangement<'s, T>,
481 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
482 ) {
483 let LirAggregateExpr {
484 func,
485 expr: _,
486 distinct,
487 } = aggr.clone();
488
489 let mut partial = input.map(move |(key, row)| {
491 let mut row_builder = SharedRow::get();
492 let value = row.iter().nth(index).unwrap();
493 row_builder.packer().push(value);
494 (key, row_builder.clone())
495 });
496
497 let mut err_output = None;
498
499 if distinct {
501 let pairer = Pairer::new(key_arity);
503 let keyed = partial.map(move |(key, val)| pairer.merge(&key, &val));
504 if validating {
505 let (oks, errs) = self
506 .build_reduce_inaccumulable_distinct::<
507 RowValBuilder<Result<(), String>, _, _>,
508 RowValSpine<Result<(), String>, _, _>,
509 >(keyed, None)
510 .as_collection(|k, v| {
511 (
512 k.to_row(),
513 v.as_ref()
514 .map(|&()| ())
515 .map_err(|m| m.as_str().into()),
516 )
517 })
518 .map_fallible::<
519 CapacityContainerBuilder<_>,
520 CapacityContainerBuilder<_>,
521 _,
522 _,
523 _,
524 >(
525 "Demux Errors",
526 move |(key_val, result)| match result {
527 Ok(()) => Ok(pairer.split(&key_val)),
528 Err(m) => {
529 Err(EvalError::Internal(m).into())
530 }
531 },
532 );
533 err_output = Some(errs);
534 partial = oks;
535 } else {
536 partial = self
537 .build_reduce_inaccumulable_distinct::<RowBuilder<_, _>, RowSpine<_, _>>(
538 keyed,
539 Some(" [val: empty]"),
540 )
541 .as_collection(move |key_val_iter, _| pairer.split(key_val_iter));
542 }
543 }
544
545 let mut datums1 = DatumVec::new();
547 let mut datums2 = DatumVec::new();
548 let mut datums_key_1 = DatumVec::new();
549 let mut datums_key_2 = DatumVec::new();
550 let mut vals1 = DatumVec::new();
554 let mut vals2 = DatumVec::new();
555 let mut vals_key_1 = DatumVec::new();
556 let mut vals_key_2 = DatumVec::new();
557 let mfp_after1 = mfp_after.clone();
558 let func2 = func.clone();
559
560 let name = if !fused_unnest_list {
561 "ReduceInaccumulable"
562 } else {
563 "FusedReduceUnnestList"
564 };
565 let arranged = partial
566 .mz_arrange::<RowRowBatcher<_, _>, RowRowSpine<_, _>>(&format!("Arranged {name}"));
567 let oks = if !fused_unnest_list {
568 arranged
569 .clone()
570 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(name, {
571 move |key, source, target| {
572 let temp_storage = RowArena::new();
573 let mut val_scratch = vals1.borrow();
580 let iter = source.iter().map(|(v, w)| {
581 val_scratch.clear();
582 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
583 (val_scratch[0], *w)
584 });
585
586 let mut datums_local = datums1.borrow();
587 key.extend_datums(&temp_storage, &mut datums_local, None);
588 let key_len = datums_local.len();
589 datums_local.push(
590 func.eval_with_fast_window_agg::<_, window_agg_helpers::OneByOneAggrImpls>(
593 iter,
594 &temp_storage,
595 ),
596 );
597
598 if let Some(row) = evaluate_mfp_after(
599 &mfp_after1,
600 &mut datums_local,
601 &temp_storage,
602 key_len,
603 ) {
604 target.push((row, Diff::ONE));
605 }
606 }
607 })
608 } else {
609 arranged
610 .clone()
611 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(name, {
612 move |key, source, target| {
613 let temp_storage = RowArena::new();
615 let mut val_scratch = vals_key_1.borrow();
616 let iter = source.iter().map(|(v, w)| {
617 val_scratch.clear();
618 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
619 (val_scratch[0], *w)
620 });
621
622 let mut datums_local = datums_key_1.borrow();
624 key.extend_datums(&temp_storage, &mut datums_local, None);
625 let key_len = datums_local.len();
626 for datum in func
627 .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
628 iter,
629 &temp_storage,
630 )
631 {
632 datums_local.truncate(key_len);
633 datums_local.push(datum);
634 if let Some(row) = evaluate_mfp_after(
635 &mfp_after1,
636 &mut datums_local,
637 &temp_storage,
638 key_len,
639 ) {
640 target.push((row, Diff::ONE));
641 }
642 }
643 }
644 })
645 };
646
647 let must_validate = validating && err_output.is_none();
651 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
652 if must_validate || mfp_after2.is_some() {
653 let error_logger = self.error_logger();
654
655 let errs = if !fused_unnest_list {
656 arranged
657 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
658 &format!("{name} Error Check"),
659 move |key, source, target| {
660 if must_validate {
664 for (value, count) in source.iter() {
665 if count.is_positive() {
666 continue;
667 }
668 let value = value.to_row();
669 let message =
670 "Non-positive accumulation in ReduceInaccumulable";
671 error_logger
672 .log(message, &format!("value={value:?}, count={count}"));
673 let err = EvalError::Internal(message.into());
674 target.push((err.into(), Diff::ONE));
675 return;
676 }
677 }
678
679 let Some(mfp) = &mfp_after2 else { return };
681 let temp_storage = RowArena::new();
682 let mut val_scratch = vals2.borrow();
683 let iter = source.iter().map(|(v, w)| {
684 val_scratch.clear();
685 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
686 (val_scratch[0], *w)
687 });
688
689 let mut datums_local = datums2.borrow();
690 key.extend_datums(&temp_storage, &mut datums_local, None);
691 datums_local.push(
692 func2.eval_with_fast_window_agg::<
693 _,
694 window_agg_helpers::OneByOneAggrImpls,
695 >(
696 iter, &temp_storage
697 ),
698 );
699 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
700 target.push((e.into(), Diff::ONE));
701 }
702 },
703 )
704 .as_collection(|_, v| v.clone())
705 } else {
706 assert!(!must_validate);
708 let Some(mfp) = mfp_after2 else {
711 unreachable!()
712 };
713 arranged
714 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
715 &format!("{name} Error Check"),
716 move |key, source, target| {
717 let temp_storage = RowArena::new();
718 let mut val_scratch = vals_key_2.borrow();
719 let iter = source.iter().map(|(v, w)| {
720 val_scratch.clear();
721 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
722 (val_scratch[0], *w)
723 });
724
725 let mut datums_local = datums_key_2.borrow();
726 key.extend_datums(&temp_storage, &mut datums_local, None);
727 let key_len = datums_local.len();
728 for datum in func2
729 .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
730 iter,
731 &temp_storage,
732 )
733 {
734 datums_local.truncate(key_len);
735 datums_local.push(datum);
736 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
739 {
740 target.push((e.into(), Diff::ONE));
741 }
742 }
743 },
744 )
745 .as_collection(|_, v| v.clone())
746 };
747
748 if let Some(e) = err_output {
749 err_output = Some(e.concat(errs));
750 } else {
751 err_output = Some(errs);
752 }
753 }
754 (oks, err_output)
755 }
756
757 fn build_reduce_inaccumulable_distinct<'s, Bu, Tr>(
758 &self,
759 input: VecCollection<'s, T, Row, Diff>,
760 name_tag: Option<&str>,
761 ) -> Arranged<'s, TraceAgent<Tr>>
762 where
763 Tr: Trace<Batch: Navigable, Time = T> + 'static,
764 for<'a> BatchCursor<Tr>: Cursor<
765 Key<'a> = DatumSeq<'a>,
766 KeyContainer = DatumContainer,
767 Time = T,
768 Diff = Diff,
769 ValOwn: Data + MaybeValidatingRow<(), String>,
770 >,
771 Bu: Builder<
772 Time = T,
773 Input: Container
774 + ClearContainer
775 + PushInto<((Row, BatchValOwn<Tr>), Tr::Time, BatchDiff<Tr>)>,
776 Output = Tr::Batch,
777 > + 'static,
778 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
779 {
780 let error_logger = self.error_logger();
781
782 let output_name = format!(
783 "ReduceInaccumulable Distinct{}",
784 name_tag.unwrap_or_default()
785 );
786
787 let input: KeyCollection<_, _, _> = input.into();
788 let arranged = input.mz_arrange::<RowBatcher<_, _>, RowSpine<_, _>>(
789 "Arranged ReduceInaccumulable Distinct [val: empty]",
790 );
791 arranged.mz_reduce_abelian::<_, Bu, Tr, _>(&output_name, move |_, source, t| {
792 if let Some(err) = BatchValOwn::<Tr>::into_error() {
793 for (value, count) in source.iter() {
794 if count.is_positive() {
795 continue;
796 }
797
798 let message = "Non-positive accumulation in ReduceInaccumulable DISTINCT";
799 error_logger.log(message, &format!("value={value:?}, count={count}"));
800 t.push((err(message.to_string()), Diff::ONE));
801 return;
802 }
803 }
804 t.push((BatchValOwn::<Tr>::ok(()), Diff::ONE))
805 })
806 }
807
808 fn build_bucketed<'s>(
826 &self,
827 input: VecCollection<'s, T, (Row, Row), Diff>,
828 BucketedPlan {
829 aggr_funcs,
830 buckets,
831 }: BucketedPlan,
832 key_arity: usize,
833 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
834 ) -> (
835 RowRowArrangement<'s, T>,
836 VecCollection<'s, T, DataflowErrorSer, Diff>,
837 ) {
838 let mut err_output: Option<VecCollection<'s, T, _, _>> = None;
839 let outer_scope = input.scope();
840 let arranged_output = outer_scope
841 .clone()
842 .region_named("ReduceHierarchical", |inner| {
843 let input = input.enter(inner);
844
845 let first_mod = buckets.get(0).copied().unwrap_or(1);
847 let aggregations = aggr_funcs.len();
848
849 let mut stage = input.map(move |(key, row)| {
851 let mut row_builder = SharedRow::get();
852 let mut row_packer = row_builder.packer();
853 row_packer.extend(row.iter().take(aggregations));
854 let values = row_builder.clone();
855
856 let hash = values.hashed() % first_mod;
858 let hash_key =
859 row_builder.pack_using(std::iter::once(Datum::from(hash)).chain(&key));
860 (hash_key, values)
861 });
862
863 for (index, b) in buckets.into_iter().enumerate() {
865 let input = if index == 0 {
867 stage
868 } else {
869 stage.map(move |(hash_key, values)| {
870 let mut hash_key_iter = hash_key.iter();
871 let hash = hash_key_iter.next().unwrap().unwrap_uint64() % b;
872 let hash_key = SharedRow::pack(
874 std::iter::once(Datum::from(hash))
875 .chain(hash_key_iter.take(key_arity)),
876 );
877 (hash_key, values)
878 })
879 };
880
881 let validating = err_output.is_none();
885
886 let (oks, errs) = self.build_bucketed_stage(&aggr_funcs, input, validating);
887 if let Some(errs) = errs {
888 err_output = Some(errs.leave_region(outer_scope));
889 }
890 stage = oks
891 }
892
893 let partial = stage.map(move |(hash_key, values)| {
895 let mut hash_key_iter = hash_key.iter();
896 let _hash = hash_key_iter.next();
897 (SharedRow::pack(hash_key_iter.take(key_arity)), values)
898 });
899
900 let mut datums1 = DatumVec::new();
902 let mut datums2 = DatumVec::new();
903 let mut vals1 = DatumVec::new();
907 let mut vals2 = DatumVec::new();
908 let mfp_after1 = mfp_after.clone();
909 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
910 let aggr_funcs2 = aggr_funcs.clone();
911
912 let error_logger = self.error_logger();
915 let arranged = partial.mz_arrange::<RowRowBatcher<_, _>, RowRowSpine<_, _>>(
918 "Arrange ReduceMinsMaxes",
919 );
920 let must_validate = err_output.is_none();
924 if must_validate || mfp_after2.is_some() {
925 let errs = arranged
926 .clone()
927 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
928 "ReduceMinsMaxes Error Check",
929 move |key, source, target| {
930 if must_validate {
934 for (val, count) in source.iter() {
935 if count.is_positive() {
936 continue;
937 }
938 let val = val.to_row();
939 let message =
940 "Non-positive accumulation in ReduceMinsMaxes";
941 error_logger
942 .log(message, &format!("val={val:?}, count={count}"));
943 target.push((
944 EvalError::Internal(message.into()).into(),
945 Diff::ONE,
946 ));
947 return;
948 }
949 }
950
951 let Some(mfp) = &mfp_after2 else { return };
953 let temp_storage = RowArena::new();
954 let mut datums_local = datums2.borrow();
955 key.extend_datums(&temp_storage, &mut datums_local, None);
956
957 let arity = aggr_funcs2.len();
962 let mut decoded = vals2.borrow();
963 for (values, _cnt) in source.iter() {
964 values.extend_datums(&temp_storage, &mut decoded, None);
965 }
966 assert_eq!(decoded.len(), source.len() * arity);
967 for (col, func) in aggr_funcs2.iter().enumerate() {
968 let column_iter = (0..source.len())
969 .map(|r| (decoded[r * arity + col], Diff::ONE));
970 datums_local.push(func.eval(column_iter, &temp_storage));
971 }
972 if let Result::Err(e) =
973 mfp.evaluate_inner(&mut datums_local, &temp_storage)
974 {
975 target.push((e.into(), Diff::ONE));
976 }
977 },
978 )
979 .as_collection(|_, v| v.clone())
980 .leave_region(outer_scope);
981 if let Some(e) = err_output.take() {
982 err_output = Some(e.concat(errs));
983 } else {
984 err_output = Some(errs);
985 }
986 }
987 arranged
988 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
989 "ReduceMinsMaxes",
990 move |key, source, target| {
991 let temp_storage = RowArena::new();
992 let mut datums_local = datums1.borrow();
993 key.extend_datums(&temp_storage, &mut datums_local, None);
994 let key_len = datums_local.len();
995
996 let arity = aggr_funcs.len();
1001 let mut decoded = vals1.borrow();
1002 for (values, _cnt) in source.iter() {
1003 values.extend_datums(&temp_storage, &mut decoded, None);
1004 }
1005 assert_eq!(decoded.len(), source.len() * arity);
1006 for (col, func) in aggr_funcs.iter().enumerate() {
1007 let column_iter = (0..source.len())
1008 .map(|r| (decoded[r * arity + col], Diff::ONE));
1009 datums_local.push(func.eval(column_iter, &temp_storage));
1010 }
1011
1012 if let Some(row) = evaluate_mfp_after(
1013 &mfp_after1,
1014 &mut datums_local,
1015 &temp_storage,
1016 key_len,
1017 ) {
1018 target.push((row, Diff::ONE));
1019 }
1020 },
1021 )
1022 .leave_region(outer_scope)
1023 });
1024 (
1025 arranged_output,
1026 err_output.expect("expected to validate in one level of the hierarchy"),
1027 )
1028 }
1029
1030 fn build_bucketed_stage<'s>(
1037 &self,
1038 aggr_funcs: &Vec<AggregateFunc>,
1039 input: VecCollection<'s, T, (Row, Row), Diff>,
1040 validating: bool,
1041 ) -> (
1042 VecCollection<'s, T, (Row, Row), Diff>,
1043 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
1044 ) {
1045 let (input, negated_output, errs) = if validating {
1046 let (input, reduced) = self
1047 .build_bucketed_negated_output::<
1048 RowValBuilder<_, _, _>,
1049 RowValSpine<Result<Row, Row>, _, _>,
1050 >(
1051 input.clone(),
1052 aggr_funcs.clone(),
1053 );
1054 let (oks, errs) = reduced
1055 .as_collection(|k, v| (k.to_row(), v.clone()))
1056 .map_fallible::<CapacityContainerBuilder<_>, CapacityContainerBuilder<_>, _, _, _>(
1057 "Checked Invalid Accumulations",
1058 |(hash_key, result)| match result {
1059 Err(hash_key) => {
1060 let mut hash_key_iter = hash_key.iter();
1061 let _hash = hash_key_iter.next();
1062 let key = SharedRow::pack(hash_key_iter);
1063 let message = format!(
1064 "Invalid data in source, saw non-positive accumulation \
1065 for key {key:?} in hierarchical mins-maxes aggregate"
1066 );
1067 Err(EvalError::Internal(message.into()).into())
1068 }
1069 Ok(values) => Ok((hash_key, values)),
1070 },
1071 );
1072 (input, oks, Some(errs))
1073 } else {
1074 let (input, reduced) = self
1075 .build_bucketed_negated_output::<RowRowBuilder<_, _>, RowRowSpine<_, _>>(
1076 input,
1077 aggr_funcs.clone(),
1078 );
1079 let oks = reduced.as_collection(|k, v| (k.to_row(), v.to_row()));
1082 (input, oks, None)
1083 };
1084
1085 let input = input.as_collection(|k, v| (k.to_row(), v.to_row()));
1086 let oks = negated_output.concat(input);
1087 (oks, errs)
1088 }
1089
1090 fn build_bucketed_negated_output<'s, Bu, Tr>(
1094 &self,
1095 input: VecCollection<'s, T, (Row, Row), Diff>,
1096 aggrs: Vec<AggregateFunc>,
1097 ) -> (
1098 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
1099 Arranged<'s, TraceAgent<Tr>>,
1100 )
1101 where
1102 Tr: Trace<Batch: Navigable, Time = T> + 'static,
1103 for<'a> BatchCursor<Tr>: Cursor<
1104 Key<'a> = DatumSeq<'a>,
1105 KeyContainer = DatumContainer,
1106 ValOwn: Data + MaybeValidatingRow<Row, Row>,
1107 Time = T,
1108 Diff = Diff,
1109 >,
1110 Bu: Builder<
1111 Time = T,
1112 Input: Container
1113 + ClearContainer
1114 + PushInto<((Row, BatchValOwn<Tr>), Tr::Time, BatchDiff<Tr>)>,
1115 Output = Tr::Batch,
1116 > + 'static,
1117 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
1118 {
1119 let error_logger = self.error_logger();
1120 let arranged_input = input.mz_arrange::<RowRowBatcher<_, _>, RowRowSpine<_, _>>(
1123 "Arranged MinsMaxesHierarchical input",
1124 );
1125
1126 let mut value_datums = DatumVec::new();
1129 let reduced = arranged_input.clone().mz_reduce_abelian::<_, Bu, Tr, _>(
1130 "Reduced Fallibly MinsMaxesHierarchical",
1131 move |key, source, target| {
1132 if let Some(err) = BatchValOwn::<Tr>::into_error() {
1133 for (value, count) in source.iter() {
1135 if count.is_positive() {
1136 continue;
1137 }
1138 error_logger.log(
1139 "Non-positive accumulation in MinsMaxesHierarchical",
1140 &format!("key={key:?}, value={value:?}, count={count}"),
1141 );
1142 let key = <BatchKeyContainer<Tr> as BatchContainer>::into_owned(key);
1145 target.push((err(key), Diff::ONE));
1146 return;
1147 }
1148 }
1149
1150 let temp_storage = RowArena::new();
1153 let arity = aggrs.len();
1154 let mut decoded = value_datums.borrow();
1155 for (values, _cnt) in source.iter() {
1156 values.extend_datums(&temp_storage, &mut decoded, None);
1157 }
1158 assert_eq!(decoded.len(), source.len() * arity);
1159
1160 let mut row_builder = SharedRow::get();
1161 let mut row_packer = row_builder.packer();
1162 for (col, func) in aggrs.iter().enumerate() {
1163 let column_iter =
1166 (0..source.len()).map(|r| (decoded[r * arity + col], Diff::ONE));
1167 row_packer.push(func.eval(column_iter, &temp_storage));
1168 }
1169 target.reserve(source.len().saturating_add(1));
1176 target.push((BatchValOwn::<Tr>::ok(row_builder.clone()), Diff::ONE));
1177 target.extend(source.iter().map(|(values, cnt)| {
1178 let mut cnt = *cnt;
1179 cnt.negate();
1180 (BatchValOwn::<Tr>::ok(values.to_row()), cnt)
1181 }));
1182 },
1183 );
1184 (arranged_input, reduced)
1185 }
1186
1187 fn build_monotonic<'s>(
1190 &self,
1191 collection: VecCollection<'s, T, (Row, Row), Diff>,
1192 MonotonicPlan {
1193 aggr_funcs,
1194 must_consolidate,
1195 }: MonotonicPlan,
1196 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1197 ) -> (
1198 RowRowArrangement<'s, T>,
1199 VecCollection<'s, T, DataflowErrorSer, Diff>,
1200 ) {
1201 let aggregations = aggr_funcs.len();
1202 let collection = collection
1204 .map(move |(key, row)| {
1205 let mut row_builder = SharedRow::get();
1206 let mut values = Vec::with_capacity(aggregations);
1207 values.extend(
1208 row.iter()
1209 .take(aggregations)
1210 .map(|v| row_builder.pack_using(std::iter::once(v))),
1211 );
1212
1213 (key, values)
1214 })
1215 .consolidate_named_if::<ConsolidateBatcher<_, _, _>>(
1216 must_consolidate,
1217 "Consolidated ReduceMonotonic input",
1218 );
1219
1220 let error_logger = self.error_logger();
1222 let (partial, validation_errs) = collection.ensure_monotonic(move |data, diff| {
1223 error_logger.log(
1224 "Non-monotonic input to ReduceMonotonic",
1225 &format!("data={data:?}, diff={diff}"),
1226 );
1227 let m = "tried to build a monotonic reduction on non-monotonic input".into();
1228 (EvalError::Internal(m).into(), Diff::ONE)
1229 });
1230 let partial = partial.explode_one(move |(key, values)| {
1234 let mut output = Vec::new();
1235 for (row, func) in values.into_iter().zip_eq(aggr_funcs.iter()) {
1236 output.push(monoids::get_monoid(row, func).expect(
1237 "hierarchical aggregations are expected to have monoid implementations",
1238 ));
1239 }
1240 (key, output)
1241 });
1242
1243 let mut datums1 = DatumVec::new();
1245 let mut datums2 = DatumVec::new();
1246 let mfp_after1 = mfp_after.clone();
1247 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1248
1249 let partial: KeyCollection<_, _, _> = partial.into();
1250 type MonoidSpine<T> = RowSpine<T, Vec<ReductionMonoid>>;
1251 let arranged =
1252 partial.mz_arrange::<RowBatcher<_, _>, MonoidSpine<_>>("ArrangeMonotonic [val: empty]");
1253 let output = arranged
1254 .clone()
1255 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1256 "ReduceMonotonic",
1257 {
1258 move |key, input, output| {
1259 let temp_storage = RowArena::new();
1260 let mut datums_local = datums1.borrow();
1261 key.extend_datums(&temp_storage, &mut datums_local, None);
1262 let key_len = datums_local.len();
1263 let accum = &input[0].1;
1264 for monoid in accum.iter() {
1265 datums_local.extend(monoid.finalize().iter());
1266 }
1267
1268 if let Some(row) = evaluate_mfp_after(
1269 &mfp_after1,
1270 &mut datums_local,
1271 &temp_storage,
1272 key_len,
1273 ) {
1274 output.push((row, Diff::ONE));
1275 }
1276 }
1277 },
1278 );
1279
1280 if let Some(mfp) = mfp_after2 {
1285 let mfp_errs = arranged
1286 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
1287 "ReduceMonotonic Error Check",
1288 move |key, input, output| {
1289 let temp_storage = RowArena::new();
1290 let mut datums_local = datums2.borrow();
1291 key.extend_datums(&temp_storage, &mut datums_local, None);
1292 let accum = &input[0].1;
1293 for monoid in accum.iter() {
1294 datums_local.extend(monoid.finalize().iter());
1295 }
1296 if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
1297 {
1298 output.push((e.into(), Diff::ONE));
1299 }
1300 },
1301 )
1302 .as_collection(|_k, v| v.clone());
1303 (output, validation_errs.concat(mfp_errs))
1304 } else {
1305 (output, validation_errs)
1306 }
1307 }
1308
1309 fn build_accumulable<'s>(
1316 &self,
1317 collection: VecCollection<'s, T, (Row, Row), Diff>,
1318 AccumulablePlan {
1319 full_aggrs,
1320 simple_aggrs,
1321 distinct_aggrs,
1322 }: AccumulablePlan,
1323 key_arity: usize,
1324 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1325 ) -> (
1326 RowRowArrangement<'s, T>,
1327 VecCollection<'s, T, DataflowErrorSer, Diff>,
1328 ) {
1329 let collection_scope = collection.scope();
1330
1331 if full_aggrs.len() == 0 || simple_aggrs.len() + distinct_aggrs.len() != full_aggrs.len() {
1333 self.error_logger().soft_panic_or_log(
1334 "Incorrect numbers of aggregates in accummulable reduction rendering",
1335 &format!(
1336 "full_aggrs={}, simple_aggrs={}, distinct_aggrs={}",
1337 full_aggrs.len(),
1338 simple_aggrs.len(),
1339 distinct_aggrs.len(),
1340 ),
1341 );
1342 }
1343
1344 let zero_diffs: (Vec<_>, Diff) = (
1356 full_aggrs
1357 .iter()
1358 .map(|f| accumulable_zero(&f.func))
1359 .collect(),
1360 Diff::ZERO,
1361 );
1362
1363 let mut to_aggregate = Vec::new();
1364 if simple_aggrs.len() > 0 {
1365 let collection = collection.clone();
1367 let easy_cases = collection.explode_one({
1368 let zero_diffs = zero_diffs.clone();
1369 move |(key, row)| {
1370 let mut diffs = zero_diffs.clone();
1371 let mut row_iter = row.iter().enumerate();
1377 for (datum_index, aggr) in simple_aggrs.iter() {
1378 let mut datum = row_iter.next().unwrap();
1379 while datum_index != &datum.0 {
1380 datum = row_iter.next().unwrap();
1381 }
1382 let datum = datum.1;
1383 diffs.0[*datum_index] = datum_to_accumulator(&aggr.func, datum);
1384 diffs.1 = Diff::ONE;
1385 }
1386 ((key, ()), diffs)
1387 }
1388 });
1389 to_aggregate.push(easy_cases);
1390 }
1391
1392 for (datum_index, aggr) in distinct_aggrs.into_iter() {
1394 let pairer = Pairer::new(key_arity);
1395 let collection = collection
1396 .clone()
1397 .map(move |(key, row)| {
1398 let value = row.iter().nth(datum_index).unwrap();
1399 (pairer.merge(&key, std::iter::once(value)), ())
1400 })
1401 .mz_arrange::<RowBatcher<_, _>, RowSpine<_, _>>(
1402 "Arranged Accumulable Distinct [val: empty]",
1403 )
1404 .mz_reduce_abelian::<_, RowBuilder<_, _>, RowSpine<_, _>, _>(
1405 "Reduced Accumulable Distinct [val: empty]",
1406 move |_k, _s, t| t.push(((), Diff::ONE)),
1407 )
1408 .as_collection(move |key_val_iter, _| pairer.split(key_val_iter))
1409 .explode_one({
1410 let zero_diffs = zero_diffs.clone();
1411 move |(key, row)| {
1412 let datum = row.iter().next().unwrap();
1413 let mut diffs = zero_diffs.clone();
1414 diffs.0[datum_index] = datum_to_accumulator(&aggr.func, datum);
1415 diffs.1 = Diff::ONE;
1416 ((key, ()), diffs)
1417 }
1418 });
1419 to_aggregate.push(collection);
1420 }
1421
1422 let collection = if to_aggregate.len() == 1 {
1424 to_aggregate.remove(0)
1425 } else {
1426 differential_dataflow::collection::concatenate(collection_scope, to_aggregate)
1427 };
1428
1429 if ENABLE_COLUMNAR_ACCUMULABLE_DIFF.get(&self.config_set) {
1434 let arranged = collection.mz_arrange::<
1435 RowBatcher<_, _, Coltainer<_>>,
1436 RowSpine<_, (Vec<Accum>, Diff), Coltainer<_>>,
1437 >("ArrangeAccumulable [val: empty]");
1438 self.reduce_accumulable(arranged, full_aggrs, mfp_after)
1439 } else {
1440 let arranged = collection
1441 .mz_arrange::<RowBatcher<_, _>, RowSpine<_, (Vec<Accum>, Diff)>>(
1442 "ArrangeAccumulable [val: empty]",
1443 );
1444 self.reduce_accumulable(arranged, full_aggrs, mfp_after)
1445 }
1446 }
1447
1448 fn reduce_accumulable<'s, DC>(
1452 &self,
1453 arranged: Arranged<'s, RowAgent<T, (Vec<Accum>, Diff), DC>>,
1454 full_aggrs: Vec<LirAggregateExpr>,
1455 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1456 ) -> (
1457 RowRowArrangement<'s, T>,
1458 VecCollection<'s, T, DataflowErrorSer, Diff>,
1459 )
1460 where
1461 DC: BatchContainer<Owned = (Vec<Accum>, Diff)>,
1462 {
1463 let mut datums1 = DatumVec::new();
1465 let mut datums2 = DatumVec::new();
1466 let mfp_after1 = mfp_after.clone();
1467 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1468 let full_aggrs2 = full_aggrs.clone();
1469
1470 let error_logger = self.error_logger();
1471 let err_full_aggrs = full_aggrs.clone();
1472 let arranged_output = arranged
1473 .clone()
1474 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1475 "ReduceAccumulable",
1476 {
1477 move |key, input, output| {
1478 let (ref accums, total) = input[0].1;
1479
1480 let temp_storage = RowArena::new();
1481 let mut datums_local = datums1.borrow();
1482 key.extend_datums(&temp_storage, &mut datums_local, None);
1483 let key_len = datums_local.len();
1484 for (aggr, accum) in full_aggrs.iter().zip_eq(accums) {
1485 datums_local.push(finalize_accum(&aggr.func, accum, total));
1486 }
1487
1488 if let Some(row) = evaluate_mfp_after(
1489 &mfp_after1,
1490 &mut datums_local,
1491 &temp_storage,
1492 key_len,
1493 ) {
1494 output.push((row, Diff::ONE));
1495 }
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 (
1538 AggregateFunc::SumInterval,
1539 Accum::Interval {
1540 months,
1541 days,
1542 micros,
1543 ..
1544 },
1545 ) => {
1546 if Interval::try_new(
1550 months.into_inner(),
1551 days.into_inner(),
1552 micros.into_inner(),
1553 )
1554 .is_none()
1555 {
1556 let err = EvalError::IntervalOutOfRange(
1557 format!(
1558 "{months} months {days} days {micros} microseconds"
1559 )
1560 .into(),
1561 );
1562 output.push((err.into(), Diff::ONE));
1563 }
1564 }
1565 _ => (), }
1567 }
1568
1569 let Some(mfp) = &mfp_after2 else { return };
1571 let temp_storage = RowArena::new();
1572 let mut datums_local = datums2.borrow();
1573 key.extend_datums(&temp_storage, &mut datums_local, None);
1574 for (aggr, accum) in full_aggrs2.iter().zip_eq(accums) {
1575 datums_local.push(finalize_accum(&aggr.func, accum, total));
1576 }
1577
1578 if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
1579 output.push((e.into(), Diff::ONE));
1580 }
1581 },
1582 );
1583 (
1584 arranged_output,
1585 arranged_errs.as_collection(|_key, error| error.clone()),
1586 )
1587 }
1588}
1589
1590fn evaluate_mfp_after<'a, 'b>(
1594 mfp_after: &'a Option<SafeMfpPlan<LirScalarExpr>>,
1595 datums_local: &'b mut mz_repr::DatumVecBorrow<'a>,
1596 temp_storage: &'a RowArena,
1597 key_len: usize,
1598) -> Option<Row> {
1599 let mut row_builder = SharedRow::get();
1600 if let Some(mfp) = mfp_after {
1603 if let Ok(Some(iter)) = mfp.evaluate_iter(datums_local, temp_storage) {
1606 Some(row_builder.pack_using(iter.skip(key_len)))
1609 } else {
1610 None
1611 }
1612 } else {
1613 Some(row_builder.pack_using(&datums_local[key_len..]))
1614 }
1615}
1616
1617fn accumulable_zero(aggr_func: &AggregateFunc) -> Accum {
1618 match aggr_func {
1619 AggregateFunc::Any | AggregateFunc::All => Accum::Bool {
1620 trues: Diff::ZERO,
1621 falses: Diff::ZERO,
1622 },
1623 AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => Accum::Float {
1624 accum: AccumCount::ZERO,
1625 pos_infs: Diff::ZERO,
1626 neg_infs: Diff::ZERO,
1627 nans: Diff::ZERO,
1628 non_nulls: Diff::ZERO,
1629 },
1630 AggregateFunc::SumNumeric => Accum::Numeric {
1631 accum: OrderedNumericAgg(NumericAgg::zero()),
1632 pos_infs: Diff::ZERO,
1633 neg_infs: Diff::ZERO,
1634 nans: Diff::ZERO,
1635 non_nulls: Diff::ZERO,
1636 },
1637 AggregateFunc::SumInterval => Accum::Interval {
1638 months: AccumCount::ZERO,
1639 days: AccumCount::ZERO,
1640 micros: AccumCount::ZERO,
1641 non_nulls: Diff::ZERO,
1642 },
1643 _ => Accum::SimpleNumber {
1644 accum: AccumCount::ZERO,
1645 non_nulls: Diff::ZERO,
1646 },
1647 }
1648}
1649
1650const FLOAT_SCALE_EXP: u32 = 24;
1654
1655#[allow(clippy::as_conversions)] const FLOAT_SCALE: f64 = (1_u64 << FLOAT_SCALE_EXP) as f64;
1658
1659fn float_to_fixed_point(n: f64) -> i128 {
1676 mz_ore::soft_assert_no_log!(n.is_finite());
1677
1678 let (mantissa, exponent, sign) = Float::integer_decode(n);
1682 let significand = u128::from(mantissa);
1683 let exp = i64::from(exponent) + i64::from(FLOAT_SCALE_EXP);
1684
1685 let magnitude: u128 = if exp >= 0 {
1686 match u32::try_from(exp) {
1689 Ok(shift) if shift < 128 => significand << shift,
1690 _ => 0,
1691 }
1692 } else {
1693 match u32::try_from(-exp) {
1696 Ok(shift) if shift < 128 => significand >> shift,
1697 _ => 0,
1698 }
1699 };
1700
1701 let magnitude = magnitude.cast_signed();
1704 if sign < 0 {
1705 magnitude.wrapping_neg()
1706 } else {
1707 magnitude
1708 }
1709}
1710
1711fn datum_to_accumulator(aggregate_func: &AggregateFunc, datum: Datum) -> Accum {
1712 match aggregate_func {
1713 AggregateFunc::Count => Accum::SimpleNumber {
1714 accum: AccumCount::ZERO, non_nulls: if datum.is_null() {
1716 Diff::ZERO
1717 } else {
1718 Diff::ONE
1719 },
1720 },
1721 AggregateFunc::Any | AggregateFunc::All => match datum {
1722 Datum::True => Accum::Bool {
1723 trues: Diff::ONE,
1724 falses: Diff::ZERO,
1725 },
1726 Datum::Null => Accum::Bool {
1727 trues: Diff::ZERO,
1728 falses: Diff::ZERO,
1729 },
1730 Datum::False => Accum::Bool {
1731 trues: Diff::ZERO,
1732 falses: Diff::ONE,
1733 },
1734 x => panic!("Invalid argument to AggregateFunc::Any: {x:?}"),
1735 },
1736 AggregateFunc::Dummy => match datum {
1737 Datum::Dummy => Accum::SimpleNumber {
1738 accum: AccumCount::ZERO,
1739 non_nulls: Diff::ZERO,
1740 },
1741 x => panic!("Invalid argument to AggregateFunc::Dummy: {x:?}"),
1742 },
1743 AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => {
1744 let n = match datum {
1745 Datum::Float32(n) => f64::from(*n),
1746 Datum::Float64(n) => *n,
1747 Datum::Null => 0f64,
1748 x => panic!("Invalid argument to AggregateFunc::{aggregate_func:?}: {x:?}"),
1749 };
1750
1751 let nans = Diff::from(n.is_nan());
1752 let pos_infs = Diff::from(n == f64::INFINITY);
1753 let neg_infs = Diff::from(n == f64::NEG_INFINITY);
1754 let non_nulls = Diff::from(datum != Datum::Null);
1755
1756 let accum = if nans.is_positive() || pos_infs.is_positive() || neg_infs.is_positive() {
1759 AccumCount::ZERO
1760 } else {
1761 float_to_fixed_point(n).into()
1765 };
1766
1767 Accum::Float {
1768 accum,
1769 pos_infs,
1770 neg_infs,
1771 nans,
1772 non_nulls,
1773 }
1774 }
1775 AggregateFunc::SumNumeric => match datum {
1776 Datum::Numeric(n) => {
1777 let (accum, pos_infs, neg_infs, nans) = if n.0.is_infinite() {
1778 if n.0.is_negative() {
1779 (NumericAgg::zero(), Diff::ZERO, Diff::ONE, Diff::ZERO)
1780 } else {
1781 (NumericAgg::zero(), Diff::ONE, Diff::ZERO, Diff::ZERO)
1782 }
1783 } else if n.0.is_nan() {
1784 (NumericAgg::zero(), Diff::ZERO, Diff::ZERO, Diff::ONE)
1785 } else {
1786 let mut cx_agg = numeric::cx_agg();
1789 (cx_agg.to_width(n.0), Diff::ZERO, Diff::ZERO, Diff::ZERO)
1790 };
1791
1792 Accum::Numeric {
1793 accum: OrderedNumericAgg(accum),
1794 pos_infs,
1795 neg_infs,
1796 nans,
1797 non_nulls: Diff::ONE,
1798 }
1799 }
1800 Datum::Null => Accum::Numeric {
1801 accum: OrderedNumericAgg(NumericAgg::zero()),
1802 pos_infs: Diff::ZERO,
1803 neg_infs: Diff::ZERO,
1804 nans: Diff::ZERO,
1805 non_nulls: Diff::ZERO,
1806 },
1807 x => panic!("Invalid argument to AggregateFunc::SumNumeric: {x:?}"),
1808 },
1809 AggregateFunc::SumInterval => match datum {
1810 Datum::Interval(i) => Accum::Interval {
1811 months: i.months.into(),
1812 days: i.days.into(),
1813 micros: i.micros.into(),
1814 non_nulls: Diff::ONE,
1815 },
1816 Datum::Null => Accum::Interval {
1817 months: AccumCount::ZERO,
1818 days: AccumCount::ZERO,
1819 micros: AccumCount::ZERO,
1820 non_nulls: Diff::ZERO,
1821 },
1822 x => panic!("Invalid argument to AggregateFunc::SumInterval: {x:?}"),
1823 },
1824 _ => {
1825 match datum {
1829 Datum::Int16(i) => Accum::SimpleNumber {
1830 accum: i.into(),
1831 non_nulls: Diff::ONE,
1832 },
1833 Datum::Int32(i) => Accum::SimpleNumber {
1834 accum: i.into(),
1835 non_nulls: Diff::ONE,
1836 },
1837 Datum::Int64(i) => Accum::SimpleNumber {
1838 accum: i.into(),
1839 non_nulls: Diff::ONE,
1840 },
1841 Datum::UInt16(u) => Accum::SimpleNumber {
1842 accum: u.into(),
1843 non_nulls: Diff::ONE,
1844 },
1845 Datum::UInt32(u) => Accum::SimpleNumber {
1846 accum: u.into(),
1847 non_nulls: Diff::ONE,
1848 },
1849 Datum::UInt64(u) => Accum::SimpleNumber {
1850 accum: u.into(),
1851 non_nulls: Diff::ONE,
1852 },
1853 Datum::MzTimestamp(t) => Accum::SimpleNumber {
1854 accum: u64::from(t).into(),
1855 non_nulls: Diff::ONE,
1856 },
1857 Datum::Null => Accum::SimpleNumber {
1858 accum: AccumCount::ZERO,
1859 non_nulls: Diff::ZERO,
1860 },
1861 x => panic!("Accumulating non-integer data: {x:?}"),
1862 }
1863 }
1864 }
1865}
1866
1867fn finalize_accum<'a>(aggr_func: &'a AggregateFunc, accum: &'a Accum, total: Diff) -> Datum<'a> {
1868 if total.is_positive() && accum.is_zero() && *aggr_func != AggregateFunc::Count {
1872 Datum::Null
1873 } else {
1874 match (&aggr_func, &accum) {
1875 (AggregateFunc::Count, Accum::SimpleNumber { non_nulls, .. }) => {
1876 Datum::Int64(non_nulls.into_inner())
1877 }
1878 (AggregateFunc::All, Accum::Bool { falses, trues }) => {
1879 if falses.is_positive() {
1881 Datum::False
1882 } else if *trues == total {
1883 Datum::True
1884 } else {
1885 Datum::Null
1886 }
1887 }
1888 (AggregateFunc::Any, Accum::Bool { falses, trues }) => {
1889 if trues.is_positive() {
1891 Datum::True
1892 } else if *falses == total {
1893 Datum::False
1894 } else {
1895 Datum::Null
1896 }
1897 }
1898 (AggregateFunc::Dummy, _) => Datum::Dummy,
1899 (AggregateFunc::SumInt16, Accum::SimpleNumber { accum, .. })
1901 | (AggregateFunc::SumInt32, Accum::SimpleNumber { accum, .. }) => {
1902 #[allow(clippy::as_conversions)]
1907 Datum::Int64(accum.into_inner() as i64)
1908 }
1909 (AggregateFunc::SumInt64, Accum::SimpleNumber { accum, .. }) => Datum::from(*accum),
1910 (AggregateFunc::SumUInt16, Accum::SimpleNumber { accum, .. })
1911 | (AggregateFunc::SumUInt32, Accum::SimpleNumber { accum, .. }) => {
1912 if !accum.is_negative() {
1913 #[allow(clippy::as_conversions)]
1919 Datum::UInt64(accum.into_inner() as u64)
1920 } else {
1921 Datum::Null
1925 }
1926 }
1927 (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1928 if !accum.is_negative() {
1929 Datum::from(*accum)
1930 } else {
1931 Datum::Null
1935 }
1936 }
1937 (
1938 AggregateFunc::SumFloat32,
1939 Accum::Float {
1940 accum,
1941 pos_infs,
1942 neg_infs,
1943 nans,
1944 non_nulls: _,
1945 },
1946 ) => {
1947 if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
1948 Datum::from(f32::NAN)
1951 } else if pos_infs.is_positive() {
1952 Datum::from(f32::INFINITY)
1953 } else if neg_infs.is_positive() {
1954 Datum::from(f32::NEG_INFINITY)
1955 } else {
1956 let sum = f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE;
1957 Datum::from(f32::cast_lossy(sum))
1958 }
1959 }
1960 (
1961 AggregateFunc::SumFloat64,
1962 Accum::Float {
1963 accum,
1964 pos_infs,
1965 neg_infs,
1966 nans,
1967 non_nulls: _,
1968 },
1969 ) => {
1970 if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
1971 Datum::from(f64::NAN)
1974 } else if pos_infs.is_positive() {
1975 Datum::from(f64::INFINITY)
1976 } else if neg_infs.is_positive() {
1977 Datum::from(f64::NEG_INFINITY)
1978 } else {
1979 Datum::from(f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE)
1980 }
1981 }
1982 (
1983 AggregateFunc::SumNumeric,
1984 Accum::Numeric {
1985 accum,
1986 pos_infs,
1987 neg_infs,
1988 nans,
1989 non_nulls: _,
1990 },
1991 ) => {
1992 let mut cx_datum = numeric::cx_datum();
1993 let d = cx_datum.to_width(accum.0);
1994 let inf_d = d.is_infinite();
2000 let neg_d = d.is_negative();
2001 let pos_inf = pos_infs.is_positive() || (inf_d && !neg_d);
2002 let neg_inf = neg_infs.is_positive() || (inf_d && neg_d);
2003 if nans.is_positive() || (pos_inf && neg_inf) {
2004 Datum::from(Numeric::nan())
2007 } else if pos_inf {
2008 Datum::from(Numeric::infinity())
2009 } else if neg_inf {
2010 let mut cx = numeric::cx_datum();
2011 let mut d = Numeric::infinity();
2012 cx.neg(&mut d);
2013 Datum::from(d)
2014 } else {
2015 Datum::from(d)
2016 }
2017 }
2018 (
2019 AggregateFunc::SumInterval,
2020 Accum::Interval {
2021 months,
2022 days,
2023 micros,
2024 non_nulls: _,
2025 },
2026 ) => {
2027 match Interval::try_new(months.into_inner(), days.into_inner(), micros.into_inner())
2028 {
2029 Some(interval) => Datum::Interval(interval),
2030 None => Datum::Null,
2036 }
2037 }
2038 _ => panic!(
2039 "Unexpected accumulation (aggr={:?}, accum={accum:?})",
2040 aggr_func
2041 ),
2042 }
2043 }
2044}
2045
2046type AccumCount = mz_ore::Overflowing<i128>;
2048
2049#[derive(
2060 Debug,
2061 Clone,
2062 Copy,
2063 PartialEq,
2064 Eq,
2065 PartialOrd,
2066 Ord,
2067 Serialize,
2068 Deserialize,
2069 Columnar
2070)]
2071#[columnar(derive(PartialEq, Eq, PartialOrd, Ord))]
2074enum Accum {
2075 Bool {
2077 trues: Diff,
2079 falses: Diff,
2081 },
2082 SimpleNumber {
2084 accum: AccumCount,
2086 non_nulls: Diff,
2088 },
2089 Float {
2091 accum: AccumCount,
2094 pos_infs: Diff,
2096 neg_infs: Diff,
2098 nans: Diff,
2100 non_nulls: Diff,
2102 },
2103 Interval {
2111 months: AccumCount,
2113 days: AccumCount,
2115 micros: AccumCount,
2117 non_nulls: Diff,
2119 },
2120 Numeric {
2122 accum: OrderedNumericAgg,
2124 pos_infs: Diff,
2126 neg_infs: Diff,
2128 nans: Diff,
2130 non_nulls: Diff,
2132 },
2133}
2134
2135impl IsZero for Accum {
2136 fn is_zero(&self) -> bool {
2137 match self {
2138 Accum::Bool { trues, falses } => trues.is_zero() && falses.is_zero(),
2139 Accum::SimpleNumber { accum, non_nulls } => accum.is_zero() && non_nulls.is_zero(),
2140 Accum::Float {
2141 accum,
2142 pos_infs,
2143 neg_infs,
2144 nans,
2145 non_nulls,
2146 } => {
2147 accum.is_zero()
2148 && pos_infs.is_zero()
2149 && neg_infs.is_zero()
2150 && nans.is_zero()
2151 && non_nulls.is_zero()
2152 }
2153 Accum::Interval {
2154 months,
2155 days,
2156 micros,
2157 non_nulls,
2158 } => months.is_zero() && days.is_zero() && micros.is_zero() && non_nulls.is_zero(),
2159 Accum::Numeric {
2160 accum,
2161 pos_infs,
2162 neg_infs,
2163 nans,
2164 non_nulls,
2165 } => {
2166 accum.0.is_zero()
2167 && pos_infs.is_zero()
2168 && neg_infs.is_zero()
2169 && nans.is_zero()
2170 && non_nulls.is_zero()
2171 }
2172 }
2173 }
2174}
2175
2176impl Semigroup for Accum {
2177 fn plus_equals(&mut self, other: &Accum) {
2178 match (&mut *self, other) {
2179 (
2180 Accum::Bool { trues, falses },
2181 Accum::Bool {
2182 trues: other_trues,
2183 falses: other_falses,
2184 },
2185 ) => {
2186 *trues += other_trues;
2187 *falses += other_falses;
2188 }
2189 (
2190 Accum::SimpleNumber { accum, non_nulls },
2191 Accum::SimpleNumber {
2192 accum: other_accum,
2193 non_nulls: other_non_nulls,
2194 },
2195 ) => {
2196 *accum += other_accum;
2197 *non_nulls += other_non_nulls;
2198 }
2199 (
2200 Accum::Float {
2201 accum,
2202 pos_infs,
2203 neg_infs,
2204 nans,
2205 non_nulls,
2206 },
2207 Accum::Float {
2208 accum: other_accum,
2209 pos_infs: other_pos_infs,
2210 neg_infs: other_neg_infs,
2211 nans: other_nans,
2212 non_nulls: other_non_nulls,
2213 },
2214 ) => {
2215 *accum = accum.checked_add(*other_accum).unwrap_or_else(|| {
2216 warn!("Float accumulator overflow. Incorrect results possible");
2217 accum.wrapping_add(*other_accum)
2218 });
2219 *pos_infs += other_pos_infs;
2220 *neg_infs += other_neg_infs;
2221 *nans += other_nans;
2222 *non_nulls += other_non_nulls;
2223 }
2224 (
2225 Accum::Interval {
2226 months,
2227 days,
2228 micros,
2229 non_nulls,
2230 },
2231 Accum::Interval {
2232 months: other_months,
2233 days: other_days,
2234 micros: other_micros,
2235 non_nulls: other_non_nulls,
2236 },
2237 ) => {
2238 *months += other_months;
2239 *days += other_days;
2240 *micros += other_micros;
2241 *non_nulls += other_non_nulls;
2242 }
2243 (
2244 Accum::Numeric {
2245 accum,
2246 pos_infs,
2247 neg_infs,
2248 nans,
2249 non_nulls,
2250 },
2251 Accum::Numeric {
2252 accum: other_accum,
2253 pos_infs: other_pos_infs,
2254 neg_infs: other_neg_infs,
2255 nans: other_nans,
2256 non_nulls: other_non_nulls,
2257 },
2258 ) => {
2259 let mut cx_agg = numeric::cx_agg();
2260 cx_agg.add(&mut accum.0, &other_accum.0);
2261 assert!(!cx_agg.status().rounded(), "Accum::Numeric overflow");
2267 cx_agg.reduce(&mut accum.0);
2286 *pos_infs += other_pos_infs;
2287 *neg_infs += other_neg_infs;
2288 *nans += other_nans;
2289 *non_nulls += other_non_nulls;
2290 }
2291 (l, r) => unreachable!(
2292 "Accumulator::plus_equals called with non-matching variants: {l:?} vs {r:?}"
2293 ),
2294 }
2295 }
2296}
2297
2298impl Multiply<Diff> for Accum {
2299 type Output = Accum;
2300
2301 fn multiply(self, factor: &Diff) -> Accum {
2302 let factor = *factor;
2303 match self {
2304 Accum::Bool { trues, falses } => Accum::Bool {
2305 trues: trues * factor,
2306 falses: falses * factor,
2307 },
2308 Accum::SimpleNumber { accum, non_nulls } => Accum::SimpleNumber {
2309 accum: accum * AccumCount::from(factor),
2310 non_nulls: non_nulls * factor,
2311 },
2312 Accum::Float {
2313 accum,
2314 pos_infs,
2315 neg_infs,
2316 nans,
2317 non_nulls,
2318 } => Accum::Float {
2319 accum: accum
2320 .checked_mul(AccumCount::from(factor))
2321 .unwrap_or_else(|| {
2322 warn!("Float accumulator overflow. Incorrect results possible");
2323 accum.wrapping_mul(AccumCount::from(factor))
2324 }),
2325 pos_infs: pos_infs * factor,
2326 neg_infs: neg_infs * factor,
2327 nans: nans * factor,
2328 non_nulls: non_nulls * factor,
2329 },
2330 Accum::Interval {
2331 months,
2332 days,
2333 micros,
2334 non_nulls,
2335 } => Accum::Interval {
2336 months: months * AccumCount::from(factor),
2337 days: days * AccumCount::from(factor),
2338 micros: micros * AccumCount::from(factor),
2339 non_nulls: non_nulls * factor,
2340 },
2341 Accum::Numeric {
2342 accum,
2343 pos_infs,
2344 neg_infs,
2345 nans,
2346 non_nulls,
2347 } => {
2348 let mut cx = numeric::cx_agg();
2349 let mut f = NumericAgg::from(factor.into_inner());
2350 cx.mul(&mut f, &accum.0);
2354 assert!(!cx.status().rounded(), "Accum::Numeric multiply overflow");
2360 Accum::Numeric {
2361 accum: OrderedNumericAgg(f),
2362 pos_infs: pos_infs * factor,
2363 neg_infs: neg_infs * factor,
2364 nans: nans * factor,
2365 non_nulls: non_nulls * factor,
2366 }
2367 }
2368 }
2369 }
2370}
2371
2372impl Columnation for Accum {
2375 type InnerRegion = CopyRegion<Self>;
2376}
2377
2378mod monoids {
2380
2381 use columnation::{Columnation, Region};
2397 use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
2398 use mz_expr::AggregateFunc;
2399 use mz_ore::soft_panic_or_log;
2400 use mz_repr::{Datum, Diff, Row};
2401 use serde::{Deserialize, Serialize};
2402
2403 #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
2405 pub enum ReductionMonoid {
2406 Min(Row),
2407 Max(Row),
2408 }
2409
2410 impl ReductionMonoid {
2411 pub fn finalize(&self) -> &Row {
2412 use ReductionMonoid::*;
2413 match self {
2414 Min(row) | Max(row) => row,
2415 }
2416 }
2417 }
2418
2419 impl Clone for ReductionMonoid {
2420 fn clone(&self) -> Self {
2421 use ReductionMonoid::*;
2422 match self {
2423 Min(row) => Min(row.clone()),
2424 Max(row) => Max(row.clone()),
2425 }
2426 }
2427
2428 fn clone_from(&mut self, source: &Self) {
2429 use ReductionMonoid::*;
2430
2431 let mut row = std::mem::take(match self {
2432 Min(row) | Max(row) => row,
2433 });
2434
2435 let source_row = match source {
2436 Min(row) | Max(row) => row,
2437 };
2438
2439 row.clone_from(source_row);
2440
2441 match source {
2442 Min(_) => *self = Min(row),
2443 Max(_) => *self = Max(row),
2444 }
2445 }
2446 }
2447
2448 impl Multiply<Diff> for ReductionMonoid {
2449 type Output = Self;
2450
2451 fn multiply(self, factor: &Diff) -> Self {
2452 assert!(factor.is_positive());
2457 self
2458 }
2459 }
2460
2461 impl Semigroup for ReductionMonoid {
2462 fn plus_equals(&mut self, rhs: &Self) {
2463 match (self, rhs) {
2464 (ReductionMonoid::Min(lhs), ReductionMonoid::Min(rhs)) => {
2465 let swap = {
2466 let lhs_val = lhs.unpack_first();
2467 let rhs_val = rhs.unpack_first();
2468 match (lhs_val, rhs_val) {
2470 (_, Datum::Null) => false,
2471 (Datum::Null, _) => true,
2472 (lhs, rhs) => rhs < lhs,
2473 }
2474 };
2475 if swap {
2476 lhs.clone_from(rhs);
2477 }
2478 }
2479 (ReductionMonoid::Max(lhs), ReductionMonoid::Max(rhs)) => {
2480 let swap = {
2481 let lhs_val = lhs.unpack_first();
2482 let rhs_val = rhs.unpack_first();
2483 match (lhs_val, rhs_val) {
2485 (_, Datum::Null) => false,
2486 (Datum::Null, _) => true,
2487 (lhs, rhs) => rhs > lhs,
2488 }
2489 };
2490 if swap {
2491 lhs.clone_from(rhs);
2492 }
2493 }
2494 (lhs, rhs) => {
2495 soft_panic_or_log!(
2496 "Mismatched monoid variants in reduction! lhs: {lhs:?} rhs: {rhs:?}"
2497 );
2498 }
2499 }
2500 }
2501 }
2502
2503 impl IsZero for ReductionMonoid {
2504 fn is_zero(&self) -> bool {
2505 false
2511 }
2512 }
2513
2514 impl Columnation for ReductionMonoid {
2515 type InnerRegion = ReductionMonoidRegion;
2516 }
2517
2518 #[derive(Default)]
2522 pub struct ReductionMonoidRegion {
2523 inner: <Row as Columnation>::InnerRegion,
2524 }
2525
2526 impl Region for ReductionMonoidRegion {
2527 type Item = ReductionMonoid;
2528
2529 unsafe fn copy(&mut self, item: &Self::Item) -> Self::Item {
2530 use ReductionMonoid::*;
2531 match item {
2532 Min(row) => Min(unsafe { self.inner.copy(row) }),
2533 Max(row) => Max(unsafe { self.inner.copy(row) }),
2534 }
2535 }
2536
2537 fn clear(&mut self) {
2538 self.inner.clear();
2539 }
2540
2541 fn reserve_items<'a, I>(&mut self, items: I)
2542 where
2543 Self: 'a,
2544 I: Iterator<Item = &'a Self::Item> + Clone,
2545 {
2546 self.inner
2547 .reserve_items(items.map(ReductionMonoid::finalize));
2548 }
2549
2550 fn reserve_regions<'a, I>(&mut self, regions: I)
2551 where
2552 Self: 'a,
2553 I: Iterator<Item = &'a Self> + Clone,
2554 {
2555 self.inner.reserve_regions(regions.map(|r| &r.inner));
2556 }
2557
2558 fn heap_size(&self, callback: impl FnMut(usize, usize)) {
2559 self.inner.heap_size(callback);
2560 }
2561 }
2562
2563 pub fn get_monoid(row: Row, func: &AggregateFunc) -> Option<ReductionMonoid> {
2566 match func {
2567 AggregateFunc::MaxNumeric
2568 | AggregateFunc::MaxInt16
2569 | AggregateFunc::MaxInt32
2570 | AggregateFunc::MaxInt64
2571 | AggregateFunc::MaxUInt16
2572 | AggregateFunc::MaxUInt32
2573 | AggregateFunc::MaxUInt64
2574 | AggregateFunc::MaxMzTimestamp
2575 | AggregateFunc::MaxFloat32
2576 | AggregateFunc::MaxFloat64
2577 | AggregateFunc::MaxBool
2578 | AggregateFunc::MaxString
2579 | AggregateFunc::MaxDate
2580 | AggregateFunc::MaxTimestamp
2581 | AggregateFunc::MaxTimestampTz
2582 | AggregateFunc::MaxInterval
2583 | AggregateFunc::MaxTime
2584 | AggregateFunc::MaxArray
2585 | AggregateFunc::MaxList => Some(ReductionMonoid::Max(row)),
2586 AggregateFunc::MinNumeric
2587 | AggregateFunc::MinInt16
2588 | AggregateFunc::MinInt32
2589 | AggregateFunc::MinInt64
2590 | AggregateFunc::MinUInt16
2591 | AggregateFunc::MinUInt32
2592 | AggregateFunc::MinUInt64
2593 | AggregateFunc::MinMzTimestamp
2594 | AggregateFunc::MinFloat32
2595 | AggregateFunc::MinFloat64
2596 | AggregateFunc::MinBool
2597 | AggregateFunc::MinString
2598 | AggregateFunc::MinDate
2599 | AggregateFunc::MinTimestamp
2600 | AggregateFunc::MinTimestampTz
2601 | AggregateFunc::MinInterval
2602 | AggregateFunc::MinTime
2603 | AggregateFunc::MinArray
2604 | AggregateFunc::MinList => Some(ReductionMonoid::Min(row)),
2605 AggregateFunc::SumInt16
2606 | AggregateFunc::SumInt32
2607 | AggregateFunc::SumInt64
2608 | AggregateFunc::SumUInt16
2609 | AggregateFunc::SumUInt32
2610 | AggregateFunc::SumUInt64
2611 | AggregateFunc::SumFloat32
2612 | AggregateFunc::SumFloat64
2613 | AggregateFunc::SumNumeric
2614 | AggregateFunc::SumInterval
2615 | AggregateFunc::Count
2616 | AggregateFunc::Any
2617 | AggregateFunc::All
2618 | AggregateFunc::Dummy
2619 | AggregateFunc::JsonbAgg { .. }
2620 | AggregateFunc::JsonbObjectAgg { .. }
2621 | AggregateFunc::MapAgg { .. }
2622 | AggregateFunc::ArrayConcat { .. }
2623 | AggregateFunc::ListConcat { .. }
2624 | AggregateFunc::StringAgg { .. }
2625 | AggregateFunc::RowNumber { .. }
2626 | AggregateFunc::Rank { .. }
2627 | AggregateFunc::DenseRank { .. }
2628 | AggregateFunc::LagLead { .. }
2629 | AggregateFunc::FirstValue { .. }
2630 | AggregateFunc::LastValue { .. }
2631 | AggregateFunc::WindowAggregate { .. }
2632 | AggregateFunc::FusedValueWindowFunc { .. }
2633 | AggregateFunc::FusedWindowAggregate { .. } => None,
2634 }
2635 }
2636}
2637
2638mod window_agg_helpers {
2639 use crate::render::reduce::*;
2640
2641 pub enum OneByOneAggrImpls {
2646 Accumulable(AccumulableOneByOneAggr),
2647 Hierarchical(HierarchicalOneByOneAggr),
2648 Basic(mz_expr::NaiveOneByOneAggr),
2649 }
2650
2651 impl mz_expr::OneByOneAggr for OneByOneAggrImpls {
2652 fn new(agg: &AggregateFunc, reverse: bool) -> Self {
2653 match reduction_type(agg) {
2654 ReductionType::Basic => {
2655 OneByOneAggrImpls::Basic(mz_expr::NaiveOneByOneAggr::new(agg, reverse))
2656 }
2657 ReductionType::Accumulable => {
2658 OneByOneAggrImpls::Accumulable(AccumulableOneByOneAggr::new(agg))
2659 }
2660 ReductionType::Hierarchical => {
2661 OneByOneAggrImpls::Hierarchical(HierarchicalOneByOneAggr::new(agg))
2662 }
2663 }
2664 }
2665
2666 fn give(&mut self, d: &Datum) {
2667 match self {
2668 OneByOneAggrImpls::Basic(i) => i.give(d),
2669 OneByOneAggrImpls::Accumulable(i) => i.give(d),
2670 OneByOneAggrImpls::Hierarchical(i) => i.give(d),
2671 }
2672 }
2673
2674 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2675 match self {
2677 OneByOneAggrImpls::Basic(i) => i.get_current_aggregate(temp_storage),
2678 OneByOneAggrImpls::Accumulable(i) => i.get_current_aggregate(temp_storage),
2679 OneByOneAggrImpls::Hierarchical(i) => i.get_current_aggregate(temp_storage),
2680 }
2681 }
2682 }
2683
2684 pub struct AccumulableOneByOneAggr {
2685 aggr_func: AggregateFunc,
2686 accum: Accum,
2687 total: Diff,
2688 }
2689
2690 impl AccumulableOneByOneAggr {
2691 fn new(aggr_func: &AggregateFunc) -> Self {
2692 AccumulableOneByOneAggr {
2693 aggr_func: aggr_func.clone(),
2694 accum: accumulable_zero(aggr_func),
2695 total: Diff::ZERO,
2696 }
2697 }
2698
2699 fn give(&mut self, d: &Datum) {
2700 self.accum
2701 .plus_equals(&datum_to_accumulator(&self.aggr_func, d.clone()));
2702 self.total += Diff::ONE;
2703 }
2704
2705 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2706 temp_storage.make_datum(|packer| {
2707 packer.push(finalize_accum(&self.aggr_func, &self.accum, self.total));
2708 })
2709 }
2710 }
2711
2712 pub struct HierarchicalOneByOneAggr {
2713 aggr_func: AggregateFunc,
2714 monoid: ReductionMonoid,
2717 }
2718
2719 impl HierarchicalOneByOneAggr {
2720 fn new(aggr_func: &AggregateFunc) -> Self {
2721 let mut row_buf = Row::default();
2722 row_buf.packer().push(Datum::Null);
2723 HierarchicalOneByOneAggr {
2724 aggr_func: aggr_func.clone(),
2725 monoid: get_monoid(row_buf, aggr_func)
2726 .expect("aggr_func should be a hierarchical aggregation function"),
2727 }
2728 }
2729
2730 fn give(&mut self, d: &Datum) {
2731 let mut row_buf = Row::default();
2732 row_buf.packer().push(d);
2733 let m = get_monoid(row_buf, &self.aggr_func)
2734 .expect("aggr_func should be a hierarchical aggregation function");
2735 self.monoid.plus_equals(&m);
2736 }
2737
2738 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2739 temp_storage.make_datum(|packer| packer.extend(self.monoid.finalize().iter()))
2740 }
2741 }
2742}
2743
2744#[cfg(test)]
2745mod tests {
2746 use super::*;
2747
2748 #[allow(clippy::as_conversions)]
2752 fn saturating_convert(n: f64) -> i128 {
2753 (n * FLOAT_SCALE) as i128
2754 }
2755
2756 #[mz_ore::test]
2757 fn float_to_fixed_point_matches_saturating_in_range() {
2758 let cases = [
2762 0.0,
2763 -0.0,
2764 1.0,
2765 -1.0,
2766 0.1,
2767 -0.1,
2768 0.5,
2769 -0.5,
2770 3.25,
2771 -3.25,
2772 123456.789,
2773 -123456.789,
2774 1e10,
2775 -1e10,
2776 1e20,
2777 -1e20,
2778 5e30, -5e30,
2780 ];
2781 for n in cases {
2782 assert_eq!(
2783 float_to_fixed_point(n),
2784 saturating_convert(n),
2785 "mismatch for n = {n}"
2786 );
2787 }
2788 }
2789
2790 #[mz_ore::test]
2791 fn float_to_fixed_point_truncates_toward_zero() {
2792 assert_eq!(float_to_fixed_point(1.75), 29_360_128);
2794 assert_eq!(float_to_fixed_point(-1.75), -29_360_128);
2795
2796 let frac = 0.123_456_7_f64;
2798 assert_eq!(float_to_fixed_point(frac), saturating_convert(frac));
2799 assert_eq!(float_to_fixed_point(-frac), saturating_convert(-frac));
2800 assert_eq!(float_to_fixed_point(-frac), -float_to_fixed_point(frac));
2801 }
2802
2803 #[mz_ore::test]
2804 fn float_to_fixed_point_subnormals_round_to_zero() {
2805 assert_eq!(float_to_fixed_point(0.0), 0);
2806 assert_eq!(float_to_fixed_point(-0.0), 0);
2807 assert_eq!(float_to_fixed_point(f64::MIN_POSITIVE / 2.0), 0);
2808 assert_eq!(float_to_fixed_point(5e-324), 0); }
2810
2811 #[mz_ore::test]
2812 fn float_to_fixed_point_cancels_large_finite_values() {
2813 for &n in &[1.1e31_f64, 1e32, 5e33, 1e284] {
2818 assert_eq!(
2819 float_to_fixed_point(n).wrapping_add(float_to_fixed_point(-n)),
2820 0,
2821 "n = {n} did not cancel with -n"
2822 );
2823 }
2824 }
2825
2826 #[mz_ore::test]
2827 fn float_to_fixed_point_sum_via_accumulator() {
2828 let func = AggregateFunc::SumFloat64;
2830 let mut acc = accumulable_zero(&func);
2831 acc.plus_equals(&datum_to_accumulator(&func, Datum::from(1.1e31_f64)));
2832 acc.plus_equals(&datum_to_accumulator(&func, Datum::from(-1.1e31_f64)));
2833 let datum = finalize_accum(&func, &acc, Diff::from(2_i64));
2834 assert_eq!(datum, Datum::from(0.0_f64));
2835 }
2836
2837 fn sample_accums() -> Vec<Accum> {
2839 let mut cx = numeric::cx_datum();
2840 let mut numeric = |s: &str| Datum::from(cx.parse(s).unwrap());
2841 let cases: Vec<(AggregateFunc, Vec<Datum>)> = vec![
2842 (AggregateFunc::Count, vec![Datum::Null, Datum::Int64(5)]),
2843 (
2844 AggregateFunc::SumInt64,
2845 vec![Datum::Int64(-7), Datum::Int64(i64::MAX)],
2846 ),
2847 (
2848 AggregateFunc::SumUInt16,
2849 vec![Datum::UInt16(3), Datum::Null],
2850 ),
2851 (
2852 AggregateFunc::Any,
2853 vec![Datum::True, Datum::False, Datum::Null],
2854 ),
2855 (
2856 AggregateFunc::SumFloat64,
2857 vec![
2858 Datum::from(1.5_f64),
2859 Datum::from(f64::NAN),
2860 Datum::from(f64::NEG_INFINITY),
2861 ],
2862 ),
2863 (
2864 AggregateFunc::SumNumeric,
2865 vec![
2866 numeric("-12345.678"),
2867 numeric("9e39"),
2868 numeric("NaN"),
2869 numeric("Infinity"),
2870 Datum::Null,
2871 ],
2872 ),
2873 (
2874 AggregateFunc::SumInterval,
2875 vec![
2876 Datum::Interval(Interval::new(-13, 40, 1_234_567)),
2877 Datum::Interval(Interval::new(i32::MAX, i32::MIN, i64::MAX)),
2878 Datum::Null,
2879 ],
2880 ),
2881 ];
2882 let mut accums = Vec::new();
2883 for (func, datums) in cases {
2884 let mut sum = accumulable_zero(&func);
2885 accums.push(sum);
2886 for datum in datums {
2887 let accum = datum_to_accumulator(&func, datum);
2888 sum.plus_equals(&accum);
2889 accums.push(accum);
2890 accums.push(accum.multiply(&Diff::from(-1_i64)));
2891 }
2892 accums.push(sum);
2893 }
2894 accums
2895 }
2896
2897 #[mz_ore::test]
2898 fn accum_columnar_round_trip() {
2899 use columnar::bytes::indexed::{DecodedStore, encode};
2900 use columnar::{AsBytes, Borrow, BorrowedOf, FromBytes, Index, Len};
2901 use differential_dataflow::trace::implementations::BatchContainer;
2902
2903 let accums = sample_accums();
2904 let container = Accum::as_columns(accums.iter());
2905 assert_eq!(container.len(), accums.len());
2906 let borrowed = container.borrow();
2907 for (index, accum) in accums.iter().enumerate() {
2908 assert_eq!(Accum::into_owned(borrowed.get(index)), *accum);
2909 }
2910 for (i, a) in accums.iter().enumerate() {
2911 for (j, b) in accums.iter().enumerate() {
2912 assert_eq!(borrowed.get(i).cmp(&borrowed.get(j)), a.cmp(b));
2913 }
2914 }
2915
2916 let bytes: Vec<&[u8]> = borrowed.as_bytes().map(|(_align, bytes)| bytes).collect();
2917 let decoded = BorrowedOf::<Accum>::from_bytes(&mut bytes.into_iter());
2918 for (index, accum) in accums.iter().enumerate() {
2919 assert_eq!(Accum::into_owned(decoded.get(index)), *accum);
2920 }
2921 let mut words = Vec::new();
2924 encode(&mut words, &borrowed);
2925 let decoded = BorrowedOf::<Accum>::from_store(&DecodedStore::new(&words), &mut 0);
2926 for (index, accum) in accums.iter().enumerate() {
2927 assert_eq!(Accum::into_owned(decoded.get(index)), *accum);
2928 }
2929
2930 let diffs: Vec<(Vec<Accum>, Diff)> = accums
2932 .chunks(3)
2933 .map(|chunk| (chunk.to_vec(), Diff::ONE))
2934 .collect();
2935 let mut coltainer = Coltainer::<(Vec<Accum>, Diff)>::default();
2936 for diff in &diffs {
2937 coltainer.push_own(diff);
2938 }
2939 assert_eq!(coltainer.len(), diffs.len());
2940 for (index, diff) in diffs.iter().enumerate() {
2941 assert_eq!(
2942 <Coltainer<(Vec<Accum>, Diff)>>::into_owned(coltainer.index(index)),
2943 *diff
2944 );
2945 }
2946 let mut sum = <Coltainer<(Vec<Accum>, Diff)>>::into_owned(coltainer.index(0));
2947 sum.plus_equals(&sum.clone().multiply(&Diff::from(-1_i64)));
2948 assert!(sum.is_zero());
2949 }
2950}