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_timely_util::columnation::ColumnationChunker;
44use mz_timely_util::operator::CollectionExt;
45use num_traits::Float;
46use serde::{Deserialize, Serialize};
47use timely::Container;
48use timely::container::{CapacityContainerBuilder, PushInto};
49use tracing::warn;
50
51use crate::extensions::arrange::{ArrangementSize, KeyCollection, MzArrange};
52use crate::extensions::reduce::{ClearContainer, MzReduce};
53use crate::render::context::{CollectionBundle, Context};
54use crate::render::errors::DataflowErrorSer;
55use crate::render::errors::MaybeValidatingRow;
56use crate::render::reduce::monoids::{ReductionMonoid, get_monoid};
57use crate::render::{ArrangementFlavor, Pairer, RenderTimestamp};
58use crate::typedefs::{
59 ErrBatcher, ErrBuilder, KeyBatcher, RowAgent, RowErrBuilder, RowErrSpine, RowRowAgent,
60 RowRowArrangement, RowRowSpine, RowSpine, RowValSpine,
61};
62use mz_row_spine::{
63 DatumContainer, DatumSeq, RowBatcher, RowBuilder, RowRowBatcher, RowRowBuilder, RowValBatcher,
64 RowValBuilder,
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::<ColumnationChunker<_>, ErrBatcher<_, _>, ErrBuilder<_, _>, _>(
214 "Arrange bundle err",
215 ),
216 ),
217 )
218 }
219
220 fn render_reduce_plan_inner<'s>(
221 &self,
222 plan: ReducePlan,
223 collection: VecCollection<'s, T, (Row, Row), Diff>,
224 errors: &mut Vec<VecCollection<'s, T, DataflowErrorSer, Diff>>,
225 key_arity: usize,
226 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
227 ) -> Arranged<'s, RowRowAgent<T, Diff>> {
228 let arrangement = match plan {
231 ReducePlan::Distinct => {
234 let (arranged_output, errs) = self.build_distinct(collection, mfp_after);
235 errors.push(errs);
236 arranged_output
237 }
238 ReducePlan::Accumulable(expr) => {
239 let (arranged_output, errs) =
240 self.build_accumulable(collection, expr, key_arity, mfp_after);
241 errors.push(errs);
242 arranged_output
243 }
244 ReducePlan::Hierarchical(HierarchicalPlan::Monotonic(expr)) => {
245 let (output, errs) = self.build_monotonic(collection, expr, mfp_after);
246 errors.push(errs);
247 output
248 }
249 ReducePlan::Hierarchical(HierarchicalPlan::Bucketed(expr)) => {
250 let (output, errs) = self.build_bucketed(collection, expr, key_arity, mfp_after);
251 errors.push(errs);
252 output
253 }
254 ReducePlan::Basic(BasicPlan::Single(SingleBasicPlan {
255 expr,
256 fused_unnest_list,
257 })) => {
258 let validating = !fused_unnest_list;
262 let (output, errs) = self.build_basic_aggregate(
263 collection,
264 0,
265 &expr,
266 validating,
267 key_arity,
268 mfp_after,
269 fused_unnest_list,
270 );
271 if validating {
272 errors.push(errs.expect("validation should have occurred as it was requested"));
273 }
274 output
275 }
276 ReducePlan::Basic(BasicPlan::Multiple(aggrs)) => {
277 let (output, errs) =
278 self.build_basic_aggregates(collection, aggrs, key_arity, mfp_after);
279 errors.push(errs);
280 output
281 }
282 };
283 arrangement
284 }
285
286 fn build_distinct<'s>(
288 &self,
289 collection: VecCollection<'s, T, (Row, Row), Diff>,
290 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
291 ) -> (
292 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
293 VecCollection<'s, T, DataflowErrorSer, Diff>,
294 ) {
295 let error_logger = self.error_logger();
296
297 let mut datums1 = DatumVec::new();
299 let mut datums2 = DatumVec::new();
300 let mfp_after1 = mfp_after.clone();
301 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
302
303 let arranged = collection
304 .mz_arrange::<
305 ColumnationChunker<_>,
306 RowRowBatcher<_, _>,
307 RowRowBuilder<_, _>,
308 RowRowSpine<_, _>,
309 >(
310 "Arranged DistinctBy",
311 );
312 let output = arranged
313 .clone()
314 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
315 "DistinctBy",
316 move |key, _input, output| {
317 let temp_storage = RowArena::new();
318 let mut datums_local = datums1.borrow();
319 key.extend_datums(&temp_storage, &mut datums_local, None);
320
321 if mfp_after1
325 .as_ref()
326 .map(|mfp| mfp.evaluate_inner(&mut datums_local, &temp_storage))
327 .unwrap_or(Ok(true))
328 == Ok(true)
329 {
330 output.push((Row::default(), Diff::ONE));
334 }
335 },
336 );
337 let errors = arranged.mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
338 "DistinctByErrorCheck",
339 move |key, input: &[(_, Diff)], output: &mut Vec<(DataflowErrorSer, _)>| {
340 for (_, count) in input.iter() {
341 if count.is_positive() {
342 continue;
343 }
344 let message = "Non-positive multiplicity in DistinctBy";
345 error_logger.log(message, &format!("row={key:?}, count={count}"));
346 output.push((EvalError::Internal(message.into()).into(), Diff::ONE));
347 return;
348 }
349 let Some(mfp) = &mfp_after2 else { return };
351 let temp_storage = RowArena::new();
352 let mut datums_local = datums2.borrow();
353 key.extend_datums(&temp_storage, &mut datums_local, None);
354
355 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
356 output.push((e.into(), Diff::ONE));
357 }
358 },
359 );
360 (output, errors.as_collection(|_k, v| v.clone()))
361 }
362
363 fn build_basic_aggregates<'s>(
371 &self,
372 input: VecCollection<'s, T, (Row, Row), Diff>,
373 aggrs: Vec<LirAggregateExpr>,
374 key_arity: usize,
375 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
376 ) -> (
377 RowRowArrangement<'s, T>,
378 VecCollection<'s, T, DataflowErrorSer, Diff>,
379 ) {
380 if aggrs.len() <= 1 {
383 self.error_logger().soft_panic_or_log(
384 "Too few aggregations when building basic aggregates",
385 &format!("len={}", aggrs.len()),
386 )
387 }
388 let mut err_output = None;
389 let mut to_collect = Vec::new();
390 for (index, aggr) in aggrs.into_iter().enumerate() {
391 let (result, errs) = self.build_basic_aggregate(
392 input.clone(),
393 index,
394 &aggr,
395 err_output.is_none(),
396 key_arity,
397 None,
398 false,
399 );
400 if errs.is_some() {
401 err_output = errs
402 }
403 to_collect
404 .push(result.as_collection(move |key, val| (key.to_row(), (index, val.to_row()))));
405 }
406
407 let mut datums1 = DatumVec::new();
409 let mut datums2 = DatumVec::new();
410 let mfp_after1 = mfp_after.clone();
411 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
412
413 let arranged = differential_dataflow::collection::concatenate(input.scope(), to_collect)
414 .mz_arrange::<
415 ColumnationChunker<_>,
416 RowValBatcher<_, _, _>,
417 RowValBuilder<_, _, _>,
418 RowValSpine<_, _, _>,
419 >(
420 "Arranged ReduceFuseBasic input",
421 );
422
423 let output = arranged
424 .clone()
425 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
426 "ReduceFuseBasic",
427 {
428 move |key, input, output| {
429 let temp_storage = RowArena::new();
430 let mut datums_local = datums1.borrow();
431 key.extend_datums(&temp_storage, &mut datums_local, None);
432 let key_len = datums_local.len();
433
434 for ((_, row), _) in input.iter() {
435 datums_local.push(row.unpack_first());
436 }
437
438 if let Some(row) = evaluate_mfp_after(
439 &mfp_after1,
440 &mut datums_local,
441 &temp_storage,
442 key_len,
443 ) {
444 output.push((row, Diff::ONE));
445 }
446 }
447 },
448 );
449 let validation_errs = err_output.expect("expected to validate in at least one aggregate");
454 if let Some(mfp) = mfp_after2 {
455 let mfp_errs = arranged
456 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
457 "ReduceFuseBasic Error Check",
458 move |key, input, output| {
459 let temp_storage = RowArena::new();
462 let mut datums_local = datums2.borrow();
463 key.extend_datums(&temp_storage, &mut datums_local, None);
464
465 for ((_, row), _) in input.iter() {
466 datums_local.push(row.unpack_first());
467 }
468
469 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
470 output.push((e.into(), Diff::ONE));
471 }
472 },
473 )
474 .as_collection(|_, v| v.clone());
475 (output, validation_errs.concat(mfp_errs))
476 } else {
477 (output, validation_errs)
478 }
479 }
480
481 fn build_basic_aggregate<'s>(
485 &self,
486 input: VecCollection<'s, T, (Row, Row), Diff>,
487 index: usize,
488 aggr: &LirAggregateExpr,
489 validating: bool,
490 key_arity: usize,
491 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
492 fused_unnest_list: bool,
493 ) -> (
494 RowRowArrangement<'s, T>,
495 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
496 ) {
497 let LirAggregateExpr {
498 func,
499 expr: _,
500 distinct,
501 } = aggr.clone();
502
503 let mut partial = input.map(move |(key, row)| {
505 let mut row_builder = SharedRow::get();
506 let value = row.iter().nth(index).unwrap();
507 row_builder.packer().push(value);
508 (key, row_builder.clone())
509 });
510
511 let mut err_output = None;
512
513 if distinct {
515 let pairer = Pairer::new(key_arity);
517 let keyed = partial.map(move |(key, val)| pairer.merge(&key, &val));
518 if validating {
519 let (oks, errs) = self
520 .build_reduce_inaccumulable_distinct::<
521 RowValBuilder<Result<(), String>, _, _>,
522 RowValSpine<Result<(), String>, _, _>,
523 >(keyed, None)
524 .as_collection(|k, v| {
525 (
526 k.to_row(),
527 v.as_ref()
528 .map(|&()| ())
529 .map_err(|m| m.as_str().into()),
530 )
531 })
532 .map_fallible::<
533 CapacityContainerBuilder<_>,
534 CapacityContainerBuilder<_>,
535 _,
536 _,
537 _,
538 >(
539 "Demux Errors",
540 move |(key_val, result)| match result {
541 Ok(()) => Ok(pairer.split(&key_val)),
542 Err(m) => {
543 Err(EvalError::Internal(m).into())
544 }
545 },
546 );
547 err_output = Some(errs);
548 partial = oks;
549 } else {
550 partial = self
551 .build_reduce_inaccumulable_distinct::<RowBuilder<_, _>, RowSpine<_, _>>(
552 keyed,
553 Some(" [val: empty]"),
554 )
555 .as_collection(move |key_val_iter, _| pairer.split(key_val_iter));
556 }
557 }
558
559 let mut datums1 = DatumVec::new();
561 let mut datums2 = DatumVec::new();
562 let mut datums_key_1 = DatumVec::new();
563 let mut datums_key_2 = DatumVec::new();
564 let mut vals1 = DatumVec::new();
568 let mut vals2 = DatumVec::new();
569 let mut vals_key_1 = DatumVec::new();
570 let mut vals_key_2 = DatumVec::new();
571 let mfp_after1 = mfp_after.clone();
572 let func2 = func.clone();
573
574 let name = if !fused_unnest_list {
575 "ReduceInaccumulable"
576 } else {
577 "FusedReduceUnnestList"
578 };
579 let arranged = partial
580 .mz_arrange::<
581 ColumnationChunker<_>,
582 RowRowBatcher<_, _>,
583 RowRowBuilder<_, _>,
584 RowRowSpine<_, _>,
585 >(&format!(
586 "Arranged {name}"
587 ));
588 let oks = if !fused_unnest_list {
589 arranged
590 .clone()
591 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(name, {
592 move |key, source, target| {
593 let temp_storage = RowArena::new();
594 let mut val_scratch = vals1.borrow();
601 let iter = source.iter().map(|(v, w)| {
602 val_scratch.clear();
603 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
604 (val_scratch[0], *w)
605 });
606
607 let mut datums_local = datums1.borrow();
608 key.extend_datums(&temp_storage, &mut datums_local, None);
609 let key_len = datums_local.len();
610 datums_local.push(
611 func.eval_with_fast_window_agg::<_, window_agg_helpers::OneByOneAggrImpls>(
614 iter,
615 &temp_storage,
616 ),
617 );
618
619 if let Some(row) = evaluate_mfp_after(
620 &mfp_after1,
621 &mut datums_local,
622 &temp_storage,
623 key_len,
624 ) {
625 target.push((row, Diff::ONE));
626 }
627 }
628 })
629 } else {
630 arranged
631 .clone()
632 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(name, {
633 move |key, source, target| {
634 let temp_storage = RowArena::new();
636 let mut val_scratch = vals_key_1.borrow();
637 let iter = source.iter().map(|(v, w)| {
638 val_scratch.clear();
639 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
640 (val_scratch[0], *w)
641 });
642
643 let mut datums_local = datums_key_1.borrow();
645 key.extend_datums(&temp_storage, &mut datums_local, None);
646 let key_len = datums_local.len();
647 for datum in func
648 .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
649 iter,
650 &temp_storage,
651 )
652 {
653 datums_local.truncate(key_len);
654 datums_local.push(datum);
655 if let Some(row) = evaluate_mfp_after(
656 &mfp_after1,
657 &mut datums_local,
658 &temp_storage,
659 key_len,
660 ) {
661 target.push((row, Diff::ONE));
662 }
663 }
664 }
665 })
666 };
667
668 let must_validate = validating && err_output.is_none();
672 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
673 if must_validate || mfp_after2.is_some() {
674 let error_logger = self.error_logger();
675
676 let errs = if !fused_unnest_list {
677 arranged
678 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
679 &format!("{name} Error Check"),
680 move |key, source, target| {
681 if must_validate {
685 for (value, count) in source.iter() {
686 if count.is_positive() {
687 continue;
688 }
689 let value = value.to_row();
690 let message =
691 "Non-positive accumulation in ReduceInaccumulable";
692 error_logger
693 .log(message, &format!("value={value:?}, count={count}"));
694 let err = EvalError::Internal(message.into());
695 target.push((err.into(), Diff::ONE));
696 return;
697 }
698 }
699
700 let Some(mfp) = &mfp_after2 else { return };
702 let temp_storage = RowArena::new();
703 let mut val_scratch = vals2.borrow();
704 let iter = source.iter().map(|(v, w)| {
705 val_scratch.clear();
706 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
707 (val_scratch[0], *w)
708 });
709
710 let mut datums_local = datums2.borrow();
711 key.extend_datums(&temp_storage, &mut datums_local, None);
712 datums_local.push(
713 func2.eval_with_fast_window_agg::<
714 _,
715 window_agg_helpers::OneByOneAggrImpls,
716 >(
717 iter, &temp_storage
718 ),
719 );
720 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
721 target.push((e.into(), Diff::ONE));
722 }
723 },
724 )
725 .as_collection(|_, v| v.clone())
726 } else {
727 assert!(!must_validate);
729 let Some(mfp) = mfp_after2 else {
732 unreachable!()
733 };
734 arranged
735 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
736 &format!("{name} Error Check"),
737 move |key, source, target| {
738 let temp_storage = RowArena::new();
739 let mut val_scratch = vals_key_2.borrow();
740 let iter = source.iter().map(|(v, w)| {
741 val_scratch.clear();
742 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
743 (val_scratch[0], *w)
744 });
745
746 let mut datums_local = datums_key_2.borrow();
747 key.extend_datums(&temp_storage, &mut datums_local, None);
748 let key_len = datums_local.len();
749 for datum in func2
750 .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
751 iter,
752 &temp_storage,
753 )
754 {
755 datums_local.truncate(key_len);
756 datums_local.push(datum);
757 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
760 {
761 target.push((e.into(), Diff::ONE));
762 }
763 }
764 },
765 )
766 .as_collection(|_, v| v.clone())
767 };
768
769 if let Some(e) = err_output {
770 err_output = Some(e.concat(errs));
771 } else {
772 err_output = Some(errs);
773 }
774 }
775 (oks, err_output)
776 }
777
778 fn build_reduce_inaccumulable_distinct<'s, Bu, Tr>(
779 &self,
780 input: VecCollection<'s, T, Row, Diff>,
781 name_tag: Option<&str>,
782 ) -> Arranged<'s, TraceAgent<Tr>>
783 where
784 Tr: Trace<Batch: Navigable, Time = T> + 'static,
785 for<'a> BatchCursor<Tr>: Cursor<
786 Key<'a> = DatumSeq<'a>,
787 KeyContainer = DatumContainer,
788 Time = T,
789 Diff = Diff,
790 ValOwn: Data + MaybeValidatingRow<(), String>,
791 >,
792 Bu: Builder<
793 Time = T,
794 Input: Container
795 + ClearContainer
796 + PushInto<((Row, BatchValOwn<Tr>), Tr::Time, BatchDiff<Tr>)>,
797 Output = Tr::Batch,
798 > + 'static,
799 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
800 {
801 let error_logger = self.error_logger();
802
803 let output_name = format!(
804 "ReduceInaccumulable Distinct{}",
805 name_tag.unwrap_or_default()
806 );
807
808 let input: KeyCollection<_, _, _> = input.into();
809 let arranged = input.mz_arrange::<
810 ColumnationChunker<_>,
811 RowBatcher<_, _>,
812 RowBuilder<_, _>,
813 RowSpine<_, _>,
814 >(
815 "Arranged ReduceInaccumulable Distinct [val: empty]",
816 );
817 arranged.mz_reduce_abelian::<_, Bu, Tr, _>(&output_name, move |_, source, t| {
818 if let Some(err) = BatchValOwn::<Tr>::into_error() {
819 for (value, count) in source.iter() {
820 if count.is_positive() {
821 continue;
822 }
823
824 let message = "Non-positive accumulation in ReduceInaccumulable DISTINCT";
825 error_logger.log(message, &format!("value={value:?}, count={count}"));
826 t.push((err(message.to_string()), Diff::ONE));
827 return;
828 }
829 }
830 t.push((BatchValOwn::<Tr>::ok(()), Diff::ONE))
831 })
832 }
833
834 fn build_bucketed<'s>(
852 &self,
853 input: VecCollection<'s, T, (Row, Row), Diff>,
854 BucketedPlan {
855 aggr_funcs,
856 buckets,
857 }: BucketedPlan,
858 key_arity: usize,
859 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
860 ) -> (
861 RowRowArrangement<'s, T>,
862 VecCollection<'s, T, DataflowErrorSer, Diff>,
863 ) {
864 let mut err_output: Option<VecCollection<'s, T, _, _>> = None;
865 let outer_scope = input.scope();
866 let arranged_output = outer_scope
867 .clone()
868 .region_named("ReduceHierarchical", |inner| {
869 let input = input.enter(inner);
870
871 let first_mod = buckets.get(0).copied().unwrap_or(1);
873 let aggregations = aggr_funcs.len();
874
875 let mut stage = input.map(move |(key, row)| {
877 let mut row_builder = SharedRow::get();
878 let mut row_packer = row_builder.packer();
879 row_packer.extend(row.iter().take(aggregations));
880 let values = row_builder.clone();
881
882 let hash = values.hashed() % first_mod;
884 let hash_key =
885 row_builder.pack_using(std::iter::once(Datum::from(hash)).chain(&key));
886 (hash_key, values)
887 });
888
889 for (index, b) in buckets.into_iter().enumerate() {
891 let input = if index == 0 {
893 stage
894 } else {
895 stage.map(move |(hash_key, values)| {
896 let mut hash_key_iter = hash_key.iter();
897 let hash = hash_key_iter.next().unwrap().unwrap_uint64() % b;
898 let hash_key = SharedRow::pack(
900 std::iter::once(Datum::from(hash))
901 .chain(hash_key_iter.take(key_arity)),
902 );
903 (hash_key, values)
904 })
905 };
906
907 let validating = err_output.is_none();
911
912 let (oks, errs) = self.build_bucketed_stage(&aggr_funcs, input, validating);
913 if let Some(errs) = errs {
914 err_output = Some(errs.leave_region(outer_scope));
915 }
916 stage = oks
917 }
918
919 let partial = stage.map(move |(hash_key, values)| {
921 let mut hash_key_iter = hash_key.iter();
922 let _hash = hash_key_iter.next();
923 (SharedRow::pack(hash_key_iter.take(key_arity)), values)
924 });
925
926 let mut datums1 = DatumVec::new();
928 let mut datums2 = DatumVec::new();
929 let mut vals1 = DatumVec::new();
933 let mut vals2 = DatumVec::new();
934 let mfp_after1 = mfp_after.clone();
935 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
936 let aggr_funcs2 = aggr_funcs.clone();
937
938 let error_logger = self.error_logger();
941 let arranged = partial
944 .mz_arrange::<
945 ColumnationChunker<_>,
946 RowRowBatcher<_, _>,
947 RowRowBuilder<_, _>,
948 RowRowSpine<_, _>,
949 >(
950 "Arrange ReduceMinsMaxes",
951 );
952 let must_validate = err_output.is_none();
956 if must_validate || mfp_after2.is_some() {
957 let errs = arranged
958 .clone()
959 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
960 "ReduceMinsMaxes Error Check",
961 move |key, source, target| {
962 if must_validate {
966 for (val, count) in source.iter() {
967 if count.is_positive() {
968 continue;
969 }
970 let val = val.to_row();
971 let message =
972 "Non-positive accumulation in ReduceMinsMaxes";
973 error_logger
974 .log(message, &format!("val={val:?}, count={count}"));
975 target.push((
976 EvalError::Internal(message.into()).into(),
977 Diff::ONE,
978 ));
979 return;
980 }
981 }
982
983 let Some(mfp) = &mfp_after2 else { return };
985 let temp_storage = RowArena::new();
986 let mut datums_local = datums2.borrow();
987 key.extend_datums(&temp_storage, &mut datums_local, None);
988
989 let arity = aggr_funcs2.len();
994 let mut decoded = vals2.borrow();
995 for (values, _cnt) in source.iter() {
996 values.extend_datums(&temp_storage, &mut decoded, None);
997 }
998 assert_eq!(decoded.len(), source.len() * arity);
999 for (col, func) in aggr_funcs2.iter().enumerate() {
1000 let column_iter = (0..source.len())
1001 .map(|r| (decoded[r * arity + col], Diff::ONE));
1002 datums_local.push(func.eval(column_iter, &temp_storage));
1003 }
1004 if let Result::Err(e) =
1005 mfp.evaluate_inner(&mut datums_local, &temp_storage)
1006 {
1007 target.push((e.into(), Diff::ONE));
1008 }
1009 },
1010 )
1011 .as_collection(|_, v| v.clone())
1012 .leave_region(outer_scope);
1013 if let Some(e) = err_output.take() {
1014 err_output = Some(e.concat(errs));
1015 } else {
1016 err_output = Some(errs);
1017 }
1018 }
1019 arranged
1020 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1021 "ReduceMinsMaxes",
1022 move |key, source, target| {
1023 let temp_storage = RowArena::new();
1024 let mut datums_local = datums1.borrow();
1025 key.extend_datums(&temp_storage, &mut datums_local, None);
1026 let key_len = datums_local.len();
1027
1028 let arity = aggr_funcs.len();
1033 let mut decoded = vals1.borrow();
1034 for (values, _cnt) in source.iter() {
1035 values.extend_datums(&temp_storage, &mut decoded, None);
1036 }
1037 assert_eq!(decoded.len(), source.len() * arity);
1038 for (col, func) in aggr_funcs.iter().enumerate() {
1039 let column_iter = (0..source.len())
1040 .map(|r| (decoded[r * arity + col], Diff::ONE));
1041 datums_local.push(func.eval(column_iter, &temp_storage));
1042 }
1043
1044 if let Some(row) = evaluate_mfp_after(
1045 &mfp_after1,
1046 &mut datums_local,
1047 &temp_storage,
1048 key_len,
1049 ) {
1050 target.push((row, Diff::ONE));
1051 }
1052 },
1053 )
1054 .leave_region(outer_scope)
1055 });
1056 (
1057 arranged_output,
1058 err_output.expect("expected to validate in one level of the hierarchy"),
1059 )
1060 }
1061
1062 fn build_bucketed_stage<'s>(
1069 &self,
1070 aggr_funcs: &Vec<AggregateFunc>,
1071 input: VecCollection<'s, T, (Row, Row), Diff>,
1072 validating: bool,
1073 ) -> (
1074 VecCollection<'s, T, (Row, Row), Diff>,
1075 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
1076 ) {
1077 let (input, negated_output, errs) = if validating {
1078 let (input, reduced) = self
1079 .build_bucketed_negated_output::<
1080 RowValBuilder<_, _, _>,
1081 RowValSpine<Result<Row, Row>, _, _>,
1082 >(
1083 input.clone(),
1084 aggr_funcs.clone(),
1085 );
1086 let (oks, errs) = reduced
1087 .as_collection(|k, v| (k.to_row(), v.clone()))
1088 .map_fallible::<CapacityContainerBuilder<_>, CapacityContainerBuilder<_>, _, _, _>(
1089 "Checked Invalid Accumulations",
1090 |(hash_key, result)| match result {
1091 Err(hash_key) => {
1092 let mut hash_key_iter = hash_key.iter();
1093 let _hash = hash_key_iter.next();
1094 let key = SharedRow::pack(hash_key_iter);
1095 let message = format!(
1096 "Invalid data in source, saw non-positive accumulation \
1097 for key {key:?} in hierarchical mins-maxes aggregate"
1098 );
1099 Err(EvalError::Internal(message.into()).into())
1100 }
1101 Ok(values) => Ok((hash_key, values)),
1102 },
1103 );
1104 (input, oks, Some(errs))
1105 } else {
1106 let (input, reduced) = self
1107 .build_bucketed_negated_output::<RowRowBuilder<_, _>, RowRowSpine<_, _>>(
1108 input,
1109 aggr_funcs.clone(),
1110 );
1111 let oks = reduced.as_collection(|k, v| (k.to_row(), v.to_row()));
1114 (input, oks, None)
1115 };
1116
1117 let input = input.as_collection(|k, v| (k.to_row(), v.to_row()));
1118 let oks = negated_output.concat(input);
1119 (oks, errs)
1120 }
1121
1122 fn build_bucketed_negated_output<'s, Bu, Tr>(
1126 &self,
1127 input: VecCollection<'s, T, (Row, Row), Diff>,
1128 aggrs: Vec<AggregateFunc>,
1129 ) -> (
1130 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
1131 Arranged<'s, TraceAgent<Tr>>,
1132 )
1133 where
1134 Tr: Trace<Batch: Navigable, Time = T> + 'static,
1135 for<'a> BatchCursor<Tr>: Cursor<
1136 Key<'a> = DatumSeq<'a>,
1137 KeyContainer = DatumContainer,
1138 ValOwn: Data + MaybeValidatingRow<Row, Row>,
1139 Time = T,
1140 Diff = Diff,
1141 >,
1142 Bu: Builder<
1143 Time = T,
1144 Input: Container
1145 + ClearContainer
1146 + PushInto<((Row, BatchValOwn<Tr>), Tr::Time, BatchDiff<Tr>)>,
1147 Output = Tr::Batch,
1148 > + 'static,
1149 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
1150 {
1151 let error_logger = self.error_logger();
1152 let arranged_input = input
1155 .mz_arrange::<
1156 ColumnationChunker<_>,
1157 RowRowBatcher<_, _>,
1158 RowRowBuilder<_, _>,
1159 RowRowSpine<_, _>,
1160 >(
1161 "Arranged MinsMaxesHierarchical input",
1162 );
1163
1164 let mut value_datums = DatumVec::new();
1167 let reduced = arranged_input.clone().mz_reduce_abelian::<_, Bu, Tr, _>(
1168 "Reduced Fallibly MinsMaxesHierarchical",
1169 move |key, source, target| {
1170 if let Some(err) = BatchValOwn::<Tr>::into_error() {
1171 for (value, count) in source.iter() {
1173 if count.is_positive() {
1174 continue;
1175 }
1176 error_logger.log(
1177 "Non-positive accumulation in MinsMaxesHierarchical",
1178 &format!("key={key:?}, value={value:?}, count={count}"),
1179 );
1180 let key = <BatchKeyContainer<Tr> as BatchContainer>::into_owned(key);
1183 target.push((err(key), Diff::ONE));
1184 return;
1185 }
1186 }
1187
1188 let temp_storage = RowArena::new();
1191 let arity = aggrs.len();
1192 let mut decoded = value_datums.borrow();
1193 for (values, _cnt) in source.iter() {
1194 values.extend_datums(&temp_storage, &mut decoded, None);
1195 }
1196 assert_eq!(decoded.len(), source.len() * arity);
1197
1198 let mut row_builder = SharedRow::get();
1199 let mut row_packer = row_builder.packer();
1200 for (col, func) in aggrs.iter().enumerate() {
1201 let column_iter =
1204 (0..source.len()).map(|r| (decoded[r * arity + col], Diff::ONE));
1205 row_packer.push(func.eval(column_iter, &temp_storage));
1206 }
1207 target.reserve(source.len().saturating_add(1));
1214 target.push((BatchValOwn::<Tr>::ok(row_builder.clone()), Diff::ONE));
1215 target.extend(source.iter().map(|(values, cnt)| {
1216 let mut cnt = *cnt;
1217 cnt.negate();
1218 (BatchValOwn::<Tr>::ok(values.to_row()), cnt)
1219 }));
1220 },
1221 );
1222 (arranged_input, reduced)
1223 }
1224
1225 fn build_monotonic<'s>(
1228 &self,
1229 collection: VecCollection<'s, T, (Row, Row), Diff>,
1230 MonotonicPlan {
1231 aggr_funcs,
1232 must_consolidate,
1233 }: MonotonicPlan,
1234 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1235 ) -> (
1236 RowRowArrangement<'s, T>,
1237 VecCollection<'s, T, DataflowErrorSer, Diff>,
1238 ) {
1239 let aggregations = aggr_funcs.len();
1240 let collection = collection
1242 .map(move |(key, row)| {
1243 let mut row_builder = SharedRow::get();
1244 let mut values = Vec::with_capacity(aggregations);
1245 values.extend(
1246 row.iter()
1247 .take(aggregations)
1248 .map(|v| row_builder.pack_using(std::iter::once(v))),
1249 );
1250
1251 (key, values)
1252 })
1253 .consolidate_named_if::<KeyBatcher<_, _, _>>(
1254 must_consolidate,
1255 "Consolidated ReduceMonotonic input",
1256 );
1257
1258 let error_logger = self.error_logger();
1260 let (partial, validation_errs) = collection.ensure_monotonic(move |data, diff| {
1261 error_logger.log(
1262 "Non-monotonic input to ReduceMonotonic",
1263 &format!("data={data:?}, diff={diff}"),
1264 );
1265 let m = "tried to build a monotonic reduction on non-monotonic input".into();
1266 (EvalError::Internal(m).into(), Diff::ONE)
1267 });
1268 let partial = partial.explode_one(move |(key, values)| {
1272 let mut output = Vec::new();
1273 for (row, func) in values.into_iter().zip_eq(aggr_funcs.iter()) {
1274 output.push(monoids::get_monoid(row, func).expect(
1275 "hierarchical aggregations are expected to have monoid implementations",
1276 ));
1277 }
1278 (key, output)
1279 });
1280
1281 let mut datums1 = DatumVec::new();
1283 let mut datums2 = DatumVec::new();
1284 let mfp_after1 = mfp_after.clone();
1285 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1286
1287 let partial: KeyCollection<_, _, _> = partial.into();
1288 let arranged = partial
1289 .mz_arrange::<
1290 ColumnationChunker<_>,
1291 RowBatcher<_, _>,
1292 RowBuilder<_, _>,
1293 RowSpine<_, Vec<ReductionMonoid>>,
1294 >(
1295 "ArrangeMonotonic [val: empty]",
1296 );
1297 let output = arranged
1298 .clone()
1299 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1300 "ReduceMonotonic",
1301 {
1302 move |key, input, output| {
1303 let temp_storage = RowArena::new();
1304 let mut datums_local = datums1.borrow();
1305 key.extend_datums(&temp_storage, &mut datums_local, None);
1306 let key_len = datums_local.len();
1307 let accum = &input[0].1;
1308 for monoid in accum.iter() {
1309 datums_local.extend(monoid.finalize().iter());
1310 }
1311
1312 if let Some(row) = evaluate_mfp_after(
1313 &mfp_after1,
1314 &mut datums_local,
1315 &temp_storage,
1316 key_len,
1317 ) {
1318 output.push((row, Diff::ONE));
1319 }
1320 }
1321 },
1322 );
1323
1324 if let Some(mfp) = mfp_after2 {
1329 let mfp_errs = arranged
1330 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
1331 "ReduceMonotonic Error Check",
1332 move |key, input, output| {
1333 let temp_storage = RowArena::new();
1334 let mut datums_local = datums2.borrow();
1335 key.extend_datums(&temp_storage, &mut datums_local, None);
1336 let accum = &input[0].1;
1337 for monoid in accum.iter() {
1338 datums_local.extend(monoid.finalize().iter());
1339 }
1340 if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
1341 {
1342 output.push((e.into(), Diff::ONE));
1343 }
1344 },
1345 )
1346 .as_collection(|_k, v| v.clone());
1347 (output, validation_errs.concat(mfp_errs))
1348 } else {
1349 (output, validation_errs)
1350 }
1351 }
1352
1353 fn build_accumulable<'s>(
1360 &self,
1361 collection: VecCollection<'s, T, (Row, Row), Diff>,
1362 AccumulablePlan {
1363 full_aggrs,
1364 simple_aggrs,
1365 distinct_aggrs,
1366 }: AccumulablePlan,
1367 key_arity: usize,
1368 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1369 ) -> (
1370 RowRowArrangement<'s, T>,
1371 VecCollection<'s, T, DataflowErrorSer, Diff>,
1372 ) {
1373 let collection_scope = collection.scope();
1374
1375 if full_aggrs.len() == 0 || simple_aggrs.len() + distinct_aggrs.len() != full_aggrs.len() {
1377 self.error_logger().soft_panic_or_log(
1378 "Incorrect numbers of aggregates in accummulable reduction rendering",
1379 &format!(
1380 "full_aggrs={}, simple_aggrs={}, distinct_aggrs={}",
1381 full_aggrs.len(),
1382 simple_aggrs.len(),
1383 distinct_aggrs.len(),
1384 ),
1385 );
1386 }
1387
1388 let zero_diffs: (Vec<_>, Diff) = (
1400 full_aggrs
1401 .iter()
1402 .map(|f| accumulable_zero(&f.func))
1403 .collect(),
1404 Diff::ZERO,
1405 );
1406
1407 let mut to_aggregate = Vec::new();
1408 if simple_aggrs.len() > 0 {
1409 let collection = collection.clone();
1411 let easy_cases = collection.explode_one({
1412 let zero_diffs = zero_diffs.clone();
1413 move |(key, row)| {
1414 let mut diffs = zero_diffs.clone();
1415 let mut row_iter = row.iter().enumerate();
1421 for (datum_index, aggr) in simple_aggrs.iter() {
1422 let mut datum = row_iter.next().unwrap();
1423 while datum_index != &datum.0 {
1424 datum = row_iter.next().unwrap();
1425 }
1426 let datum = datum.1;
1427 diffs.0[*datum_index] = datum_to_accumulator(&aggr.func, datum);
1428 diffs.1 = Diff::ONE;
1429 }
1430 ((key, ()), diffs)
1431 }
1432 });
1433 to_aggregate.push(easy_cases);
1434 }
1435
1436 for (datum_index, aggr) in distinct_aggrs.into_iter() {
1438 let pairer = Pairer::new(key_arity);
1439 let collection = collection
1440 .clone()
1441 .map(move |(key, row)| {
1442 let value = row.iter().nth(datum_index).unwrap();
1443 (pairer.merge(&key, std::iter::once(value)), ())
1444 })
1445 .mz_arrange::<
1446 ColumnationChunker<_>,
1447 RowBatcher<_, _>,
1448 RowBuilder<_, _>,
1449 RowSpine<_, _>,
1450 >(
1451 "Arranged Accumulable Distinct [val: empty]",
1452 )
1453 .mz_reduce_abelian::<_, RowBuilder<_, _>, RowSpine<_, _>, _>(
1454 "Reduced Accumulable Distinct [val: empty]",
1455 move |_k, _s, t| t.push(((), Diff::ONE)),
1456 )
1457 .as_collection(move |key_val_iter, _| pairer.split(key_val_iter))
1458 .explode_one({
1459 let zero_diffs = zero_diffs.clone();
1460 move |(key, row)| {
1461 let datum = row.iter().next().unwrap();
1462 let mut diffs = zero_diffs.clone();
1463 diffs.0[datum_index] = datum_to_accumulator(&aggr.func, datum);
1464 diffs.1 = Diff::ONE;
1465 ((key, ()), diffs)
1466 }
1467 });
1468 to_aggregate.push(collection);
1469 }
1470
1471 let collection = if to_aggregate.len() == 1 {
1473 to_aggregate.remove(0)
1474 } else {
1475 differential_dataflow::collection::concatenate(collection_scope, to_aggregate)
1476 };
1477
1478 if ENABLE_COLUMNAR_ACCUMULABLE_DIFF.get(&self.config_set) {
1483 let arranged = collection
1484 .mz_arrange::<
1485 ColumnationChunker<_>,
1486 RowBatcher<_, _>,
1487 RowBuilder<_, _, Coltainer<_>>,
1488 RowSpine<_, (Vec<Accum>, Diff), Coltainer<_>>,
1489 >(
1490 "ArrangeAccumulable [val: empty]",
1491 );
1492 self.reduce_accumulable(arranged, full_aggrs, mfp_after)
1493 } else {
1494 let arranged = collection
1495 .mz_arrange::<
1496 ColumnationChunker<_>,
1497 RowBatcher<_, _>,
1498 RowBuilder<_, _>,
1499 RowSpine<_, (Vec<Accum>, Diff)>,
1500 >(
1501 "ArrangeAccumulable [val: empty]",
1502 );
1503 self.reduce_accumulable(arranged, full_aggrs, mfp_after)
1504 }
1505 }
1506
1507 fn reduce_accumulable<'s, DC>(
1511 &self,
1512 arranged: Arranged<'s, RowAgent<T, (Vec<Accum>, Diff), DC>>,
1513 full_aggrs: Vec<LirAggregateExpr>,
1514 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1515 ) -> (
1516 RowRowArrangement<'s, T>,
1517 VecCollection<'s, T, DataflowErrorSer, Diff>,
1518 )
1519 where
1520 DC: BatchContainer<Owned = (Vec<Accum>, Diff)>,
1521 {
1522 let mut datums1 = DatumVec::new();
1524 let mut datums2 = DatumVec::new();
1525 let mfp_after1 = mfp_after.clone();
1526 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1527 let full_aggrs2 = full_aggrs.clone();
1528
1529 let error_logger = self.error_logger();
1530 let err_full_aggrs = full_aggrs.clone();
1531 let arranged_output = arranged
1532 .clone()
1533 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1534 "ReduceAccumulable",
1535 {
1536 move |key, input, output| {
1537 let (ref accums, total) = input[0].1;
1538
1539 let temp_storage = RowArena::new();
1540 let mut datums_local = datums1.borrow();
1541 key.extend_datums(&temp_storage, &mut datums_local, None);
1542 let key_len = datums_local.len();
1543 for (aggr, accum) in full_aggrs.iter().zip_eq(accums) {
1544 datums_local.push(finalize_accum(&aggr.func, accum, total));
1545 }
1546
1547 if let Some(row) = evaluate_mfp_after(
1548 &mfp_after1,
1549 &mut datums_local,
1550 &temp_storage,
1551 key_len,
1552 ) {
1553 output.push((row, Diff::ONE));
1554 }
1555 }
1556 },
1557 );
1558 let arranged_errs = arranged
1559 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
1560 "AccumulableErrorCheck",
1561 move |key, input, output| {
1562 let (ref accums, total) = input[0].1;
1563 for (aggr, accum) in err_full_aggrs.iter().zip_eq(accums) {
1564 if total == Diff::ZERO && !accum.is_zero() {
1567 error_logger.log(
1568 "Net-zero records with non-zero accumulation in ReduceAccumulable",
1569 &format!("aggr={aggr:?}, accum={accum:?}"),
1570 );
1571 let key = key.to_row();
1572 let message = format!(
1573 "Invalid data in source, saw net-zero records for key {key} \
1574 with non-zero accumulation in accumulable aggregate"
1575 );
1576 output.push((EvalError::Internal(message.into()).into(), Diff::ONE));
1577 }
1578 match (&aggr.func, &accum) {
1579 (AggregateFunc::SumUInt16, Accum::SimpleNumber { accum, .. })
1580 | (AggregateFunc::SumUInt32, Accum::SimpleNumber { accum, .. })
1581 | (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1582 if accum.is_negative() {
1583 error_logger.log(
1584 "Invalid negative unsigned aggregation in ReduceAccumulable",
1585 &format!("aggr={aggr:?}, accum={accum:?}"),
1586 );
1587 let key = key.to_row();
1588 let message = format!(
1589 "Invalid data in source, saw negative accumulation with \
1590 unsigned type for key {key}"
1591 );
1592 let err = EvalError::Internal(message.into());
1593 output.push((err.into(), Diff::ONE));
1594 }
1595 }
1596 (
1597 AggregateFunc::SumInterval,
1598 Accum::Interval {
1599 months,
1600 days,
1601 micros,
1602 ..
1603 },
1604 ) => {
1605 if Interval::try_new(
1609 months.into_inner(),
1610 days.into_inner(),
1611 micros.into_inner(),
1612 )
1613 .is_none()
1614 {
1615 let err = EvalError::IntervalOutOfRange(
1616 format!(
1617 "{months} months {days} days {micros} microseconds"
1618 )
1619 .into(),
1620 );
1621 output.push((err.into(), Diff::ONE));
1622 }
1623 }
1624 _ => (), }
1626 }
1627
1628 let Some(mfp) = &mfp_after2 else { return };
1630 let temp_storage = RowArena::new();
1631 let mut datums_local = datums2.borrow();
1632 key.extend_datums(&temp_storage, &mut datums_local, None);
1633 for (aggr, accum) in full_aggrs2.iter().zip_eq(accums) {
1634 datums_local.push(finalize_accum(&aggr.func, accum, total));
1635 }
1636
1637 if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
1638 output.push((e.into(), Diff::ONE));
1639 }
1640 },
1641 );
1642 (
1643 arranged_output,
1644 arranged_errs.as_collection(|_key, error| error.clone()),
1645 )
1646 }
1647}
1648
1649fn evaluate_mfp_after<'a, 'b>(
1653 mfp_after: &'a Option<SafeMfpPlan<LirScalarExpr>>,
1654 datums_local: &'b mut mz_repr::DatumVecBorrow<'a>,
1655 temp_storage: &'a RowArena,
1656 key_len: usize,
1657) -> Option<Row> {
1658 let mut row_builder = SharedRow::get();
1659 if let Some(mfp) = mfp_after {
1662 if let Ok(Some(iter)) = mfp.evaluate_iter(datums_local, temp_storage) {
1665 Some(row_builder.pack_using(iter.skip(key_len)))
1668 } else {
1669 None
1670 }
1671 } else {
1672 Some(row_builder.pack_using(&datums_local[key_len..]))
1673 }
1674}
1675
1676fn accumulable_zero(aggr_func: &AggregateFunc) -> Accum {
1677 match aggr_func {
1678 AggregateFunc::Any | AggregateFunc::All => Accum::Bool {
1679 trues: Diff::ZERO,
1680 falses: Diff::ZERO,
1681 },
1682 AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => Accum::Float {
1683 accum: AccumCount::ZERO,
1684 pos_infs: Diff::ZERO,
1685 neg_infs: Diff::ZERO,
1686 nans: Diff::ZERO,
1687 non_nulls: Diff::ZERO,
1688 },
1689 AggregateFunc::SumNumeric => Accum::Numeric {
1690 accum: OrderedNumericAgg(NumericAgg::zero()),
1691 pos_infs: Diff::ZERO,
1692 neg_infs: Diff::ZERO,
1693 nans: Diff::ZERO,
1694 non_nulls: Diff::ZERO,
1695 },
1696 AggregateFunc::SumInterval => Accum::Interval {
1697 months: AccumCount::ZERO,
1698 days: AccumCount::ZERO,
1699 micros: AccumCount::ZERO,
1700 non_nulls: Diff::ZERO,
1701 },
1702 _ => Accum::SimpleNumber {
1703 accum: AccumCount::ZERO,
1704 non_nulls: Diff::ZERO,
1705 },
1706 }
1707}
1708
1709const FLOAT_SCALE_EXP: u32 = 24;
1713
1714#[allow(clippy::as_conversions)] const FLOAT_SCALE: f64 = (1_u64 << FLOAT_SCALE_EXP) as f64;
1717
1718fn float_to_fixed_point(n: f64) -> i128 {
1735 mz_ore::soft_assert_no_log!(n.is_finite());
1736
1737 let (mantissa, exponent, sign) = Float::integer_decode(n);
1741 let significand = u128::from(mantissa);
1742 let exp = i64::from(exponent) + i64::from(FLOAT_SCALE_EXP);
1743
1744 let magnitude: u128 = if exp >= 0 {
1745 match u32::try_from(exp) {
1748 Ok(shift) if shift < 128 => significand << shift,
1749 _ => 0,
1750 }
1751 } else {
1752 match u32::try_from(-exp) {
1755 Ok(shift) if shift < 128 => significand >> shift,
1756 _ => 0,
1757 }
1758 };
1759
1760 let magnitude = magnitude.cast_signed();
1763 if sign < 0 {
1764 magnitude.wrapping_neg()
1765 } else {
1766 magnitude
1767 }
1768}
1769
1770fn datum_to_accumulator(aggregate_func: &AggregateFunc, datum: Datum) -> Accum {
1771 match aggregate_func {
1772 AggregateFunc::Count => Accum::SimpleNumber {
1773 accum: AccumCount::ZERO, non_nulls: if datum.is_null() {
1775 Diff::ZERO
1776 } else {
1777 Diff::ONE
1778 },
1779 },
1780 AggregateFunc::Any | AggregateFunc::All => match datum {
1781 Datum::True => Accum::Bool {
1782 trues: Diff::ONE,
1783 falses: Diff::ZERO,
1784 },
1785 Datum::Null => Accum::Bool {
1786 trues: Diff::ZERO,
1787 falses: Diff::ZERO,
1788 },
1789 Datum::False => Accum::Bool {
1790 trues: Diff::ZERO,
1791 falses: Diff::ONE,
1792 },
1793 x => panic!("Invalid argument to AggregateFunc::Any: {x:?}"),
1794 },
1795 AggregateFunc::Dummy => match datum {
1796 Datum::Dummy => Accum::SimpleNumber {
1797 accum: AccumCount::ZERO,
1798 non_nulls: Diff::ZERO,
1799 },
1800 x => panic!("Invalid argument to AggregateFunc::Dummy: {x:?}"),
1801 },
1802 AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => {
1803 let n = match datum {
1804 Datum::Float32(n) => f64::from(*n),
1805 Datum::Float64(n) => *n,
1806 Datum::Null => 0f64,
1807 x => panic!("Invalid argument to AggregateFunc::{aggregate_func:?}: {x:?}"),
1808 };
1809
1810 let nans = Diff::from(n.is_nan());
1811 let pos_infs = Diff::from(n == f64::INFINITY);
1812 let neg_infs = Diff::from(n == f64::NEG_INFINITY);
1813 let non_nulls = Diff::from(datum != Datum::Null);
1814
1815 let accum = if nans.is_positive() || pos_infs.is_positive() || neg_infs.is_positive() {
1818 AccumCount::ZERO
1819 } else {
1820 float_to_fixed_point(n).into()
1824 };
1825
1826 Accum::Float {
1827 accum,
1828 pos_infs,
1829 neg_infs,
1830 nans,
1831 non_nulls,
1832 }
1833 }
1834 AggregateFunc::SumNumeric => match datum {
1835 Datum::Numeric(n) => {
1836 let (accum, pos_infs, neg_infs, nans) = if n.0.is_infinite() {
1837 if n.0.is_negative() {
1838 (NumericAgg::zero(), Diff::ZERO, Diff::ONE, Diff::ZERO)
1839 } else {
1840 (NumericAgg::zero(), Diff::ONE, Diff::ZERO, Diff::ZERO)
1841 }
1842 } else if n.0.is_nan() {
1843 (NumericAgg::zero(), Diff::ZERO, Diff::ZERO, Diff::ONE)
1844 } else {
1845 let mut cx_agg = numeric::cx_agg();
1848 (cx_agg.to_width(n.0), Diff::ZERO, Diff::ZERO, Diff::ZERO)
1849 };
1850
1851 Accum::Numeric {
1852 accum: OrderedNumericAgg(accum),
1853 pos_infs,
1854 neg_infs,
1855 nans,
1856 non_nulls: Diff::ONE,
1857 }
1858 }
1859 Datum::Null => Accum::Numeric {
1860 accum: OrderedNumericAgg(NumericAgg::zero()),
1861 pos_infs: Diff::ZERO,
1862 neg_infs: Diff::ZERO,
1863 nans: Diff::ZERO,
1864 non_nulls: Diff::ZERO,
1865 },
1866 x => panic!("Invalid argument to AggregateFunc::SumNumeric: {x:?}"),
1867 },
1868 AggregateFunc::SumInterval => match datum {
1869 Datum::Interval(i) => Accum::Interval {
1870 months: i.months.into(),
1871 days: i.days.into(),
1872 micros: i.micros.into(),
1873 non_nulls: Diff::ONE,
1874 },
1875 Datum::Null => Accum::Interval {
1876 months: AccumCount::ZERO,
1877 days: AccumCount::ZERO,
1878 micros: AccumCount::ZERO,
1879 non_nulls: Diff::ZERO,
1880 },
1881 x => panic!("Invalid argument to AggregateFunc::SumInterval: {x:?}"),
1882 },
1883 _ => {
1884 match datum {
1888 Datum::Int16(i) => Accum::SimpleNumber {
1889 accum: i.into(),
1890 non_nulls: Diff::ONE,
1891 },
1892 Datum::Int32(i) => Accum::SimpleNumber {
1893 accum: i.into(),
1894 non_nulls: Diff::ONE,
1895 },
1896 Datum::Int64(i) => Accum::SimpleNumber {
1897 accum: i.into(),
1898 non_nulls: Diff::ONE,
1899 },
1900 Datum::UInt16(u) => Accum::SimpleNumber {
1901 accum: u.into(),
1902 non_nulls: Diff::ONE,
1903 },
1904 Datum::UInt32(u) => Accum::SimpleNumber {
1905 accum: u.into(),
1906 non_nulls: Diff::ONE,
1907 },
1908 Datum::UInt64(u) => Accum::SimpleNumber {
1909 accum: u.into(),
1910 non_nulls: Diff::ONE,
1911 },
1912 Datum::MzTimestamp(t) => Accum::SimpleNumber {
1913 accum: u64::from(t).into(),
1914 non_nulls: Diff::ONE,
1915 },
1916 Datum::Null => Accum::SimpleNumber {
1917 accum: AccumCount::ZERO,
1918 non_nulls: Diff::ZERO,
1919 },
1920 x => panic!("Accumulating non-integer data: {x:?}"),
1921 }
1922 }
1923 }
1924}
1925
1926fn finalize_accum<'a>(aggr_func: &'a AggregateFunc, accum: &'a Accum, total: Diff) -> Datum<'a> {
1927 if total.is_positive() && accum.is_zero() && *aggr_func != AggregateFunc::Count {
1931 Datum::Null
1932 } else {
1933 match (&aggr_func, &accum) {
1934 (AggregateFunc::Count, Accum::SimpleNumber { non_nulls, .. }) => {
1935 Datum::Int64(non_nulls.into_inner())
1936 }
1937 (AggregateFunc::All, Accum::Bool { falses, trues }) => {
1938 if falses.is_positive() {
1940 Datum::False
1941 } else if *trues == total {
1942 Datum::True
1943 } else {
1944 Datum::Null
1945 }
1946 }
1947 (AggregateFunc::Any, Accum::Bool { falses, trues }) => {
1948 if trues.is_positive() {
1950 Datum::True
1951 } else if *falses == total {
1952 Datum::False
1953 } else {
1954 Datum::Null
1955 }
1956 }
1957 (AggregateFunc::Dummy, _) => Datum::Dummy,
1958 (AggregateFunc::SumInt16, Accum::SimpleNumber { accum, .. })
1960 | (AggregateFunc::SumInt32, Accum::SimpleNumber { accum, .. }) => {
1961 #[allow(clippy::as_conversions)]
1966 Datum::Int64(accum.into_inner() as i64)
1967 }
1968 (AggregateFunc::SumInt64, Accum::SimpleNumber { accum, .. }) => Datum::from(*accum),
1969 (AggregateFunc::SumUInt16, Accum::SimpleNumber { accum, .. })
1970 | (AggregateFunc::SumUInt32, Accum::SimpleNumber { accum, .. }) => {
1971 if !accum.is_negative() {
1972 #[allow(clippy::as_conversions)]
1978 Datum::UInt64(accum.into_inner() as u64)
1979 } else {
1980 Datum::Null
1984 }
1985 }
1986 (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1987 if !accum.is_negative() {
1988 Datum::from(*accum)
1989 } else {
1990 Datum::Null
1994 }
1995 }
1996 (
1997 AggregateFunc::SumFloat32,
1998 Accum::Float {
1999 accum,
2000 pos_infs,
2001 neg_infs,
2002 nans,
2003 non_nulls: _,
2004 },
2005 ) => {
2006 if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
2007 Datum::from(f32::NAN)
2010 } else if pos_infs.is_positive() {
2011 Datum::from(f32::INFINITY)
2012 } else if neg_infs.is_positive() {
2013 Datum::from(f32::NEG_INFINITY)
2014 } else {
2015 let sum = f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE;
2016 Datum::from(f32::cast_lossy(sum))
2017 }
2018 }
2019 (
2020 AggregateFunc::SumFloat64,
2021 Accum::Float {
2022 accum,
2023 pos_infs,
2024 neg_infs,
2025 nans,
2026 non_nulls: _,
2027 },
2028 ) => {
2029 if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
2030 Datum::from(f64::NAN)
2033 } else if pos_infs.is_positive() {
2034 Datum::from(f64::INFINITY)
2035 } else if neg_infs.is_positive() {
2036 Datum::from(f64::NEG_INFINITY)
2037 } else {
2038 Datum::from(f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE)
2039 }
2040 }
2041 (
2042 AggregateFunc::SumNumeric,
2043 Accum::Numeric {
2044 accum,
2045 pos_infs,
2046 neg_infs,
2047 nans,
2048 non_nulls: _,
2049 },
2050 ) => {
2051 let mut cx_datum = numeric::cx_datum();
2052 let d = cx_datum.to_width(accum.0);
2053 let inf_d = d.is_infinite();
2059 let neg_d = d.is_negative();
2060 let pos_inf = pos_infs.is_positive() || (inf_d && !neg_d);
2061 let neg_inf = neg_infs.is_positive() || (inf_d && neg_d);
2062 if nans.is_positive() || (pos_inf && neg_inf) {
2063 Datum::from(Numeric::nan())
2066 } else if pos_inf {
2067 Datum::from(Numeric::infinity())
2068 } else if neg_inf {
2069 let mut cx = numeric::cx_datum();
2070 let mut d = Numeric::infinity();
2071 cx.neg(&mut d);
2072 Datum::from(d)
2073 } else {
2074 Datum::from(d)
2075 }
2076 }
2077 (
2078 AggregateFunc::SumInterval,
2079 Accum::Interval {
2080 months,
2081 days,
2082 micros,
2083 non_nulls: _,
2084 },
2085 ) => {
2086 match Interval::try_new(months.into_inner(), days.into_inner(), micros.into_inner())
2087 {
2088 Some(interval) => Datum::Interval(interval),
2089 None => Datum::Null,
2095 }
2096 }
2097 _ => panic!(
2098 "Unexpected accumulation (aggr={:?}, accum={accum:?})",
2099 aggr_func
2100 ),
2101 }
2102 }
2103}
2104
2105type AccumCount = mz_ore::Overflowing<i128>;
2107
2108#[derive(
2119 Debug,
2120 Clone,
2121 Copy,
2122 PartialEq,
2123 Eq,
2124 PartialOrd,
2125 Ord,
2126 Serialize,
2127 Deserialize,
2128 Columnar
2129)]
2130#[columnar(derive(PartialEq, Eq, PartialOrd, Ord))]
2133enum Accum {
2134 Bool {
2136 trues: Diff,
2138 falses: Diff,
2140 },
2141 SimpleNumber {
2143 accum: AccumCount,
2145 non_nulls: Diff,
2147 },
2148 Float {
2150 accum: AccumCount,
2153 pos_infs: Diff,
2155 neg_infs: Diff,
2157 nans: Diff,
2159 non_nulls: Diff,
2161 },
2162 Interval {
2170 months: AccumCount,
2172 days: AccumCount,
2174 micros: AccumCount,
2176 non_nulls: Diff,
2178 },
2179 Numeric {
2181 accum: OrderedNumericAgg,
2183 pos_infs: Diff,
2185 neg_infs: Diff,
2187 nans: Diff,
2189 non_nulls: Diff,
2191 },
2192}
2193
2194impl IsZero for Accum {
2195 fn is_zero(&self) -> bool {
2196 match self {
2197 Accum::Bool { trues, falses } => trues.is_zero() && falses.is_zero(),
2198 Accum::SimpleNumber { accum, non_nulls } => accum.is_zero() && non_nulls.is_zero(),
2199 Accum::Float {
2200 accum,
2201 pos_infs,
2202 neg_infs,
2203 nans,
2204 non_nulls,
2205 } => {
2206 accum.is_zero()
2207 && pos_infs.is_zero()
2208 && neg_infs.is_zero()
2209 && nans.is_zero()
2210 && non_nulls.is_zero()
2211 }
2212 Accum::Interval {
2213 months,
2214 days,
2215 micros,
2216 non_nulls,
2217 } => months.is_zero() && days.is_zero() && micros.is_zero() && non_nulls.is_zero(),
2218 Accum::Numeric {
2219 accum,
2220 pos_infs,
2221 neg_infs,
2222 nans,
2223 non_nulls,
2224 } => {
2225 accum.0.is_zero()
2226 && pos_infs.is_zero()
2227 && neg_infs.is_zero()
2228 && nans.is_zero()
2229 && non_nulls.is_zero()
2230 }
2231 }
2232 }
2233}
2234
2235impl Semigroup for Accum {
2236 fn plus_equals(&mut self, other: &Accum) {
2237 match (&mut *self, other) {
2238 (
2239 Accum::Bool { trues, falses },
2240 Accum::Bool {
2241 trues: other_trues,
2242 falses: other_falses,
2243 },
2244 ) => {
2245 *trues += other_trues;
2246 *falses += other_falses;
2247 }
2248 (
2249 Accum::SimpleNumber { accum, non_nulls },
2250 Accum::SimpleNumber {
2251 accum: other_accum,
2252 non_nulls: other_non_nulls,
2253 },
2254 ) => {
2255 *accum += other_accum;
2256 *non_nulls += other_non_nulls;
2257 }
2258 (
2259 Accum::Float {
2260 accum,
2261 pos_infs,
2262 neg_infs,
2263 nans,
2264 non_nulls,
2265 },
2266 Accum::Float {
2267 accum: other_accum,
2268 pos_infs: other_pos_infs,
2269 neg_infs: other_neg_infs,
2270 nans: other_nans,
2271 non_nulls: other_non_nulls,
2272 },
2273 ) => {
2274 *accum = accum.checked_add(*other_accum).unwrap_or_else(|| {
2275 warn!("Float accumulator overflow. Incorrect results possible");
2276 accum.wrapping_add(*other_accum)
2277 });
2278 *pos_infs += other_pos_infs;
2279 *neg_infs += other_neg_infs;
2280 *nans += other_nans;
2281 *non_nulls += other_non_nulls;
2282 }
2283 (
2284 Accum::Interval {
2285 months,
2286 days,
2287 micros,
2288 non_nulls,
2289 },
2290 Accum::Interval {
2291 months: other_months,
2292 days: other_days,
2293 micros: other_micros,
2294 non_nulls: other_non_nulls,
2295 },
2296 ) => {
2297 *months += other_months;
2298 *days += other_days;
2299 *micros += other_micros;
2300 *non_nulls += other_non_nulls;
2301 }
2302 (
2303 Accum::Numeric {
2304 accum,
2305 pos_infs,
2306 neg_infs,
2307 nans,
2308 non_nulls,
2309 },
2310 Accum::Numeric {
2311 accum: other_accum,
2312 pos_infs: other_pos_infs,
2313 neg_infs: other_neg_infs,
2314 nans: other_nans,
2315 non_nulls: other_non_nulls,
2316 },
2317 ) => {
2318 let mut cx_agg = numeric::cx_agg();
2319 cx_agg.add(&mut accum.0, &other_accum.0);
2320 assert!(!cx_agg.status().rounded(), "Accum::Numeric overflow");
2326 cx_agg.reduce(&mut accum.0);
2345 *pos_infs += other_pos_infs;
2346 *neg_infs += other_neg_infs;
2347 *nans += other_nans;
2348 *non_nulls += other_non_nulls;
2349 }
2350 (l, r) => unreachable!(
2351 "Accumulator::plus_equals called with non-matching variants: {l:?} vs {r:?}"
2352 ),
2353 }
2354 }
2355}
2356
2357impl Multiply<Diff> for Accum {
2358 type Output = Accum;
2359
2360 fn multiply(self, factor: &Diff) -> Accum {
2361 let factor = *factor;
2362 match self {
2363 Accum::Bool { trues, falses } => Accum::Bool {
2364 trues: trues * factor,
2365 falses: falses * factor,
2366 },
2367 Accum::SimpleNumber { accum, non_nulls } => Accum::SimpleNumber {
2368 accum: accum * AccumCount::from(factor),
2369 non_nulls: non_nulls * factor,
2370 },
2371 Accum::Float {
2372 accum,
2373 pos_infs,
2374 neg_infs,
2375 nans,
2376 non_nulls,
2377 } => Accum::Float {
2378 accum: accum
2379 .checked_mul(AccumCount::from(factor))
2380 .unwrap_or_else(|| {
2381 warn!("Float accumulator overflow. Incorrect results possible");
2382 accum.wrapping_mul(AccumCount::from(factor))
2383 }),
2384 pos_infs: pos_infs * factor,
2385 neg_infs: neg_infs * factor,
2386 nans: nans * factor,
2387 non_nulls: non_nulls * factor,
2388 },
2389 Accum::Interval {
2390 months,
2391 days,
2392 micros,
2393 non_nulls,
2394 } => Accum::Interval {
2395 months: months * AccumCount::from(factor),
2396 days: days * AccumCount::from(factor),
2397 micros: micros * AccumCount::from(factor),
2398 non_nulls: non_nulls * factor,
2399 },
2400 Accum::Numeric {
2401 accum,
2402 pos_infs,
2403 neg_infs,
2404 nans,
2405 non_nulls,
2406 } => {
2407 let mut cx = numeric::cx_agg();
2408 let mut f = NumericAgg::from(factor.into_inner());
2409 cx.mul(&mut f, &accum.0);
2413 assert!(!cx.status().rounded(), "Accum::Numeric multiply overflow");
2419 Accum::Numeric {
2420 accum: OrderedNumericAgg(f),
2421 pos_infs: pos_infs * factor,
2422 neg_infs: neg_infs * factor,
2423 nans: nans * factor,
2424 non_nulls: non_nulls * factor,
2425 }
2426 }
2427 }
2428 }
2429}
2430
2431impl Columnation for Accum {
2434 type InnerRegion = CopyRegion<Self>;
2435}
2436
2437mod monoids {
2439
2440 use columnation::{Columnation, Region};
2456 use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
2457 use mz_expr::AggregateFunc;
2458 use mz_ore::soft_panic_or_log;
2459 use mz_repr::{Datum, Diff, Row};
2460 use serde::{Deserialize, Serialize};
2461
2462 #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
2464 pub enum ReductionMonoid {
2465 Min(Row),
2466 Max(Row),
2467 }
2468
2469 impl ReductionMonoid {
2470 pub fn finalize(&self) -> &Row {
2471 use ReductionMonoid::*;
2472 match self {
2473 Min(row) | Max(row) => row,
2474 }
2475 }
2476 }
2477
2478 impl Clone for ReductionMonoid {
2479 fn clone(&self) -> Self {
2480 use ReductionMonoid::*;
2481 match self {
2482 Min(row) => Min(row.clone()),
2483 Max(row) => Max(row.clone()),
2484 }
2485 }
2486
2487 fn clone_from(&mut self, source: &Self) {
2488 use ReductionMonoid::*;
2489
2490 let mut row = std::mem::take(match self {
2491 Min(row) | Max(row) => row,
2492 });
2493
2494 let source_row = match source {
2495 Min(row) | Max(row) => row,
2496 };
2497
2498 row.clone_from(source_row);
2499
2500 match source {
2501 Min(_) => *self = Min(row),
2502 Max(_) => *self = Max(row),
2503 }
2504 }
2505 }
2506
2507 impl Multiply<Diff> for ReductionMonoid {
2508 type Output = Self;
2509
2510 fn multiply(self, factor: &Diff) -> Self {
2511 assert!(factor.is_positive());
2516 self
2517 }
2518 }
2519
2520 impl Semigroup for ReductionMonoid {
2521 fn plus_equals(&mut self, rhs: &Self) {
2522 match (self, rhs) {
2523 (ReductionMonoid::Min(lhs), ReductionMonoid::Min(rhs)) => {
2524 let swap = {
2525 let lhs_val = lhs.unpack_first();
2526 let rhs_val = rhs.unpack_first();
2527 match (lhs_val, rhs_val) {
2529 (_, Datum::Null) => false,
2530 (Datum::Null, _) => true,
2531 (lhs, rhs) => rhs < lhs,
2532 }
2533 };
2534 if swap {
2535 lhs.clone_from(rhs);
2536 }
2537 }
2538 (ReductionMonoid::Max(lhs), ReductionMonoid::Max(rhs)) => {
2539 let swap = {
2540 let lhs_val = lhs.unpack_first();
2541 let rhs_val = rhs.unpack_first();
2542 match (lhs_val, rhs_val) {
2544 (_, Datum::Null) => false,
2545 (Datum::Null, _) => true,
2546 (lhs, rhs) => rhs > lhs,
2547 }
2548 };
2549 if swap {
2550 lhs.clone_from(rhs);
2551 }
2552 }
2553 (lhs, rhs) => {
2554 soft_panic_or_log!(
2555 "Mismatched monoid variants in reduction! lhs: {lhs:?} rhs: {rhs:?}"
2556 );
2557 }
2558 }
2559 }
2560 }
2561
2562 impl IsZero for ReductionMonoid {
2563 fn is_zero(&self) -> bool {
2564 false
2570 }
2571 }
2572
2573 impl Columnation for ReductionMonoid {
2574 type InnerRegion = ReductionMonoidRegion;
2575 }
2576
2577 #[derive(Default)]
2581 pub struct ReductionMonoidRegion {
2582 inner: <Row as Columnation>::InnerRegion,
2583 }
2584
2585 impl Region for ReductionMonoidRegion {
2586 type Item = ReductionMonoid;
2587
2588 unsafe fn copy(&mut self, item: &Self::Item) -> Self::Item {
2589 use ReductionMonoid::*;
2590 match item {
2591 Min(row) => Min(unsafe { self.inner.copy(row) }),
2592 Max(row) => Max(unsafe { self.inner.copy(row) }),
2593 }
2594 }
2595
2596 fn clear(&mut self) {
2597 self.inner.clear();
2598 }
2599
2600 fn reserve_items<'a, I>(&mut self, items: I)
2601 where
2602 Self: 'a,
2603 I: Iterator<Item = &'a Self::Item> + Clone,
2604 {
2605 self.inner
2606 .reserve_items(items.map(ReductionMonoid::finalize));
2607 }
2608
2609 fn reserve_regions<'a, I>(&mut self, regions: I)
2610 where
2611 Self: 'a,
2612 I: Iterator<Item = &'a Self> + Clone,
2613 {
2614 self.inner.reserve_regions(regions.map(|r| &r.inner));
2615 }
2616
2617 fn heap_size(&self, callback: impl FnMut(usize, usize)) {
2618 self.inner.heap_size(callback);
2619 }
2620 }
2621
2622 pub fn get_monoid(row: Row, func: &AggregateFunc) -> Option<ReductionMonoid> {
2625 match func {
2626 AggregateFunc::MaxNumeric
2627 | AggregateFunc::MaxInt16
2628 | AggregateFunc::MaxInt32
2629 | AggregateFunc::MaxInt64
2630 | AggregateFunc::MaxUInt16
2631 | AggregateFunc::MaxUInt32
2632 | AggregateFunc::MaxUInt64
2633 | AggregateFunc::MaxMzTimestamp
2634 | AggregateFunc::MaxFloat32
2635 | AggregateFunc::MaxFloat64
2636 | AggregateFunc::MaxBool
2637 | AggregateFunc::MaxString
2638 | AggregateFunc::MaxDate
2639 | AggregateFunc::MaxTimestamp
2640 | AggregateFunc::MaxTimestampTz
2641 | AggregateFunc::MaxInterval
2642 | AggregateFunc::MaxTime
2643 | AggregateFunc::MaxArray
2644 | AggregateFunc::MaxList => Some(ReductionMonoid::Max(row)),
2645 AggregateFunc::MinNumeric
2646 | AggregateFunc::MinInt16
2647 | AggregateFunc::MinInt32
2648 | AggregateFunc::MinInt64
2649 | AggregateFunc::MinUInt16
2650 | AggregateFunc::MinUInt32
2651 | AggregateFunc::MinUInt64
2652 | AggregateFunc::MinMzTimestamp
2653 | AggregateFunc::MinFloat32
2654 | AggregateFunc::MinFloat64
2655 | AggregateFunc::MinBool
2656 | AggregateFunc::MinString
2657 | AggregateFunc::MinDate
2658 | AggregateFunc::MinTimestamp
2659 | AggregateFunc::MinTimestampTz
2660 | AggregateFunc::MinInterval
2661 | AggregateFunc::MinTime
2662 | AggregateFunc::MinArray
2663 | AggregateFunc::MinList => Some(ReductionMonoid::Min(row)),
2664 AggregateFunc::SumInt16
2665 | AggregateFunc::SumInt32
2666 | AggregateFunc::SumInt64
2667 | AggregateFunc::SumUInt16
2668 | AggregateFunc::SumUInt32
2669 | AggregateFunc::SumUInt64
2670 | AggregateFunc::SumFloat32
2671 | AggregateFunc::SumFloat64
2672 | AggregateFunc::SumNumeric
2673 | AggregateFunc::SumInterval
2674 | AggregateFunc::Count
2675 | AggregateFunc::Any
2676 | AggregateFunc::All
2677 | AggregateFunc::Dummy
2678 | AggregateFunc::JsonbAgg { .. }
2679 | AggregateFunc::JsonbObjectAgg { .. }
2680 | AggregateFunc::MapAgg { .. }
2681 | AggregateFunc::ArrayConcat { .. }
2682 | AggregateFunc::ListConcat { .. }
2683 | AggregateFunc::StringAgg { .. }
2684 | AggregateFunc::RowNumber { .. }
2685 | AggregateFunc::Rank { .. }
2686 | AggregateFunc::DenseRank { .. }
2687 | AggregateFunc::LagLead { .. }
2688 | AggregateFunc::FirstValue { .. }
2689 | AggregateFunc::LastValue { .. }
2690 | AggregateFunc::WindowAggregate { .. }
2691 | AggregateFunc::FusedValueWindowFunc { .. }
2692 | AggregateFunc::FusedWindowAggregate { .. } => None,
2693 }
2694 }
2695}
2696
2697mod window_agg_helpers {
2698 use crate::render::reduce::*;
2699
2700 pub enum OneByOneAggrImpls {
2705 Accumulable(AccumulableOneByOneAggr),
2706 Hierarchical(HierarchicalOneByOneAggr),
2707 Basic(mz_expr::NaiveOneByOneAggr),
2708 }
2709
2710 impl mz_expr::OneByOneAggr for OneByOneAggrImpls {
2711 fn new(agg: &AggregateFunc, reverse: bool) -> Self {
2712 match reduction_type(agg) {
2713 ReductionType::Basic => {
2714 OneByOneAggrImpls::Basic(mz_expr::NaiveOneByOneAggr::new(agg, reverse))
2715 }
2716 ReductionType::Accumulable => {
2717 OneByOneAggrImpls::Accumulable(AccumulableOneByOneAggr::new(agg))
2718 }
2719 ReductionType::Hierarchical => {
2720 OneByOneAggrImpls::Hierarchical(HierarchicalOneByOneAggr::new(agg))
2721 }
2722 }
2723 }
2724
2725 fn give(&mut self, d: &Datum) {
2726 match self {
2727 OneByOneAggrImpls::Basic(i) => i.give(d),
2728 OneByOneAggrImpls::Accumulable(i) => i.give(d),
2729 OneByOneAggrImpls::Hierarchical(i) => i.give(d),
2730 }
2731 }
2732
2733 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2734 match self {
2736 OneByOneAggrImpls::Basic(i) => i.get_current_aggregate(temp_storage),
2737 OneByOneAggrImpls::Accumulable(i) => i.get_current_aggregate(temp_storage),
2738 OneByOneAggrImpls::Hierarchical(i) => i.get_current_aggregate(temp_storage),
2739 }
2740 }
2741 }
2742
2743 pub struct AccumulableOneByOneAggr {
2744 aggr_func: AggregateFunc,
2745 accum: Accum,
2746 total: Diff,
2747 }
2748
2749 impl AccumulableOneByOneAggr {
2750 fn new(aggr_func: &AggregateFunc) -> Self {
2751 AccumulableOneByOneAggr {
2752 aggr_func: aggr_func.clone(),
2753 accum: accumulable_zero(aggr_func),
2754 total: Diff::ZERO,
2755 }
2756 }
2757
2758 fn give(&mut self, d: &Datum) {
2759 self.accum
2760 .plus_equals(&datum_to_accumulator(&self.aggr_func, d.clone()));
2761 self.total += Diff::ONE;
2762 }
2763
2764 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2765 temp_storage.make_datum(|packer| {
2766 packer.push(finalize_accum(&self.aggr_func, &self.accum, self.total));
2767 })
2768 }
2769 }
2770
2771 pub struct HierarchicalOneByOneAggr {
2772 aggr_func: AggregateFunc,
2773 monoid: ReductionMonoid,
2776 }
2777
2778 impl HierarchicalOneByOneAggr {
2779 fn new(aggr_func: &AggregateFunc) -> Self {
2780 let mut row_buf = Row::default();
2781 row_buf.packer().push(Datum::Null);
2782 HierarchicalOneByOneAggr {
2783 aggr_func: aggr_func.clone(),
2784 monoid: get_monoid(row_buf, aggr_func)
2785 .expect("aggr_func should be a hierarchical aggregation function"),
2786 }
2787 }
2788
2789 fn give(&mut self, d: &Datum) {
2790 let mut row_buf = Row::default();
2791 row_buf.packer().push(d);
2792 let m = get_monoid(row_buf, &self.aggr_func)
2793 .expect("aggr_func should be a hierarchical aggregation function");
2794 self.monoid.plus_equals(&m);
2795 }
2796
2797 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2798 temp_storage.make_datum(|packer| packer.extend(self.monoid.finalize().iter()))
2799 }
2800 }
2801}
2802
2803#[cfg(test)]
2804mod tests {
2805 use super::*;
2806
2807 #[allow(clippy::as_conversions)]
2811 fn saturating_convert(n: f64) -> i128 {
2812 (n * FLOAT_SCALE) as i128
2813 }
2814
2815 #[mz_ore::test]
2816 fn float_to_fixed_point_matches_saturating_in_range() {
2817 let cases = [
2821 0.0,
2822 -0.0,
2823 1.0,
2824 -1.0,
2825 0.1,
2826 -0.1,
2827 0.5,
2828 -0.5,
2829 3.25,
2830 -3.25,
2831 123456.789,
2832 -123456.789,
2833 1e10,
2834 -1e10,
2835 1e20,
2836 -1e20,
2837 5e30, -5e30,
2839 ];
2840 for n in cases {
2841 assert_eq!(
2842 float_to_fixed_point(n),
2843 saturating_convert(n),
2844 "mismatch for n = {n}"
2845 );
2846 }
2847 }
2848
2849 #[mz_ore::test]
2850 fn float_to_fixed_point_truncates_toward_zero() {
2851 assert_eq!(float_to_fixed_point(1.75), 29_360_128);
2853 assert_eq!(float_to_fixed_point(-1.75), -29_360_128);
2854
2855 let frac = 0.123_456_7_f64;
2857 assert_eq!(float_to_fixed_point(frac), saturating_convert(frac));
2858 assert_eq!(float_to_fixed_point(-frac), saturating_convert(-frac));
2859 assert_eq!(float_to_fixed_point(-frac), -float_to_fixed_point(frac));
2860 }
2861
2862 #[mz_ore::test]
2863 fn float_to_fixed_point_subnormals_round_to_zero() {
2864 assert_eq!(float_to_fixed_point(0.0), 0);
2865 assert_eq!(float_to_fixed_point(-0.0), 0);
2866 assert_eq!(float_to_fixed_point(f64::MIN_POSITIVE / 2.0), 0);
2867 assert_eq!(float_to_fixed_point(5e-324), 0); }
2869
2870 #[mz_ore::test]
2871 fn float_to_fixed_point_cancels_large_finite_values() {
2872 for &n in &[1.1e31_f64, 1e32, 5e33, 1e284] {
2877 assert_eq!(
2878 float_to_fixed_point(n).wrapping_add(float_to_fixed_point(-n)),
2879 0,
2880 "n = {n} did not cancel with -n"
2881 );
2882 }
2883 }
2884
2885 #[mz_ore::test]
2886 fn float_to_fixed_point_sum_via_accumulator() {
2887 let func = AggregateFunc::SumFloat64;
2889 let mut acc = accumulable_zero(&func);
2890 acc.plus_equals(&datum_to_accumulator(&func, Datum::from(1.1e31_f64)));
2891 acc.plus_equals(&datum_to_accumulator(&func, Datum::from(-1.1e31_f64)));
2892 let datum = finalize_accum(&func, &acc, Diff::from(2_i64));
2893 assert_eq!(datum, Datum::from(0.0_f64));
2894 }
2895
2896 fn sample_accums() -> Vec<Accum> {
2898 let mut cx = numeric::cx_datum();
2899 let mut numeric = |s: &str| Datum::from(cx.parse(s).unwrap());
2900 let cases: Vec<(AggregateFunc, Vec<Datum>)> = vec![
2901 (AggregateFunc::Count, vec![Datum::Null, Datum::Int64(5)]),
2902 (
2903 AggregateFunc::SumInt64,
2904 vec![Datum::Int64(-7), Datum::Int64(i64::MAX)],
2905 ),
2906 (
2907 AggregateFunc::SumUInt16,
2908 vec![Datum::UInt16(3), Datum::Null],
2909 ),
2910 (
2911 AggregateFunc::Any,
2912 vec![Datum::True, Datum::False, Datum::Null],
2913 ),
2914 (
2915 AggregateFunc::SumFloat64,
2916 vec![
2917 Datum::from(1.5_f64),
2918 Datum::from(f64::NAN),
2919 Datum::from(f64::NEG_INFINITY),
2920 ],
2921 ),
2922 (
2923 AggregateFunc::SumNumeric,
2924 vec![
2925 numeric("-12345.678"),
2926 numeric("9e39"),
2927 numeric("NaN"),
2928 numeric("Infinity"),
2929 Datum::Null,
2930 ],
2931 ),
2932 (
2933 AggregateFunc::SumInterval,
2934 vec![
2935 Datum::Interval(Interval::new(-13, 40, 1_234_567)),
2936 Datum::Interval(Interval::new(i32::MAX, i32::MIN, i64::MAX)),
2937 Datum::Null,
2938 ],
2939 ),
2940 ];
2941 let mut accums = Vec::new();
2942 for (func, datums) in cases {
2943 let mut sum = accumulable_zero(&func);
2944 accums.push(sum);
2945 for datum in datums {
2946 let accum = datum_to_accumulator(&func, datum);
2947 sum.plus_equals(&accum);
2948 accums.push(accum);
2949 accums.push(accum.multiply(&Diff::from(-1_i64)));
2950 }
2951 accums.push(sum);
2952 }
2953 accums
2954 }
2955
2956 #[mz_ore::test]
2957 fn accum_columnar_round_trip() {
2958 use columnar::bytes::indexed::{DecodedStore, encode};
2959 use columnar::{AsBytes, Borrow, BorrowedOf, FromBytes, Index, Len};
2960 use differential_dataflow::trace::implementations::BatchContainer;
2961
2962 let accums = sample_accums();
2963 let container = Accum::as_columns(accums.iter());
2964 assert_eq!(container.len(), accums.len());
2965 let borrowed = container.borrow();
2966 for (index, accum) in accums.iter().enumerate() {
2967 assert_eq!(Accum::into_owned(borrowed.get(index)), *accum);
2968 }
2969 for (i, a) in accums.iter().enumerate() {
2970 for (j, b) in accums.iter().enumerate() {
2971 assert_eq!(borrowed.get(i).cmp(&borrowed.get(j)), a.cmp(b));
2972 }
2973 }
2974
2975 let bytes: Vec<&[u8]> = borrowed.as_bytes().map(|(_align, bytes)| bytes).collect();
2976 let decoded = BorrowedOf::<Accum>::from_bytes(&mut bytes.into_iter());
2977 for (index, accum) in accums.iter().enumerate() {
2978 assert_eq!(Accum::into_owned(decoded.get(index)), *accum);
2979 }
2980 let mut words = Vec::new();
2983 encode(&mut words, &borrowed);
2984 let decoded = BorrowedOf::<Accum>::from_store(&DecodedStore::new(&words), &mut 0);
2985 for (index, accum) in accums.iter().enumerate() {
2986 assert_eq!(Accum::into_owned(decoded.get(index)), *accum);
2987 }
2988
2989 let diffs: Vec<(Vec<Accum>, Diff)> = accums
2991 .chunks(3)
2992 .map(|chunk| (chunk.to_vec(), Diff::ONE))
2993 .collect();
2994 let mut coltainer = Coltainer::<(Vec<Accum>, Diff)>::default();
2995 for diff in &diffs {
2996 coltainer.push_own(diff);
2997 }
2998 assert_eq!(coltainer.len(), diffs.len());
2999 for (index, diff) in diffs.iter().enumerate() {
3000 assert_eq!(
3001 <Coltainer<(Vec<Accum>, Diff)>>::into_owned(coltainer.index(index)),
3002 *diff
3003 );
3004 }
3005 let mut sum = <Coltainer<(Vec<Accum>, Diff)>>::into_owned(coltainer.index(0));
3006 sum.plus_equals(&sum.clone().multiply(&Diff::from(-1_i64)));
3007 assert!(sum.is_zero());
3008 }
3009}