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::{
31 ENABLE_COLUMNAR_ACCUMULABLE_DIFF, ENABLE_COMPUTE_TEMPORAL_BUCKETING, TEMPORAL_BUCKETING_SUMMARY,
32};
33use mz_compute_types::plan::ArrangementStrategy;
34use mz_compute_types::plan::reduce::{
35 AccumulablePlan, BasicPlan, BucketedPlan, HierarchicalPlan, KeyValPlan, LirAggregateExpr,
36 MonotonicPlan, ReducePlan, ReductionType, SingleBasicPlan, reduction_type,
37};
38use mz_compute_types::plan::scalar::LirScalarExpr;
39use mz_expr::{AggregateFunc, EvalError, SafeMfpPlan};
40use mz_ore::cast::CastLossy;
41use mz_repr::adt::interval::Interval;
42use mz_repr::adt::numeric::{self, Numeric, NumericAgg, OrderedNumericAgg};
43use mz_repr::fixed_length::ExtendDatums;
44use mz_repr::{Datum, DatumVec, Diff, Row, RowArena, SharedRow};
45use mz_timely_util::columnation::ColumnationChunker;
46use mz_timely_util::operator::CollectionExt;
47use num_traits::Float;
48use serde::{Deserialize, Serialize};
49use timely::Container;
50use timely::container::{CapacityContainerBuilder, PushInto};
51use tracing::warn;
52
53use crate::extensions::arrange::{ArrangementSize, KeyCollection, MzArrange};
54use crate::extensions::reduce::{ClearContainer, MzReduce};
55use crate::render::context::{CollectionBundle, Context};
56use crate::render::errors::DataflowErrorSer;
57use crate::render::errors::MaybeValidatingRow;
58use crate::render::reduce::monoids::{ReductionMonoid, get_monoid};
59use crate::render::{ArrangementFlavor, Pairer, RenderTimestamp};
60use crate::typedefs::{
61 ErrBatcher, ErrBuilder, KeyBatcher, RowAgent, RowErrBuilder, RowErrSpine, RowRowAgent,
62 RowRowArrangement, RowRowSpine, RowSpine, RowValSpine,
63};
64use mz_row_spine::{
65 DatumContainer, DatumSeq, RowBatcher, RowBuilder, RowRowBatcher, RowRowBuilder, RowValBatcher,
66 RowValBuilder,
67};
68
69type BatchKeyContainer<Tr> = <BatchCursor<Tr> as Cursor>::KeyContainer;
71
72impl<'scope, T: RenderTimestamp> Context<'scope, T> {
73 pub fn render_reduce(
76 &self,
77 input_key: Option<Vec<LirScalarExpr>>,
78 input: CollectionBundle<'scope, T>,
79 key_val_plan: KeyValPlan,
80 reduce_plan: ReducePlan,
81 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
82 temporal_bucketing_strategy: ArrangementStrategy,
83 ) -> CollectionBundle<'scope, T>
84 where
85 T: crate::render::MaybeBucketByTime,
86 {
87 input.scope().region_named("Reduce", |inner| {
88 let KeyValPlan {
89 mut key_plan,
90 mut val_plan,
91 } = key_val_plan;
92 let key_arity = key_plan.projection.len();
93 let mut datums = DatumVec::new();
94
95 let mut demand = Vec::new();
97 demand.extend(key_plan.demand());
98 demand.extend(val_plan.demand());
99 demand.sort();
100 demand.dedup();
101
102 let mut demand_map = BTreeMap::new();
104 for column in demand.iter() {
105 demand_map.insert(*column, demand_map.len());
106 }
107 let demand_map_len = demand_map.len();
108 key_plan.permute_fn(|c| demand_map[&c], demand_map_len);
109 val_plan.permute_fn(|c| demand_map[&c], demand_map_len);
110 let max_demand = demand.iter().max().map(|x| *x + 1).unwrap_or(0);
111 let skips = mz_compute_types::plan::reduce::convert_indexes_to_skips(demand);
112
113 let (key_val_input, err) = input
114 .enter_region(inner)
115 .flat_map::<ConsolidatingContainerBuilder<Vec<((Row, Row), T, Diff)>>, _>(
116 input_key.map(|k| (k, None)),
117 max_demand,
118 move |row_datums, time, diff, ok_session, err_session| {
119 let mut row_builder = SharedRow::get();
120 let temp_storage = RowArena::new();
121
122 let mut row_iter = row_datums.drain(..);
123 let mut datums_local = datums.borrow();
124 for skip in skips.iter() {
126 datums_local.push(row_iter.nth(*skip).unwrap());
127 }
128
129 let key = key_plan.evaluate_into(
131 &mut datums_local,
132 &temp_storage,
133 &mut row_builder,
134 );
135 let key = match key {
136 Err(e) => {
137 err_session.give((e.into(), time, diff));
138 return 1;
139 }
140 Ok(Some(key)) => key.clone(),
141 Ok(None) => panic!("Row expected as no predicate was used"),
142 };
143
144 datums_local.truncate(skips.len());
147 let val = val_plan.evaluate_into(
148 &mut datums_local,
149 &temp_storage,
150 &mut row_builder,
151 );
152 let val = match val {
153 Err(e) => {
154 err_session.give((e.into(), time, diff));
155 return 1;
156 }
157 Ok(Some(val)) => val.clone(),
158 Ok(None) => panic!("Row expected as no predicate was used"),
159 };
160
161 ok_session.give(((key, val), time, diff));
162 1
163 },
164 );
165
166 let key_val_collection = key_val_input.as_collection();
171 let key_val_collection = if matches!(
172 temporal_bucketing_strategy,
173 ArrangementStrategy::TemporalBucketing
174 ) && ENABLE_COMPUTE_TEMPORAL_BUCKETING.get(&self.config_set)
175 {
176 let summary: mz_repr::Timestamp = TEMPORAL_BUCKETING_SUMMARY
177 .get(&self.config_set)
178 .try_into()
179 .expect("must fit");
180 T::maybe_apply_temporal_bucketing_vec(
181 key_val_collection.inner,
182 self.as_of_frontier.clone(),
183 summary,
184 )
185 } else {
186 key_val_collection
187 };
188
189 self.render_reduce_plan(reduce_plan, key_val_collection, err, key_arity, mfp_after)
191 .leave_region(self.scope)
192 })
193 }
194
195 fn render_reduce_plan<'s>(
201 &self,
202 plan: ReducePlan,
203 collection: VecCollection<'s, T, (Row, Row), Diff>,
204 err_input: VecCollection<'s, T, DataflowErrorSer, Diff>,
205 key_arity: usize,
206 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
207 ) -> CollectionBundle<'s, T> {
208 let mut errors = Default::default();
209 let arrangement =
210 self.render_reduce_plan_inner(plan, collection, &mut errors, key_arity, mfp_after);
211 let errs: KeyCollection<_, _, _> = err_input.concatenate(errors).into();
212 CollectionBundle::from_columns(
213 0..key_arity,
214 ArrangementFlavor::Local(
215 arrangement,
216 errs.mz_arrange::<ColumnationChunker<_>, ErrBatcher<_, _>, ErrBuilder<_, _>, _>(
217 "Arrange bundle err",
218 ),
219 ),
220 )
221 }
222
223 fn render_reduce_plan_inner<'s>(
224 &self,
225 plan: ReducePlan,
226 collection: VecCollection<'s, T, (Row, Row), Diff>,
227 errors: &mut Vec<VecCollection<'s, T, DataflowErrorSer, Diff>>,
228 key_arity: usize,
229 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
230 ) -> Arranged<'s, RowRowAgent<T, Diff>> {
231 let arrangement = match plan {
234 ReducePlan::Distinct => {
237 let (arranged_output, errs) = self.build_distinct(collection, mfp_after);
238 errors.push(errs);
239 arranged_output
240 }
241 ReducePlan::Accumulable(expr) => {
242 let (arranged_output, errs) =
243 self.build_accumulable(collection, expr, key_arity, mfp_after);
244 errors.push(errs);
245 arranged_output
246 }
247 ReducePlan::Hierarchical(HierarchicalPlan::Monotonic(expr)) => {
248 let (output, errs) = self.build_monotonic(collection, expr, mfp_after);
249 errors.push(errs);
250 output
251 }
252 ReducePlan::Hierarchical(HierarchicalPlan::Bucketed(expr)) => {
253 let (output, errs) = self.build_bucketed(collection, expr, key_arity, mfp_after);
254 errors.push(errs);
255 output
256 }
257 ReducePlan::Basic(BasicPlan::Single(SingleBasicPlan {
258 expr,
259 fused_unnest_list,
260 })) => {
261 let validating = !fused_unnest_list;
265 let (output, errs) = self.build_basic_aggregate(
266 collection,
267 0,
268 &expr,
269 validating,
270 key_arity,
271 mfp_after,
272 fused_unnest_list,
273 );
274 if validating {
275 errors.push(errs.expect("validation should have occurred as it was requested"));
276 }
277 output
278 }
279 ReducePlan::Basic(BasicPlan::Multiple(aggrs)) => {
280 let (output, errs) =
281 self.build_basic_aggregates(collection, aggrs, key_arity, mfp_after);
282 errors.push(errs);
283 output
284 }
285 };
286 arrangement
287 }
288
289 fn build_distinct<'s>(
291 &self,
292 collection: VecCollection<'s, T, (Row, Row), Diff>,
293 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
294 ) -> (
295 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
296 VecCollection<'s, T, DataflowErrorSer, Diff>,
297 ) {
298 let error_logger = self.error_logger();
299
300 let mut datums1 = DatumVec::new();
302 let mut datums2 = DatumVec::new();
303 let mfp_after1 = mfp_after.clone();
304 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
305
306 let arranged = collection
307 .mz_arrange::<
308 ColumnationChunker<_>,
309 RowRowBatcher<_, _>,
310 RowRowBuilder<_, _>,
311 RowRowSpine<_, _>,
312 >(
313 "Arranged DistinctBy",
314 );
315 let output = arranged
316 .clone()
317 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
318 "DistinctBy",
319 move |key, _input, output| {
320 let temp_storage = RowArena::new();
321 let mut datums_local = datums1.borrow();
322 key.extend_datums(&temp_storage, &mut datums_local, None);
323
324 if mfp_after1
328 .as_ref()
329 .map(|mfp| mfp.evaluate_inner(&mut datums_local, &temp_storage))
330 .unwrap_or(Ok(true))
331 == Ok(true)
332 {
333 output.push((Row::default(), Diff::ONE));
337 }
338 },
339 );
340 let errors = arranged.mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
341 "DistinctByErrorCheck",
342 move |key, input: &[(_, Diff)], output: &mut Vec<(DataflowErrorSer, _)>| {
343 for (_, count) in input.iter() {
344 if count.is_positive() {
345 continue;
346 }
347 let message = "Non-positive multiplicity in DistinctBy";
348 error_logger.log(message, &format!("row={key:?}, count={count}"));
349 output.push((EvalError::Internal(message.into()).into(), Diff::ONE));
350 return;
351 }
352 let Some(mfp) = &mfp_after2 else { return };
354 let temp_storage = RowArena::new();
355 let mut datums_local = datums2.borrow();
356 key.extend_datums(&temp_storage, &mut datums_local, None);
357
358 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
359 output.push((e.into(), Diff::ONE));
360 }
361 },
362 );
363 (output, errors.as_collection(|_k, v| v.clone()))
364 }
365
366 fn build_basic_aggregates<'s>(
374 &self,
375 input: VecCollection<'s, T, (Row, Row), Diff>,
376 aggrs: Vec<LirAggregateExpr>,
377 key_arity: usize,
378 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
379 ) -> (
380 RowRowArrangement<'s, T>,
381 VecCollection<'s, T, DataflowErrorSer, Diff>,
382 ) {
383 if aggrs.len() <= 1 {
386 self.error_logger().soft_panic_or_log(
387 "Too few aggregations when building basic aggregates",
388 &format!("len={}", aggrs.len()),
389 )
390 }
391 let mut err_output = None;
392 let mut to_collect = Vec::new();
393 for (index, aggr) in aggrs.into_iter().enumerate() {
394 let (result, errs) = self.build_basic_aggregate(
395 input.clone(),
396 index,
397 &aggr,
398 err_output.is_none(),
399 key_arity,
400 None,
401 false,
402 );
403 if errs.is_some() {
404 err_output = errs
405 }
406 to_collect
407 .push(result.as_collection(move |key, val| (key.to_row(), (index, val.to_row()))));
408 }
409
410 let mut datums1 = DatumVec::new();
412 let mut datums2 = DatumVec::new();
413 let mfp_after1 = mfp_after.clone();
414 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
415
416 let arranged = differential_dataflow::collection::concatenate(input.scope(), to_collect)
417 .mz_arrange::<
418 ColumnationChunker<_>,
419 RowValBatcher<_, _, _>,
420 RowValBuilder<_, _, _>,
421 RowValSpine<_, _, _>,
422 >(
423 "Arranged ReduceFuseBasic input",
424 );
425
426 let output = arranged
427 .clone()
428 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
429 "ReduceFuseBasic",
430 {
431 move |key, input, output| {
432 let temp_storage = RowArena::new();
433 let mut datums_local = datums1.borrow();
434 key.extend_datums(&temp_storage, &mut datums_local, None);
435 let key_len = datums_local.len();
436
437 for ((_, row), _) in input.iter() {
438 datums_local.push(row.unpack_first());
439 }
440
441 if let Some(row) = evaluate_mfp_after(
442 &mfp_after1,
443 &mut datums_local,
444 &temp_storage,
445 key_len,
446 ) {
447 output.push((row, Diff::ONE));
448 }
449 }
450 },
451 );
452 let validation_errs = err_output.expect("expected to validate in at least one aggregate");
457 if let Some(mfp) = mfp_after2 {
458 let mfp_errs = arranged
459 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
460 "ReduceFuseBasic Error Check",
461 move |key, input, output| {
462 let temp_storage = RowArena::new();
465 let mut datums_local = datums2.borrow();
466 key.extend_datums(&temp_storage, &mut datums_local, None);
467
468 for ((_, row), _) in input.iter() {
469 datums_local.push(row.unpack_first());
470 }
471
472 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
473 output.push((e.into(), Diff::ONE));
474 }
475 },
476 )
477 .as_collection(|_, v| v.clone());
478 (output, validation_errs.concat(mfp_errs))
479 } else {
480 (output, validation_errs)
481 }
482 }
483
484 fn build_basic_aggregate<'s>(
488 &self,
489 input: VecCollection<'s, T, (Row, Row), Diff>,
490 index: usize,
491 aggr: &LirAggregateExpr,
492 validating: bool,
493 key_arity: usize,
494 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
495 fused_unnest_list: bool,
496 ) -> (
497 RowRowArrangement<'s, T>,
498 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
499 ) {
500 let LirAggregateExpr {
501 func,
502 expr: _,
503 distinct,
504 } = aggr.clone();
505
506 let mut partial = input.map(move |(key, row)| {
508 let mut row_builder = SharedRow::get();
509 let value = row.iter().nth(index).unwrap();
510 row_builder.packer().push(value);
511 (key, row_builder.clone())
512 });
513
514 let mut err_output = None;
515
516 if distinct {
518 let pairer = Pairer::new(key_arity);
520 let keyed = partial.map(move |(key, val)| pairer.merge(&key, &val));
521 if validating {
522 let (oks, errs) = self
523 .build_reduce_inaccumulable_distinct::<
524 RowValBuilder<Result<(), String>, _, _>,
525 RowValSpine<Result<(), String>, _, _>,
526 >(keyed, None)
527 .as_collection(|k, v| {
528 (
529 k.to_row(),
530 v.as_ref()
531 .map(|&()| ())
532 .map_err(|m| m.as_str().into()),
533 )
534 })
535 .map_fallible::<
536 CapacityContainerBuilder<_>,
537 CapacityContainerBuilder<_>,
538 _,
539 _,
540 _,
541 >(
542 "Demux Errors",
543 move |(key_val, result)| match result {
544 Ok(()) => Ok(pairer.split(&key_val)),
545 Err(m) => {
546 Err(EvalError::Internal(m).into())
547 }
548 },
549 );
550 err_output = Some(errs);
551 partial = oks;
552 } else {
553 partial = self
554 .build_reduce_inaccumulable_distinct::<RowBuilder<_, _>, RowSpine<_, _>>(
555 keyed,
556 Some(" [val: empty]"),
557 )
558 .as_collection(move |key_val_iter, _| pairer.split(key_val_iter));
559 }
560 }
561
562 let mut datums1 = DatumVec::new();
564 let mut datums2 = DatumVec::new();
565 let mut datums_key_1 = DatumVec::new();
566 let mut datums_key_2 = DatumVec::new();
567 let mut vals1 = DatumVec::new();
571 let mut vals2 = DatumVec::new();
572 let mut vals_key_1 = DatumVec::new();
573 let mut vals_key_2 = DatumVec::new();
574 let mfp_after1 = mfp_after.clone();
575 let func2 = func.clone();
576
577 let name = if !fused_unnest_list {
578 "ReduceInaccumulable"
579 } else {
580 "FusedReduceUnnestList"
581 };
582 let arranged = partial
583 .mz_arrange::<
584 ColumnationChunker<_>,
585 RowRowBatcher<_, _>,
586 RowRowBuilder<_, _>,
587 RowRowSpine<_, _>,
588 >(&format!(
589 "Arranged {name}"
590 ));
591 let oks = if !fused_unnest_list {
592 arranged
593 .clone()
594 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(name, {
595 move |key, source, target| {
596 let temp_storage = RowArena::new();
597 let mut val_scratch = vals1.borrow();
604 let iter = source.iter().map(|(v, w)| {
605 val_scratch.clear();
606 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
607 (val_scratch[0], *w)
608 });
609
610 let mut datums_local = datums1.borrow();
611 key.extend_datums(&temp_storage, &mut datums_local, None);
612 let key_len = datums_local.len();
613 datums_local.push(
614 func.eval_with_fast_window_agg::<_, window_agg_helpers::OneByOneAggrImpls>(
617 iter,
618 &temp_storage,
619 ),
620 );
621
622 if let Some(row) = evaluate_mfp_after(
623 &mfp_after1,
624 &mut datums_local,
625 &temp_storage,
626 key_len,
627 ) {
628 target.push((row, Diff::ONE));
629 }
630 }
631 })
632 } else {
633 arranged
634 .clone()
635 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(name, {
636 move |key, source, target| {
637 let temp_storage = RowArena::new();
639 let mut val_scratch = vals_key_1.borrow();
640 let iter = source.iter().map(|(v, w)| {
641 val_scratch.clear();
642 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
643 (val_scratch[0], *w)
644 });
645
646 let mut datums_local = datums_key_1.borrow();
648 key.extend_datums(&temp_storage, &mut datums_local, None);
649 let key_len = datums_local.len();
650 for datum in func
651 .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
652 iter,
653 &temp_storage,
654 )
655 {
656 datums_local.truncate(key_len);
657 datums_local.push(datum);
658 if let Some(row) = evaluate_mfp_after(
659 &mfp_after1,
660 &mut datums_local,
661 &temp_storage,
662 key_len,
663 ) {
664 target.push((row, Diff::ONE));
665 }
666 }
667 }
668 })
669 };
670
671 let must_validate = validating && err_output.is_none();
675 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
676 if must_validate || mfp_after2.is_some() {
677 let error_logger = self.error_logger();
678
679 let errs = if !fused_unnest_list {
680 arranged
681 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
682 &format!("{name} Error Check"),
683 move |key, source, target| {
684 if must_validate {
688 for (value, count) in source.iter() {
689 if count.is_positive() {
690 continue;
691 }
692 let value = value.to_row();
693 let message =
694 "Non-positive accumulation in ReduceInaccumulable";
695 error_logger
696 .log(message, &format!("value={value:?}, count={count}"));
697 let err = EvalError::Internal(message.into());
698 target.push((err.into(), Diff::ONE));
699 return;
700 }
701 }
702
703 let Some(mfp) = &mfp_after2 else { return };
705 let temp_storage = RowArena::new();
706 let mut val_scratch = vals2.borrow();
707 let iter = source.iter().map(|(v, w)| {
708 val_scratch.clear();
709 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
710 (val_scratch[0], *w)
711 });
712
713 let mut datums_local = datums2.borrow();
714 key.extend_datums(&temp_storage, &mut datums_local, None);
715 datums_local.push(
716 func2.eval_with_fast_window_agg::<
717 _,
718 window_agg_helpers::OneByOneAggrImpls,
719 >(
720 iter, &temp_storage
721 ),
722 );
723 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
724 target.push((e.into(), Diff::ONE));
725 }
726 },
727 )
728 .as_collection(|_, v| v.clone())
729 } else {
730 assert!(!must_validate);
732 let Some(mfp) = mfp_after2 else {
735 unreachable!()
736 };
737 arranged
738 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
739 &format!("{name} Error Check"),
740 move |key, source, target| {
741 let temp_storage = RowArena::new();
742 let mut val_scratch = vals_key_2.borrow();
743 let iter = source.iter().map(|(v, w)| {
744 val_scratch.clear();
745 v.extend_datums(&temp_storage, &mut val_scratch, Some(1));
746 (val_scratch[0], *w)
747 });
748
749 let mut datums_local = datums_key_2.borrow();
750 key.extend_datums(&temp_storage, &mut datums_local, None);
751 let key_len = datums_local.len();
752 for datum in func2
753 .eval_with_unnest_list::<_, window_agg_helpers::OneByOneAggrImpls>(
754 iter,
755 &temp_storage,
756 )
757 {
758 datums_local.truncate(key_len);
759 datums_local.push(datum);
760 if let Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
763 {
764 target.push((e.into(), Diff::ONE));
765 }
766 }
767 },
768 )
769 .as_collection(|_, v| v.clone())
770 };
771
772 if let Some(e) = err_output {
773 err_output = Some(e.concat(errs));
774 } else {
775 err_output = Some(errs);
776 }
777 }
778 (oks, err_output)
779 }
780
781 fn build_reduce_inaccumulable_distinct<'s, Bu, Tr>(
782 &self,
783 input: VecCollection<'s, T, Row, Diff>,
784 name_tag: Option<&str>,
785 ) -> Arranged<'s, TraceAgent<Tr>>
786 where
787 Tr: Trace<Batch: Navigable, Time = T> + 'static,
788 for<'a> BatchCursor<Tr>: Cursor<
789 Key<'a> = DatumSeq<'a>,
790 KeyContainer = DatumContainer,
791 Time = T,
792 Diff = Diff,
793 ValOwn: Data + MaybeValidatingRow<(), String>,
794 >,
795 Bu: Builder<
796 Time = T,
797 Input: Container
798 + ClearContainer
799 + PushInto<((Row, BatchValOwn<Tr>), Tr::Time, BatchDiff<Tr>)>,
800 Output = Tr::Batch,
801 > + 'static,
802 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
803 {
804 let error_logger = self.error_logger();
805
806 let output_name = format!(
807 "ReduceInaccumulable Distinct{}",
808 name_tag.unwrap_or_default()
809 );
810
811 let input: KeyCollection<_, _, _> = input.into();
812 let arranged = input.mz_arrange::<
813 ColumnationChunker<_>,
814 RowBatcher<_, _>,
815 RowBuilder<_, _>,
816 RowSpine<_, _>,
817 >(
818 "Arranged ReduceInaccumulable Distinct [val: empty]",
819 );
820 arranged.mz_reduce_abelian::<_, Bu, Tr, _>(&output_name, move |_, source, t| {
821 if let Some(err) = BatchValOwn::<Tr>::into_error() {
822 for (value, count) in source.iter() {
823 if count.is_positive() {
824 continue;
825 }
826
827 let message = "Non-positive accumulation in ReduceInaccumulable DISTINCT";
828 error_logger.log(message, &format!("value={value:?}, count={count}"));
829 t.push((err(message.to_string()), Diff::ONE));
830 return;
831 }
832 }
833 t.push((BatchValOwn::<Tr>::ok(()), Diff::ONE))
834 })
835 }
836
837 fn build_bucketed<'s>(
855 &self,
856 input: VecCollection<'s, T, (Row, Row), Diff>,
857 BucketedPlan {
858 aggr_funcs,
859 buckets,
860 }: BucketedPlan,
861 key_arity: usize,
862 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
863 ) -> (
864 RowRowArrangement<'s, T>,
865 VecCollection<'s, T, DataflowErrorSer, Diff>,
866 ) {
867 let mut err_output: Option<VecCollection<'s, T, _, _>> = None;
868 let outer_scope = input.scope();
869 let arranged_output = outer_scope
870 .clone()
871 .region_named("ReduceHierarchical", |inner| {
872 let input = input.enter(inner);
873
874 let first_mod = buckets.get(0).copied().unwrap_or(1);
876 let aggregations = aggr_funcs.len();
877
878 let mut stage = input.map(move |(key, row)| {
880 let mut row_builder = SharedRow::get();
881 let mut row_packer = row_builder.packer();
882 row_packer.extend(row.iter().take(aggregations));
883 let values = row_builder.clone();
884
885 let hash = values.hashed() % first_mod;
887 let hash_key =
888 row_builder.pack_using(std::iter::once(Datum::from(hash)).chain(&key));
889 (hash_key, values)
890 });
891
892 for (index, b) in buckets.into_iter().enumerate() {
894 let input = if index == 0 {
896 stage
897 } else {
898 stage.map(move |(hash_key, values)| {
899 let mut hash_key_iter = hash_key.iter();
900 let hash = hash_key_iter.next().unwrap().unwrap_uint64() % b;
901 let hash_key = SharedRow::pack(
903 std::iter::once(Datum::from(hash))
904 .chain(hash_key_iter.take(key_arity)),
905 );
906 (hash_key, values)
907 })
908 };
909
910 let validating = err_output.is_none();
914
915 let (oks, errs) = self.build_bucketed_stage(&aggr_funcs, input, validating);
916 if let Some(errs) = errs {
917 err_output = Some(errs.leave_region(outer_scope));
918 }
919 stage = oks
920 }
921
922 let partial = stage.map(move |(hash_key, values)| {
924 let mut hash_key_iter = hash_key.iter();
925 let _hash = hash_key_iter.next();
926 (SharedRow::pack(hash_key_iter.take(key_arity)), values)
927 });
928
929 let mut datums1 = DatumVec::new();
931 let mut datums2 = DatumVec::new();
932 let mut vals1 = DatumVec::new();
936 let mut vals2 = DatumVec::new();
937 let mfp_after1 = mfp_after.clone();
938 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
939 let aggr_funcs2 = aggr_funcs.clone();
940
941 let error_logger = self.error_logger();
944 let arranged = partial
947 .mz_arrange::<
948 ColumnationChunker<_>,
949 RowRowBatcher<_, _>,
950 RowRowBuilder<_, _>,
951 RowRowSpine<_, _>,
952 >(
953 "Arrange ReduceMinsMaxes",
954 );
955 let must_validate = err_output.is_none();
959 if must_validate || mfp_after2.is_some() {
960 let errs = arranged
961 .clone()
962 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
963 "ReduceMinsMaxes Error Check",
964 move |key, source, target| {
965 if must_validate {
969 for (val, count) in source.iter() {
970 if count.is_positive() {
971 continue;
972 }
973 let val = val.to_row();
974 let message =
975 "Non-positive accumulation in ReduceMinsMaxes";
976 error_logger
977 .log(message, &format!("val={val:?}, count={count}"));
978 target.push((
979 EvalError::Internal(message.into()).into(),
980 Diff::ONE,
981 ));
982 return;
983 }
984 }
985
986 let Some(mfp) = &mfp_after2 else { return };
988 let temp_storage = RowArena::new();
989 let mut datums_local = datums2.borrow();
990 key.extend_datums(&temp_storage, &mut datums_local, None);
991
992 let arity = aggr_funcs2.len();
997 let mut decoded = vals2.borrow();
998 for (values, _cnt) in source.iter() {
999 values.extend_datums(&temp_storage, &mut decoded, None);
1000 }
1001 assert_eq!(decoded.len(), source.len() * arity);
1002 for (col, func) in aggr_funcs2.iter().enumerate() {
1003 let column_iter = (0..source.len())
1004 .map(|r| (decoded[r * arity + col], Diff::ONE));
1005 datums_local.push(func.eval(column_iter, &temp_storage));
1006 }
1007 if let Result::Err(e) =
1008 mfp.evaluate_inner(&mut datums_local, &temp_storage)
1009 {
1010 target.push((e.into(), Diff::ONE));
1011 }
1012 },
1013 )
1014 .as_collection(|_, v| v.clone())
1015 .leave_region(outer_scope);
1016 if let Some(e) = err_output.take() {
1017 err_output = Some(e.concat(errs));
1018 } else {
1019 err_output = Some(errs);
1020 }
1021 }
1022 arranged
1023 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1024 "ReduceMinsMaxes",
1025 move |key, source, target| {
1026 let temp_storage = RowArena::new();
1027 let mut datums_local = datums1.borrow();
1028 key.extend_datums(&temp_storage, &mut datums_local, None);
1029 let key_len = datums_local.len();
1030
1031 let arity = aggr_funcs.len();
1036 let mut decoded = vals1.borrow();
1037 for (values, _cnt) in source.iter() {
1038 values.extend_datums(&temp_storage, &mut decoded, None);
1039 }
1040 assert_eq!(decoded.len(), source.len() * arity);
1041 for (col, func) in aggr_funcs.iter().enumerate() {
1042 let column_iter = (0..source.len())
1043 .map(|r| (decoded[r * arity + col], Diff::ONE));
1044 datums_local.push(func.eval(column_iter, &temp_storage));
1045 }
1046
1047 if let Some(row) = evaluate_mfp_after(
1048 &mfp_after1,
1049 &mut datums_local,
1050 &temp_storage,
1051 key_len,
1052 ) {
1053 target.push((row, Diff::ONE));
1054 }
1055 },
1056 )
1057 .leave_region(outer_scope)
1058 });
1059 (
1060 arranged_output,
1061 err_output.expect("expected to validate in one level of the hierarchy"),
1062 )
1063 }
1064
1065 fn build_bucketed_stage<'s>(
1072 &self,
1073 aggr_funcs: &Vec<AggregateFunc>,
1074 input: VecCollection<'s, T, (Row, Row), Diff>,
1075 validating: bool,
1076 ) -> (
1077 VecCollection<'s, T, (Row, Row), Diff>,
1078 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
1079 ) {
1080 let (input, negated_output, errs) = if validating {
1081 let (input, reduced) = self
1082 .build_bucketed_negated_output::<
1083 RowValBuilder<_, _, _>,
1084 RowValSpine<Result<Row, Row>, _, _>,
1085 >(
1086 input.clone(),
1087 aggr_funcs.clone(),
1088 );
1089 let (oks, errs) = reduced
1090 .as_collection(|k, v| (k.to_row(), v.clone()))
1091 .map_fallible::<CapacityContainerBuilder<_>, CapacityContainerBuilder<_>, _, _, _>(
1092 "Checked Invalid Accumulations",
1093 |(hash_key, result)| match result {
1094 Err(hash_key) => {
1095 let mut hash_key_iter = hash_key.iter();
1096 let _hash = hash_key_iter.next();
1097 let key = SharedRow::pack(hash_key_iter);
1098 let message = format!(
1099 "Invalid data in source, saw non-positive accumulation \
1100 for key {key:?} in hierarchical mins-maxes aggregate"
1101 );
1102 Err(EvalError::Internal(message.into()).into())
1103 }
1104 Ok(values) => Ok((hash_key, values)),
1105 },
1106 );
1107 (input, oks, Some(errs))
1108 } else {
1109 let (input, reduced) = self
1110 .build_bucketed_negated_output::<RowRowBuilder<_, _>, RowRowSpine<_, _>>(
1111 input,
1112 aggr_funcs.clone(),
1113 );
1114 let oks = reduced.as_collection(|k, v| (k.to_row(), v.to_row()));
1117 (input, oks, None)
1118 };
1119
1120 let input = input.as_collection(|k, v| (k.to_row(), v.to_row()));
1121 let oks = negated_output.concat(input);
1122 (oks, errs)
1123 }
1124
1125 fn build_bucketed_negated_output<'s, Bu, Tr>(
1129 &self,
1130 input: VecCollection<'s, T, (Row, Row), Diff>,
1131 aggrs: Vec<AggregateFunc>,
1132 ) -> (
1133 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
1134 Arranged<'s, TraceAgent<Tr>>,
1135 )
1136 where
1137 Tr: Trace<Batch: Navigable, Time = T> + 'static,
1138 for<'a> BatchCursor<Tr>: Cursor<
1139 Key<'a> = DatumSeq<'a>,
1140 KeyContainer = DatumContainer,
1141 ValOwn: Data + MaybeValidatingRow<Row, Row>,
1142 Time = T,
1143 Diff = Diff,
1144 >,
1145 Bu: Builder<
1146 Time = T,
1147 Input: Container
1148 + ClearContainer
1149 + PushInto<((Row, BatchValOwn<Tr>), Tr::Time, BatchDiff<Tr>)>,
1150 Output = Tr::Batch,
1151 > + 'static,
1152 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
1153 {
1154 let error_logger = self.error_logger();
1155 let arranged_input = input
1158 .mz_arrange::<
1159 ColumnationChunker<_>,
1160 RowRowBatcher<_, _>,
1161 RowRowBuilder<_, _>,
1162 RowRowSpine<_, _>,
1163 >(
1164 "Arranged MinsMaxesHierarchical input",
1165 );
1166
1167 let mut value_datums = DatumVec::new();
1170 let reduced = arranged_input.clone().mz_reduce_abelian::<_, Bu, Tr, _>(
1171 "Reduced Fallibly MinsMaxesHierarchical",
1172 move |key, source, target| {
1173 if let Some(err) = BatchValOwn::<Tr>::into_error() {
1174 for (value, count) in source.iter() {
1176 if count.is_positive() {
1177 continue;
1178 }
1179 error_logger.log(
1180 "Non-positive accumulation in MinsMaxesHierarchical",
1181 &format!("key={key:?}, value={value:?}, count={count}"),
1182 );
1183 let key = <BatchKeyContainer<Tr> as BatchContainer>::into_owned(key);
1186 target.push((err(key), Diff::ONE));
1187 return;
1188 }
1189 }
1190
1191 let temp_storage = RowArena::new();
1194 let arity = aggrs.len();
1195 let mut decoded = value_datums.borrow();
1196 for (values, _cnt) in source.iter() {
1197 values.extend_datums(&temp_storage, &mut decoded, None);
1198 }
1199 assert_eq!(decoded.len(), source.len() * arity);
1200
1201 let mut row_builder = SharedRow::get();
1202 let mut row_packer = row_builder.packer();
1203 for (col, func) in aggrs.iter().enumerate() {
1204 let column_iter =
1207 (0..source.len()).map(|r| (decoded[r * arity + col], Diff::ONE));
1208 row_packer.push(func.eval(column_iter, &temp_storage));
1209 }
1210 target.reserve(source.len().saturating_add(1));
1217 target.push((BatchValOwn::<Tr>::ok(row_builder.clone()), Diff::ONE));
1218 target.extend(source.iter().map(|(values, cnt)| {
1219 let mut cnt = *cnt;
1220 cnt.negate();
1221 (BatchValOwn::<Tr>::ok(values.to_row()), cnt)
1222 }));
1223 },
1224 );
1225 (arranged_input, reduced)
1226 }
1227
1228 fn build_monotonic<'s>(
1231 &self,
1232 collection: VecCollection<'s, T, (Row, Row), Diff>,
1233 MonotonicPlan {
1234 aggr_funcs,
1235 must_consolidate,
1236 }: MonotonicPlan,
1237 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1238 ) -> (
1239 RowRowArrangement<'s, T>,
1240 VecCollection<'s, T, DataflowErrorSer, Diff>,
1241 ) {
1242 let aggregations = aggr_funcs.len();
1243 let collection = collection
1245 .map(move |(key, row)| {
1246 let mut row_builder = SharedRow::get();
1247 let mut values = Vec::with_capacity(aggregations);
1248 values.extend(
1249 row.iter()
1250 .take(aggregations)
1251 .map(|v| row_builder.pack_using(std::iter::once(v))),
1252 );
1253
1254 (key, values)
1255 })
1256 .consolidate_named_if::<KeyBatcher<_, _, _>>(
1257 must_consolidate,
1258 "Consolidated ReduceMonotonic input",
1259 );
1260
1261 let error_logger = self.error_logger();
1263 let (partial, validation_errs) = collection.ensure_monotonic(move |data, diff| {
1264 error_logger.log(
1265 "Non-monotonic input to ReduceMonotonic",
1266 &format!("data={data:?}, diff={diff}"),
1267 );
1268 let m = "tried to build a monotonic reduction on non-monotonic input".into();
1269 (EvalError::Internal(m).into(), Diff::ONE)
1270 });
1271 let partial = partial.explode_one(move |(key, values)| {
1275 let mut output = Vec::new();
1276 for (row, func) in values.into_iter().zip_eq(aggr_funcs.iter()) {
1277 output.push(monoids::get_monoid(row, func).expect(
1278 "hierarchical aggregations are expected to have monoid implementations",
1279 ));
1280 }
1281 (key, output)
1282 });
1283
1284 let mut datums1 = DatumVec::new();
1286 let mut datums2 = DatumVec::new();
1287 let mfp_after1 = mfp_after.clone();
1288 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1289
1290 let partial: KeyCollection<_, _, _> = partial.into();
1291 let arranged = partial
1292 .mz_arrange::<
1293 ColumnationChunker<_>,
1294 RowBatcher<_, _>,
1295 RowBuilder<_, _>,
1296 RowSpine<_, Vec<ReductionMonoid>>,
1297 >(
1298 "ArrangeMonotonic [val: empty]",
1299 );
1300 let output = arranged
1301 .clone()
1302 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1303 "ReduceMonotonic",
1304 {
1305 move |key, input, output| {
1306 let temp_storage = RowArena::new();
1307 let mut datums_local = datums1.borrow();
1308 key.extend_datums(&temp_storage, &mut datums_local, None);
1309 let key_len = datums_local.len();
1310 let accum = &input[0].1;
1311 for monoid in accum.iter() {
1312 datums_local.extend(monoid.finalize().iter());
1313 }
1314
1315 if let Some(row) = evaluate_mfp_after(
1316 &mfp_after1,
1317 &mut datums_local,
1318 &temp_storage,
1319 key_len,
1320 ) {
1321 output.push((row, Diff::ONE));
1322 }
1323 }
1324 },
1325 );
1326
1327 if let Some(mfp) = mfp_after2 {
1332 let mfp_errs = arranged
1333 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
1334 "ReduceMonotonic Error Check",
1335 move |key, input, output| {
1336 let temp_storage = RowArena::new();
1337 let mut datums_local = datums2.borrow();
1338 key.extend_datums(&temp_storage, &mut datums_local, None);
1339 let accum = &input[0].1;
1340 for monoid in accum.iter() {
1341 datums_local.extend(monoid.finalize().iter());
1342 }
1343 if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage)
1344 {
1345 output.push((e.into(), Diff::ONE));
1346 }
1347 },
1348 )
1349 .as_collection(|_k, v| v.clone());
1350 (output, validation_errs.concat(mfp_errs))
1351 } else {
1352 (output, validation_errs)
1353 }
1354 }
1355
1356 fn build_accumulable<'s>(
1363 &self,
1364 collection: VecCollection<'s, T, (Row, Row), Diff>,
1365 AccumulablePlan {
1366 full_aggrs,
1367 simple_aggrs,
1368 distinct_aggrs,
1369 }: AccumulablePlan,
1370 key_arity: usize,
1371 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1372 ) -> (
1373 RowRowArrangement<'s, T>,
1374 VecCollection<'s, T, DataflowErrorSer, Diff>,
1375 ) {
1376 let collection_scope = collection.scope();
1377
1378 if full_aggrs.len() == 0 || simple_aggrs.len() + distinct_aggrs.len() != full_aggrs.len() {
1380 self.error_logger().soft_panic_or_log(
1381 "Incorrect numbers of aggregates in accummulable reduction rendering",
1382 &format!(
1383 "full_aggrs={}, simple_aggrs={}, distinct_aggrs={}",
1384 full_aggrs.len(),
1385 simple_aggrs.len(),
1386 distinct_aggrs.len(),
1387 ),
1388 );
1389 }
1390
1391 let zero_diffs: (Vec<_>, Diff) = (
1403 full_aggrs
1404 .iter()
1405 .map(|f| accumulable_zero(&f.func))
1406 .collect(),
1407 Diff::ZERO,
1408 );
1409
1410 let mut to_aggregate = Vec::new();
1411 if simple_aggrs.len() > 0 {
1412 let collection = collection.clone();
1414 let easy_cases = collection.explode_one({
1415 let zero_diffs = zero_diffs.clone();
1416 move |(key, row)| {
1417 let mut diffs = zero_diffs.clone();
1418 let mut row_iter = row.iter().enumerate();
1424 for (datum_index, aggr) in simple_aggrs.iter() {
1425 let mut datum = row_iter.next().unwrap();
1426 while datum_index != &datum.0 {
1427 datum = row_iter.next().unwrap();
1428 }
1429 let datum = datum.1;
1430 diffs.0[*datum_index] = datum_to_accumulator(&aggr.func, datum);
1431 diffs.1 = Diff::ONE;
1432 }
1433 ((key, ()), diffs)
1434 }
1435 });
1436 to_aggregate.push(easy_cases);
1437 }
1438
1439 for (datum_index, aggr) in distinct_aggrs.into_iter() {
1441 let pairer = Pairer::new(key_arity);
1442 let collection = collection
1443 .clone()
1444 .map(move |(key, row)| {
1445 let value = row.iter().nth(datum_index).unwrap();
1446 (pairer.merge(&key, std::iter::once(value)), ())
1447 })
1448 .mz_arrange::<
1449 ColumnationChunker<_>,
1450 RowBatcher<_, _>,
1451 RowBuilder<_, _>,
1452 RowSpine<_, _>,
1453 >(
1454 "Arranged Accumulable Distinct [val: empty]",
1455 )
1456 .mz_reduce_abelian::<_, RowBuilder<_, _>, RowSpine<_, _>, _>(
1457 "Reduced Accumulable Distinct [val: empty]",
1458 move |_k, _s, t| t.push(((), Diff::ONE)),
1459 )
1460 .as_collection(move |key_val_iter, _| pairer.split(key_val_iter))
1461 .explode_one({
1462 let zero_diffs = zero_diffs.clone();
1463 move |(key, row)| {
1464 let datum = row.iter().next().unwrap();
1465 let mut diffs = zero_diffs.clone();
1466 diffs.0[datum_index] = datum_to_accumulator(&aggr.func, datum);
1467 diffs.1 = Diff::ONE;
1468 ((key, ()), diffs)
1469 }
1470 });
1471 to_aggregate.push(collection);
1472 }
1473
1474 let collection = if to_aggregate.len() == 1 {
1476 to_aggregate.remove(0)
1477 } else {
1478 differential_dataflow::collection::concatenate(collection_scope, to_aggregate)
1479 };
1480
1481 if ENABLE_COLUMNAR_ACCUMULABLE_DIFF.get(&self.config_set) {
1486 let arranged = collection
1487 .mz_arrange::<
1488 ColumnationChunker<_>,
1489 RowBatcher<_, _>,
1490 RowBuilder<_, _, Coltainer<_>>,
1491 RowSpine<_, (Vec<Accum>, Diff), Coltainer<_>>,
1492 >(
1493 "ArrangeAccumulable [val: empty]",
1494 );
1495 self.reduce_accumulable(arranged, full_aggrs, mfp_after)
1496 } else {
1497 let arranged = collection
1498 .mz_arrange::<
1499 ColumnationChunker<_>,
1500 RowBatcher<_, _>,
1501 RowBuilder<_, _>,
1502 RowSpine<_, (Vec<Accum>, Diff)>,
1503 >(
1504 "ArrangeAccumulable [val: empty]",
1505 );
1506 self.reduce_accumulable(arranged, full_aggrs, mfp_after)
1507 }
1508 }
1509
1510 fn reduce_accumulable<'s, DC>(
1514 &self,
1515 arranged: Arranged<'s, RowAgent<T, (Vec<Accum>, Diff), DC>>,
1516 full_aggrs: Vec<LirAggregateExpr>,
1517 mfp_after: Option<SafeMfpPlan<LirScalarExpr>>,
1518 ) -> (
1519 RowRowArrangement<'s, T>,
1520 VecCollection<'s, T, DataflowErrorSer, Diff>,
1521 )
1522 where
1523 DC: BatchContainer<Owned = (Vec<Accum>, Diff)>,
1524 {
1525 let mut datums1 = DatumVec::new();
1527 let mut datums2 = DatumVec::new();
1528 let mfp_after1 = mfp_after.clone();
1529 let mfp_after2 = mfp_after.filter(|mfp| mfp.could_error());
1530 let full_aggrs2 = full_aggrs.clone();
1531
1532 let error_logger = self.error_logger();
1533 let err_full_aggrs = full_aggrs.clone();
1534 let arranged_output = arranged
1535 .clone()
1536 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
1537 "ReduceAccumulable",
1538 {
1539 move |key, input, output| {
1540 let (ref accums, total) = input[0].1;
1541
1542 let temp_storage = RowArena::new();
1543 let mut datums_local = datums1.borrow();
1544 key.extend_datums(&temp_storage, &mut datums_local, None);
1545 let key_len = datums_local.len();
1546 for (aggr, accum) in full_aggrs.iter().zip_eq(accums) {
1547 datums_local.push(finalize_accum(&aggr.func, accum, total));
1548 }
1549
1550 if let Some(row) = evaluate_mfp_after(
1551 &mfp_after1,
1552 &mut datums_local,
1553 &temp_storage,
1554 key_len,
1555 ) {
1556 output.push((row, Diff::ONE));
1557 }
1558 }
1559 },
1560 );
1561 let arranged_errs = arranged
1562 .mz_reduce_abelian::<_, RowErrBuilder<_, _>, RowErrSpine<_, _>, _>(
1563 "AccumulableErrorCheck",
1564 move |key, input, output| {
1565 let (ref accums, total) = input[0].1;
1566 for (aggr, accum) in err_full_aggrs.iter().zip_eq(accums) {
1567 if total == Diff::ZERO && !accum.is_zero() {
1570 error_logger.log(
1571 "Net-zero records with non-zero accumulation in ReduceAccumulable",
1572 &format!("aggr={aggr:?}, accum={accum:?}"),
1573 );
1574 let key = key.to_row();
1575 let message = format!(
1576 "Invalid data in source, saw net-zero records for key {key} \
1577 with non-zero accumulation in accumulable aggregate"
1578 );
1579 output.push((EvalError::Internal(message.into()).into(), Diff::ONE));
1580 }
1581 match (&aggr.func, &accum) {
1582 (AggregateFunc::SumUInt16, Accum::SimpleNumber { accum, .. })
1583 | (AggregateFunc::SumUInt32, Accum::SimpleNumber { accum, .. })
1584 | (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1585 if accum.is_negative() {
1586 error_logger.log(
1587 "Invalid negative unsigned aggregation in ReduceAccumulable",
1588 &format!("aggr={aggr:?}, accum={accum:?}"),
1589 );
1590 let key = key.to_row();
1591 let message = format!(
1592 "Invalid data in source, saw negative accumulation with \
1593 unsigned type for key {key}"
1594 );
1595 let err = EvalError::Internal(message.into());
1596 output.push((err.into(), Diff::ONE));
1597 }
1598 }
1599 (
1600 AggregateFunc::SumInterval,
1601 Accum::Interval {
1602 months,
1603 days,
1604 micros,
1605 ..
1606 },
1607 ) => {
1608 if Interval::try_new(
1612 months.into_inner(),
1613 days.into_inner(),
1614 micros.into_inner(),
1615 )
1616 .is_none()
1617 {
1618 let err = EvalError::IntervalOutOfRange(
1619 format!(
1620 "{months} months {days} days {micros} microseconds"
1621 )
1622 .into(),
1623 );
1624 output.push((err.into(), Diff::ONE));
1625 }
1626 }
1627 _ => (), }
1629 }
1630
1631 let Some(mfp) = &mfp_after2 else { return };
1633 let temp_storage = RowArena::new();
1634 let mut datums_local = datums2.borrow();
1635 key.extend_datums(&temp_storage, &mut datums_local, None);
1636 for (aggr, accum) in full_aggrs2.iter().zip_eq(accums) {
1637 datums_local.push(finalize_accum(&aggr.func, accum, total));
1638 }
1639
1640 if let Result::Err(e) = mfp.evaluate_inner(&mut datums_local, &temp_storage) {
1641 output.push((e.into(), Diff::ONE));
1642 }
1643 },
1644 );
1645 (
1646 arranged_output,
1647 arranged_errs.as_collection(|_key, error| error.clone()),
1648 )
1649 }
1650}
1651
1652fn evaluate_mfp_after<'a, 'b>(
1656 mfp_after: &'a Option<SafeMfpPlan<LirScalarExpr>>,
1657 datums_local: &'b mut mz_repr::DatumVecBorrow<'a>,
1658 temp_storage: &'a RowArena,
1659 key_len: usize,
1660) -> Option<Row> {
1661 let mut row_builder = SharedRow::get();
1662 if let Some(mfp) = mfp_after {
1665 if let Ok(Some(iter)) = mfp.evaluate_iter(datums_local, temp_storage) {
1668 Some(row_builder.pack_using(iter.skip(key_len)))
1671 } else {
1672 None
1673 }
1674 } else {
1675 Some(row_builder.pack_using(&datums_local[key_len..]))
1676 }
1677}
1678
1679fn accumulable_zero(aggr_func: &AggregateFunc) -> Accum {
1680 match aggr_func {
1681 AggregateFunc::Any | AggregateFunc::All => Accum::Bool {
1682 trues: Diff::ZERO,
1683 falses: Diff::ZERO,
1684 },
1685 AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => Accum::Float {
1686 accum: AccumCount::ZERO,
1687 pos_infs: Diff::ZERO,
1688 neg_infs: Diff::ZERO,
1689 nans: Diff::ZERO,
1690 non_nulls: Diff::ZERO,
1691 },
1692 AggregateFunc::SumNumeric => Accum::Numeric {
1693 accum: OrderedNumericAgg(NumericAgg::zero()),
1694 pos_infs: Diff::ZERO,
1695 neg_infs: Diff::ZERO,
1696 nans: Diff::ZERO,
1697 non_nulls: Diff::ZERO,
1698 },
1699 AggregateFunc::SumInterval => Accum::Interval {
1700 months: AccumCount::ZERO,
1701 days: AccumCount::ZERO,
1702 micros: AccumCount::ZERO,
1703 non_nulls: Diff::ZERO,
1704 },
1705 _ => Accum::SimpleNumber {
1706 accum: AccumCount::ZERO,
1707 non_nulls: Diff::ZERO,
1708 },
1709 }
1710}
1711
1712const FLOAT_SCALE_EXP: u32 = 24;
1716
1717#[allow(clippy::as_conversions)] const FLOAT_SCALE: f64 = (1_u64 << FLOAT_SCALE_EXP) as f64;
1720
1721fn float_to_fixed_point(n: f64) -> i128 {
1738 mz_ore::soft_assert_no_log!(n.is_finite());
1739
1740 let (mantissa, exponent, sign) = Float::integer_decode(n);
1744 let significand = u128::from(mantissa);
1745 let exp = i64::from(exponent) + i64::from(FLOAT_SCALE_EXP);
1746
1747 let magnitude: u128 = if exp >= 0 {
1748 match u32::try_from(exp) {
1751 Ok(shift) if shift < 128 => significand << shift,
1752 _ => 0,
1753 }
1754 } else {
1755 match u32::try_from(-exp) {
1758 Ok(shift) if shift < 128 => significand >> shift,
1759 _ => 0,
1760 }
1761 };
1762
1763 let magnitude = magnitude.cast_signed();
1766 if sign < 0 {
1767 magnitude.wrapping_neg()
1768 } else {
1769 magnitude
1770 }
1771}
1772
1773fn datum_to_accumulator(aggregate_func: &AggregateFunc, datum: Datum) -> Accum {
1774 match aggregate_func {
1775 AggregateFunc::Count => Accum::SimpleNumber {
1776 accum: AccumCount::ZERO, non_nulls: if datum.is_null() {
1778 Diff::ZERO
1779 } else {
1780 Diff::ONE
1781 },
1782 },
1783 AggregateFunc::Any | AggregateFunc::All => match datum {
1784 Datum::True => Accum::Bool {
1785 trues: Diff::ONE,
1786 falses: Diff::ZERO,
1787 },
1788 Datum::Null => Accum::Bool {
1789 trues: Diff::ZERO,
1790 falses: Diff::ZERO,
1791 },
1792 Datum::False => Accum::Bool {
1793 trues: Diff::ZERO,
1794 falses: Diff::ONE,
1795 },
1796 x => panic!("Invalid argument to AggregateFunc::Any: {x:?}"),
1797 },
1798 AggregateFunc::Dummy => match datum {
1799 Datum::Dummy => Accum::SimpleNumber {
1800 accum: AccumCount::ZERO,
1801 non_nulls: Diff::ZERO,
1802 },
1803 x => panic!("Invalid argument to AggregateFunc::Dummy: {x:?}"),
1804 },
1805 AggregateFunc::SumFloat32 | AggregateFunc::SumFloat64 => {
1806 let n = match datum {
1807 Datum::Float32(n) => f64::from(*n),
1808 Datum::Float64(n) => *n,
1809 Datum::Null => 0f64,
1810 x => panic!("Invalid argument to AggregateFunc::{aggregate_func:?}: {x:?}"),
1811 };
1812
1813 let nans = Diff::from(n.is_nan());
1814 let pos_infs = Diff::from(n == f64::INFINITY);
1815 let neg_infs = Diff::from(n == f64::NEG_INFINITY);
1816 let non_nulls = Diff::from(datum != Datum::Null);
1817
1818 let accum = if nans.is_positive() || pos_infs.is_positive() || neg_infs.is_positive() {
1821 AccumCount::ZERO
1822 } else {
1823 float_to_fixed_point(n).into()
1827 };
1828
1829 Accum::Float {
1830 accum,
1831 pos_infs,
1832 neg_infs,
1833 nans,
1834 non_nulls,
1835 }
1836 }
1837 AggregateFunc::SumNumeric => match datum {
1838 Datum::Numeric(n) => {
1839 let (accum, pos_infs, neg_infs, nans) = if n.0.is_infinite() {
1840 if n.0.is_negative() {
1841 (NumericAgg::zero(), Diff::ZERO, Diff::ONE, Diff::ZERO)
1842 } else {
1843 (NumericAgg::zero(), Diff::ONE, Diff::ZERO, Diff::ZERO)
1844 }
1845 } else if n.0.is_nan() {
1846 (NumericAgg::zero(), Diff::ZERO, Diff::ZERO, Diff::ONE)
1847 } else {
1848 let mut cx_agg = numeric::cx_agg();
1851 (cx_agg.to_width(n.0), Diff::ZERO, Diff::ZERO, Diff::ZERO)
1852 };
1853
1854 Accum::Numeric {
1855 accum: OrderedNumericAgg(accum),
1856 pos_infs,
1857 neg_infs,
1858 nans,
1859 non_nulls: Diff::ONE,
1860 }
1861 }
1862 Datum::Null => Accum::Numeric {
1863 accum: OrderedNumericAgg(NumericAgg::zero()),
1864 pos_infs: Diff::ZERO,
1865 neg_infs: Diff::ZERO,
1866 nans: Diff::ZERO,
1867 non_nulls: Diff::ZERO,
1868 },
1869 x => panic!("Invalid argument to AggregateFunc::SumNumeric: {x:?}"),
1870 },
1871 AggregateFunc::SumInterval => match datum {
1872 Datum::Interval(i) => Accum::Interval {
1873 months: i.months.into(),
1874 days: i.days.into(),
1875 micros: i.micros.into(),
1876 non_nulls: Diff::ONE,
1877 },
1878 Datum::Null => Accum::Interval {
1879 months: AccumCount::ZERO,
1880 days: AccumCount::ZERO,
1881 micros: AccumCount::ZERO,
1882 non_nulls: Diff::ZERO,
1883 },
1884 x => panic!("Invalid argument to AggregateFunc::SumInterval: {x:?}"),
1885 },
1886 _ => {
1887 match datum {
1891 Datum::Int16(i) => Accum::SimpleNumber {
1892 accum: i.into(),
1893 non_nulls: Diff::ONE,
1894 },
1895 Datum::Int32(i) => Accum::SimpleNumber {
1896 accum: i.into(),
1897 non_nulls: Diff::ONE,
1898 },
1899 Datum::Int64(i) => Accum::SimpleNumber {
1900 accum: i.into(),
1901 non_nulls: Diff::ONE,
1902 },
1903 Datum::UInt16(u) => Accum::SimpleNumber {
1904 accum: u.into(),
1905 non_nulls: Diff::ONE,
1906 },
1907 Datum::UInt32(u) => Accum::SimpleNumber {
1908 accum: u.into(),
1909 non_nulls: Diff::ONE,
1910 },
1911 Datum::UInt64(u) => Accum::SimpleNumber {
1912 accum: u.into(),
1913 non_nulls: Diff::ONE,
1914 },
1915 Datum::MzTimestamp(t) => Accum::SimpleNumber {
1916 accum: u64::from(t).into(),
1917 non_nulls: Diff::ONE,
1918 },
1919 Datum::Null => Accum::SimpleNumber {
1920 accum: AccumCount::ZERO,
1921 non_nulls: Diff::ZERO,
1922 },
1923 x => panic!("Accumulating non-integer data: {x:?}"),
1924 }
1925 }
1926 }
1927}
1928
1929fn finalize_accum<'a>(aggr_func: &'a AggregateFunc, accum: &'a Accum, total: Diff) -> Datum<'a> {
1930 if total.is_positive() && accum.is_zero() && *aggr_func != AggregateFunc::Count {
1934 Datum::Null
1935 } else {
1936 match (&aggr_func, &accum) {
1937 (AggregateFunc::Count, Accum::SimpleNumber { non_nulls, .. }) => {
1938 Datum::Int64(non_nulls.into_inner())
1939 }
1940 (AggregateFunc::All, Accum::Bool { falses, trues }) => {
1941 if falses.is_positive() {
1943 Datum::False
1944 } else if *trues == total {
1945 Datum::True
1946 } else {
1947 Datum::Null
1948 }
1949 }
1950 (AggregateFunc::Any, Accum::Bool { falses, trues }) => {
1951 if trues.is_positive() {
1953 Datum::True
1954 } else if *falses == total {
1955 Datum::False
1956 } else {
1957 Datum::Null
1958 }
1959 }
1960 (AggregateFunc::Dummy, _) => Datum::Dummy,
1961 (AggregateFunc::SumInt16, Accum::SimpleNumber { accum, .. })
1963 | (AggregateFunc::SumInt32, Accum::SimpleNumber { accum, .. }) => {
1964 #[allow(clippy::as_conversions)]
1969 Datum::Int64(accum.into_inner() as i64)
1970 }
1971 (AggregateFunc::SumInt64, Accum::SimpleNumber { accum, .. }) => Datum::from(*accum),
1972 (AggregateFunc::SumUInt16, Accum::SimpleNumber { accum, .. })
1973 | (AggregateFunc::SumUInt32, Accum::SimpleNumber { accum, .. }) => {
1974 if !accum.is_negative() {
1975 #[allow(clippy::as_conversions)]
1981 Datum::UInt64(accum.into_inner() as u64)
1982 } else {
1983 Datum::Null
1987 }
1988 }
1989 (AggregateFunc::SumUInt64, Accum::SimpleNumber { accum, .. }) => {
1990 if !accum.is_negative() {
1991 Datum::from(*accum)
1992 } else {
1993 Datum::Null
1997 }
1998 }
1999 (
2000 AggregateFunc::SumFloat32,
2001 Accum::Float {
2002 accum,
2003 pos_infs,
2004 neg_infs,
2005 nans,
2006 non_nulls: _,
2007 },
2008 ) => {
2009 if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
2010 Datum::from(f32::NAN)
2013 } else if pos_infs.is_positive() {
2014 Datum::from(f32::INFINITY)
2015 } else if neg_infs.is_positive() {
2016 Datum::from(f32::NEG_INFINITY)
2017 } else {
2018 let sum = f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE;
2019 Datum::from(f32::cast_lossy(sum))
2020 }
2021 }
2022 (
2023 AggregateFunc::SumFloat64,
2024 Accum::Float {
2025 accum,
2026 pos_infs,
2027 neg_infs,
2028 nans,
2029 non_nulls: _,
2030 },
2031 ) => {
2032 if nans.is_positive() || (pos_infs.is_positive() && neg_infs.is_positive()) {
2033 Datum::from(f64::NAN)
2036 } else if pos_infs.is_positive() {
2037 Datum::from(f64::INFINITY)
2038 } else if neg_infs.is_positive() {
2039 Datum::from(f64::NEG_INFINITY)
2040 } else {
2041 Datum::from(f64::cast_lossy(accum.into_inner()) / FLOAT_SCALE)
2042 }
2043 }
2044 (
2045 AggregateFunc::SumNumeric,
2046 Accum::Numeric {
2047 accum,
2048 pos_infs,
2049 neg_infs,
2050 nans,
2051 non_nulls: _,
2052 },
2053 ) => {
2054 let mut cx_datum = numeric::cx_datum();
2055 let d = cx_datum.to_width(accum.0);
2056 let inf_d = d.is_infinite();
2062 let neg_d = d.is_negative();
2063 let pos_inf = pos_infs.is_positive() || (inf_d && !neg_d);
2064 let neg_inf = neg_infs.is_positive() || (inf_d && neg_d);
2065 if nans.is_positive() || (pos_inf && neg_inf) {
2066 Datum::from(Numeric::nan())
2069 } else if pos_inf {
2070 Datum::from(Numeric::infinity())
2071 } else if neg_inf {
2072 let mut cx = numeric::cx_datum();
2073 let mut d = Numeric::infinity();
2074 cx.neg(&mut d);
2075 Datum::from(d)
2076 } else {
2077 Datum::from(d)
2078 }
2079 }
2080 (
2081 AggregateFunc::SumInterval,
2082 Accum::Interval {
2083 months,
2084 days,
2085 micros,
2086 non_nulls: _,
2087 },
2088 ) => {
2089 match Interval::try_new(months.into_inner(), days.into_inner(), micros.into_inner())
2090 {
2091 Some(interval) => Datum::Interval(interval),
2092 None => Datum::Null,
2098 }
2099 }
2100 _ => panic!(
2101 "Unexpected accumulation (aggr={:?}, accum={accum:?})",
2102 aggr_func
2103 ),
2104 }
2105 }
2106}
2107
2108type AccumCount = mz_ore::Overflowing<i128>;
2110
2111#[derive(
2122 Debug,
2123 Clone,
2124 Copy,
2125 PartialEq,
2126 Eq,
2127 PartialOrd,
2128 Ord,
2129 Serialize,
2130 Deserialize,
2131 Columnar
2132)]
2133#[columnar(derive(PartialEq, Eq, PartialOrd, Ord))]
2136enum Accum {
2137 Bool {
2139 trues: Diff,
2141 falses: Diff,
2143 },
2144 SimpleNumber {
2146 accum: AccumCount,
2148 non_nulls: Diff,
2150 },
2151 Float {
2153 accum: AccumCount,
2156 pos_infs: Diff,
2158 neg_infs: Diff,
2160 nans: Diff,
2162 non_nulls: Diff,
2164 },
2165 Interval {
2173 months: AccumCount,
2175 days: AccumCount,
2177 micros: AccumCount,
2179 non_nulls: Diff,
2181 },
2182 Numeric {
2184 accum: OrderedNumericAgg,
2186 pos_infs: Diff,
2188 neg_infs: Diff,
2190 nans: Diff,
2192 non_nulls: Diff,
2194 },
2195}
2196
2197impl IsZero for Accum {
2198 fn is_zero(&self) -> bool {
2199 match self {
2200 Accum::Bool { trues, falses } => trues.is_zero() && falses.is_zero(),
2201 Accum::SimpleNumber { accum, non_nulls } => accum.is_zero() && non_nulls.is_zero(),
2202 Accum::Float {
2203 accum,
2204 pos_infs,
2205 neg_infs,
2206 nans,
2207 non_nulls,
2208 } => {
2209 accum.is_zero()
2210 && pos_infs.is_zero()
2211 && neg_infs.is_zero()
2212 && nans.is_zero()
2213 && non_nulls.is_zero()
2214 }
2215 Accum::Interval {
2216 months,
2217 days,
2218 micros,
2219 non_nulls,
2220 } => months.is_zero() && days.is_zero() && micros.is_zero() && non_nulls.is_zero(),
2221 Accum::Numeric {
2222 accum,
2223 pos_infs,
2224 neg_infs,
2225 nans,
2226 non_nulls,
2227 } => {
2228 accum.0.is_zero()
2229 && pos_infs.is_zero()
2230 && neg_infs.is_zero()
2231 && nans.is_zero()
2232 && non_nulls.is_zero()
2233 }
2234 }
2235 }
2236}
2237
2238impl Semigroup for Accum {
2239 fn plus_equals(&mut self, other: &Accum) {
2240 match (&mut *self, other) {
2241 (
2242 Accum::Bool { trues, falses },
2243 Accum::Bool {
2244 trues: other_trues,
2245 falses: other_falses,
2246 },
2247 ) => {
2248 *trues += other_trues;
2249 *falses += other_falses;
2250 }
2251 (
2252 Accum::SimpleNumber { accum, non_nulls },
2253 Accum::SimpleNumber {
2254 accum: other_accum,
2255 non_nulls: other_non_nulls,
2256 },
2257 ) => {
2258 *accum += other_accum;
2259 *non_nulls += other_non_nulls;
2260 }
2261 (
2262 Accum::Float {
2263 accum,
2264 pos_infs,
2265 neg_infs,
2266 nans,
2267 non_nulls,
2268 },
2269 Accum::Float {
2270 accum: other_accum,
2271 pos_infs: other_pos_infs,
2272 neg_infs: other_neg_infs,
2273 nans: other_nans,
2274 non_nulls: other_non_nulls,
2275 },
2276 ) => {
2277 *accum = accum.checked_add(*other_accum).unwrap_or_else(|| {
2278 warn!("Float accumulator overflow. Incorrect results possible");
2279 accum.wrapping_add(*other_accum)
2280 });
2281 *pos_infs += other_pos_infs;
2282 *neg_infs += other_neg_infs;
2283 *nans += other_nans;
2284 *non_nulls += other_non_nulls;
2285 }
2286 (
2287 Accum::Interval {
2288 months,
2289 days,
2290 micros,
2291 non_nulls,
2292 },
2293 Accum::Interval {
2294 months: other_months,
2295 days: other_days,
2296 micros: other_micros,
2297 non_nulls: other_non_nulls,
2298 },
2299 ) => {
2300 *months += other_months;
2301 *days += other_days;
2302 *micros += other_micros;
2303 *non_nulls += other_non_nulls;
2304 }
2305 (
2306 Accum::Numeric {
2307 accum,
2308 pos_infs,
2309 neg_infs,
2310 nans,
2311 non_nulls,
2312 },
2313 Accum::Numeric {
2314 accum: other_accum,
2315 pos_infs: other_pos_infs,
2316 neg_infs: other_neg_infs,
2317 nans: other_nans,
2318 non_nulls: other_non_nulls,
2319 },
2320 ) => {
2321 let mut cx_agg = numeric::cx_agg();
2322 cx_agg.add(&mut accum.0, &other_accum.0);
2323 assert!(!cx_agg.status().rounded(), "Accum::Numeric overflow");
2329 cx_agg.reduce(&mut accum.0);
2348 *pos_infs += other_pos_infs;
2349 *neg_infs += other_neg_infs;
2350 *nans += other_nans;
2351 *non_nulls += other_non_nulls;
2352 }
2353 (l, r) => unreachable!(
2354 "Accumulator::plus_equals called with non-matching variants: {l:?} vs {r:?}"
2355 ),
2356 }
2357 }
2358}
2359
2360impl Multiply<Diff> for Accum {
2361 type Output = Accum;
2362
2363 fn multiply(self, factor: &Diff) -> Accum {
2364 let factor = *factor;
2365 match self {
2366 Accum::Bool { trues, falses } => Accum::Bool {
2367 trues: trues * factor,
2368 falses: falses * factor,
2369 },
2370 Accum::SimpleNumber { accum, non_nulls } => Accum::SimpleNumber {
2371 accum: accum * AccumCount::from(factor),
2372 non_nulls: non_nulls * factor,
2373 },
2374 Accum::Float {
2375 accum,
2376 pos_infs,
2377 neg_infs,
2378 nans,
2379 non_nulls,
2380 } => Accum::Float {
2381 accum: accum
2382 .checked_mul(AccumCount::from(factor))
2383 .unwrap_or_else(|| {
2384 warn!("Float accumulator overflow. Incorrect results possible");
2385 accum.wrapping_mul(AccumCount::from(factor))
2386 }),
2387 pos_infs: pos_infs * factor,
2388 neg_infs: neg_infs * factor,
2389 nans: nans * factor,
2390 non_nulls: non_nulls * factor,
2391 },
2392 Accum::Interval {
2393 months,
2394 days,
2395 micros,
2396 non_nulls,
2397 } => Accum::Interval {
2398 months: months * AccumCount::from(factor),
2399 days: days * AccumCount::from(factor),
2400 micros: micros * AccumCount::from(factor),
2401 non_nulls: non_nulls * factor,
2402 },
2403 Accum::Numeric {
2404 accum,
2405 pos_infs,
2406 neg_infs,
2407 nans,
2408 non_nulls,
2409 } => {
2410 let mut cx = numeric::cx_agg();
2411 let mut f = NumericAgg::from(factor.into_inner());
2412 cx.mul(&mut f, &accum.0);
2416 assert!(!cx.status().rounded(), "Accum::Numeric multiply overflow");
2422 Accum::Numeric {
2423 accum: OrderedNumericAgg(f),
2424 pos_infs: pos_infs * factor,
2425 neg_infs: neg_infs * factor,
2426 nans: nans * factor,
2427 non_nulls: non_nulls * factor,
2428 }
2429 }
2430 }
2431 }
2432}
2433
2434impl Columnation for Accum {
2437 type InnerRegion = CopyRegion<Self>;
2438}
2439
2440mod monoids {
2442
2443 use columnation::{Columnation, Region};
2459 use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
2460 use mz_expr::AggregateFunc;
2461 use mz_ore::soft_panic_or_log;
2462 use mz_repr::{Datum, Diff, Row};
2463 use serde::{Deserialize, Serialize};
2464
2465 #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
2467 pub enum ReductionMonoid {
2468 Min(Row),
2469 Max(Row),
2470 }
2471
2472 impl ReductionMonoid {
2473 pub fn finalize(&self) -> &Row {
2474 use ReductionMonoid::*;
2475 match self {
2476 Min(row) | Max(row) => row,
2477 }
2478 }
2479 }
2480
2481 impl Clone for ReductionMonoid {
2482 fn clone(&self) -> Self {
2483 use ReductionMonoid::*;
2484 match self {
2485 Min(row) => Min(row.clone()),
2486 Max(row) => Max(row.clone()),
2487 }
2488 }
2489
2490 fn clone_from(&mut self, source: &Self) {
2491 use ReductionMonoid::*;
2492
2493 let mut row = std::mem::take(match self {
2494 Min(row) | Max(row) => row,
2495 });
2496
2497 let source_row = match source {
2498 Min(row) | Max(row) => row,
2499 };
2500
2501 row.clone_from(source_row);
2502
2503 match source {
2504 Min(_) => *self = Min(row),
2505 Max(_) => *self = Max(row),
2506 }
2507 }
2508 }
2509
2510 impl Multiply<Diff> for ReductionMonoid {
2511 type Output = Self;
2512
2513 fn multiply(self, factor: &Diff) -> Self {
2514 assert!(factor.is_positive());
2519 self
2520 }
2521 }
2522
2523 impl Semigroup for ReductionMonoid {
2524 fn plus_equals(&mut self, rhs: &Self) {
2525 match (self, rhs) {
2526 (ReductionMonoid::Min(lhs), ReductionMonoid::Min(rhs)) => {
2527 let swap = {
2528 let lhs_val = lhs.unpack_first();
2529 let rhs_val = rhs.unpack_first();
2530 match (lhs_val, rhs_val) {
2532 (_, Datum::Null) => false,
2533 (Datum::Null, _) => true,
2534 (lhs, rhs) => rhs < lhs,
2535 }
2536 };
2537 if swap {
2538 lhs.clone_from(rhs);
2539 }
2540 }
2541 (ReductionMonoid::Max(lhs), ReductionMonoid::Max(rhs)) => {
2542 let swap = {
2543 let lhs_val = lhs.unpack_first();
2544 let rhs_val = rhs.unpack_first();
2545 match (lhs_val, rhs_val) {
2547 (_, Datum::Null) => false,
2548 (Datum::Null, _) => true,
2549 (lhs, rhs) => rhs > lhs,
2550 }
2551 };
2552 if swap {
2553 lhs.clone_from(rhs);
2554 }
2555 }
2556 (lhs, rhs) => {
2557 soft_panic_or_log!(
2558 "Mismatched monoid variants in reduction! lhs: {lhs:?} rhs: {rhs:?}"
2559 );
2560 }
2561 }
2562 }
2563 }
2564
2565 impl IsZero for ReductionMonoid {
2566 fn is_zero(&self) -> bool {
2567 false
2573 }
2574 }
2575
2576 impl Columnation for ReductionMonoid {
2577 type InnerRegion = ReductionMonoidRegion;
2578 }
2579
2580 #[derive(Default)]
2584 pub struct ReductionMonoidRegion {
2585 inner: <Row as Columnation>::InnerRegion,
2586 }
2587
2588 impl Region for ReductionMonoidRegion {
2589 type Item = ReductionMonoid;
2590
2591 unsafe fn copy(&mut self, item: &Self::Item) -> Self::Item {
2592 use ReductionMonoid::*;
2593 match item {
2594 Min(row) => Min(unsafe { self.inner.copy(row) }),
2595 Max(row) => Max(unsafe { self.inner.copy(row) }),
2596 }
2597 }
2598
2599 fn clear(&mut self) {
2600 self.inner.clear();
2601 }
2602
2603 fn reserve_items<'a, I>(&mut self, items: I)
2604 where
2605 Self: 'a,
2606 I: Iterator<Item = &'a Self::Item> + Clone,
2607 {
2608 self.inner
2609 .reserve_items(items.map(ReductionMonoid::finalize));
2610 }
2611
2612 fn reserve_regions<'a, I>(&mut self, regions: I)
2613 where
2614 Self: 'a,
2615 I: Iterator<Item = &'a Self> + Clone,
2616 {
2617 self.inner.reserve_regions(regions.map(|r| &r.inner));
2618 }
2619
2620 fn heap_size(&self, callback: impl FnMut(usize, usize)) {
2621 self.inner.heap_size(callback);
2622 }
2623 }
2624
2625 pub fn get_monoid(row: Row, func: &AggregateFunc) -> Option<ReductionMonoid> {
2628 match func {
2629 AggregateFunc::MaxNumeric
2630 | AggregateFunc::MaxInt16
2631 | AggregateFunc::MaxInt32
2632 | AggregateFunc::MaxInt64
2633 | AggregateFunc::MaxUInt16
2634 | AggregateFunc::MaxUInt32
2635 | AggregateFunc::MaxUInt64
2636 | AggregateFunc::MaxMzTimestamp
2637 | AggregateFunc::MaxFloat32
2638 | AggregateFunc::MaxFloat64
2639 | AggregateFunc::MaxBool
2640 | AggregateFunc::MaxString
2641 | AggregateFunc::MaxDate
2642 | AggregateFunc::MaxTimestamp
2643 | AggregateFunc::MaxTimestampTz
2644 | AggregateFunc::MaxInterval
2645 | AggregateFunc::MaxTime => Some(ReductionMonoid::Max(row)),
2646 AggregateFunc::MinNumeric
2647 | AggregateFunc::MinInt16
2648 | AggregateFunc::MinInt32
2649 | AggregateFunc::MinInt64
2650 | AggregateFunc::MinUInt16
2651 | AggregateFunc::MinUInt32
2652 | AggregateFunc::MinUInt64
2653 | AggregateFunc::MinMzTimestamp
2654 | AggregateFunc::MinFloat32
2655 | AggregateFunc::MinFloat64
2656 | AggregateFunc::MinBool
2657 | AggregateFunc::MinString
2658 | AggregateFunc::MinDate
2659 | AggregateFunc::MinTimestamp
2660 | AggregateFunc::MinTimestampTz
2661 | AggregateFunc::MinInterval
2662 | AggregateFunc::MinTime => Some(ReductionMonoid::Min(row)),
2663 AggregateFunc::SumInt16
2664 | AggregateFunc::SumInt32
2665 | AggregateFunc::SumInt64
2666 | AggregateFunc::SumUInt16
2667 | AggregateFunc::SumUInt32
2668 | AggregateFunc::SumUInt64
2669 | AggregateFunc::SumFloat32
2670 | AggregateFunc::SumFloat64
2671 | AggregateFunc::SumNumeric
2672 | AggregateFunc::SumInterval
2673 | AggregateFunc::Count
2674 | AggregateFunc::Any
2675 | AggregateFunc::All
2676 | AggregateFunc::Dummy
2677 | AggregateFunc::JsonbAgg { .. }
2678 | AggregateFunc::JsonbObjectAgg { .. }
2679 | AggregateFunc::MapAgg { .. }
2680 | AggregateFunc::ArrayConcat { .. }
2681 | AggregateFunc::ListConcat { .. }
2682 | AggregateFunc::StringAgg { .. }
2683 | AggregateFunc::RowNumber { .. }
2684 | AggregateFunc::Rank { .. }
2685 | AggregateFunc::DenseRank { .. }
2686 | AggregateFunc::LagLead { .. }
2687 | AggregateFunc::FirstValue { .. }
2688 | AggregateFunc::LastValue { .. }
2689 | AggregateFunc::WindowAggregate { .. }
2690 | AggregateFunc::FusedValueWindowFunc { .. }
2691 | AggregateFunc::FusedWindowAggregate { .. } => None,
2692 }
2693 }
2694}
2695
2696mod window_agg_helpers {
2697 use crate::render::reduce::*;
2698
2699 pub enum OneByOneAggrImpls {
2704 Accumulable(AccumulableOneByOneAggr),
2705 Hierarchical(HierarchicalOneByOneAggr),
2706 Basic(mz_expr::NaiveOneByOneAggr),
2707 }
2708
2709 impl mz_expr::OneByOneAggr for OneByOneAggrImpls {
2710 fn new(agg: &AggregateFunc, reverse: bool) -> Self {
2711 match reduction_type(agg) {
2712 ReductionType::Basic => {
2713 OneByOneAggrImpls::Basic(mz_expr::NaiveOneByOneAggr::new(agg, reverse))
2714 }
2715 ReductionType::Accumulable => {
2716 OneByOneAggrImpls::Accumulable(AccumulableOneByOneAggr::new(agg))
2717 }
2718 ReductionType::Hierarchical => {
2719 OneByOneAggrImpls::Hierarchical(HierarchicalOneByOneAggr::new(agg))
2720 }
2721 }
2722 }
2723
2724 fn give(&mut self, d: &Datum) {
2725 match self {
2726 OneByOneAggrImpls::Basic(i) => i.give(d),
2727 OneByOneAggrImpls::Accumulable(i) => i.give(d),
2728 OneByOneAggrImpls::Hierarchical(i) => i.give(d),
2729 }
2730 }
2731
2732 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2733 match self {
2735 OneByOneAggrImpls::Basic(i) => i.get_current_aggregate(temp_storage),
2736 OneByOneAggrImpls::Accumulable(i) => i.get_current_aggregate(temp_storage),
2737 OneByOneAggrImpls::Hierarchical(i) => i.get_current_aggregate(temp_storage),
2738 }
2739 }
2740 }
2741
2742 pub struct AccumulableOneByOneAggr {
2743 aggr_func: AggregateFunc,
2744 accum: Accum,
2745 total: Diff,
2746 }
2747
2748 impl AccumulableOneByOneAggr {
2749 fn new(aggr_func: &AggregateFunc) -> Self {
2750 AccumulableOneByOneAggr {
2751 aggr_func: aggr_func.clone(),
2752 accum: accumulable_zero(aggr_func),
2753 total: Diff::ZERO,
2754 }
2755 }
2756
2757 fn give(&mut self, d: &Datum) {
2758 self.accum
2759 .plus_equals(&datum_to_accumulator(&self.aggr_func, d.clone()));
2760 self.total += Diff::ONE;
2761 }
2762
2763 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2764 temp_storage.make_datum(|packer| {
2765 packer.push(finalize_accum(&self.aggr_func, &self.accum, self.total));
2766 })
2767 }
2768 }
2769
2770 pub struct HierarchicalOneByOneAggr {
2771 aggr_func: AggregateFunc,
2772 monoid: ReductionMonoid,
2775 }
2776
2777 impl HierarchicalOneByOneAggr {
2778 fn new(aggr_func: &AggregateFunc) -> Self {
2779 let mut row_buf = Row::default();
2780 row_buf.packer().push(Datum::Null);
2781 HierarchicalOneByOneAggr {
2782 aggr_func: aggr_func.clone(),
2783 monoid: get_monoid(row_buf, aggr_func)
2784 .expect("aggr_func should be a hierarchical aggregation function"),
2785 }
2786 }
2787
2788 fn give(&mut self, d: &Datum) {
2789 let mut row_buf = Row::default();
2790 row_buf.packer().push(d);
2791 let m = get_monoid(row_buf, &self.aggr_func)
2792 .expect("aggr_func should be a hierarchical aggregation function");
2793 self.monoid.plus_equals(&m);
2794 }
2795
2796 fn get_current_aggregate<'a>(&self, temp_storage: &'a RowArena) -> Datum<'a> {
2797 temp_storage.make_datum(|packer| packer.extend(self.monoid.finalize().iter()))
2798 }
2799 }
2800}
2801
2802#[cfg(test)]
2803mod tests {
2804 use super::*;
2805
2806 #[allow(clippy::as_conversions)]
2810 fn saturating_convert(n: f64) -> i128 {
2811 (n * FLOAT_SCALE) as i128
2812 }
2813
2814 #[mz_ore::test]
2815 fn float_to_fixed_point_matches_saturating_in_range() {
2816 let cases = [
2820 0.0,
2821 -0.0,
2822 1.0,
2823 -1.0,
2824 0.1,
2825 -0.1,
2826 0.5,
2827 -0.5,
2828 3.25,
2829 -3.25,
2830 123456.789,
2831 -123456.789,
2832 1e10,
2833 -1e10,
2834 1e20,
2835 -1e20,
2836 5e30, -5e30,
2838 ];
2839 for n in cases {
2840 assert_eq!(
2841 float_to_fixed_point(n),
2842 saturating_convert(n),
2843 "mismatch for n = {n}"
2844 );
2845 }
2846 }
2847
2848 #[mz_ore::test]
2849 fn float_to_fixed_point_truncates_toward_zero() {
2850 assert_eq!(float_to_fixed_point(1.75), 29_360_128);
2852 assert_eq!(float_to_fixed_point(-1.75), -29_360_128);
2853
2854 let frac = 0.123_456_7_f64;
2856 assert_eq!(float_to_fixed_point(frac), saturating_convert(frac));
2857 assert_eq!(float_to_fixed_point(-frac), saturating_convert(-frac));
2858 assert_eq!(float_to_fixed_point(-frac), -float_to_fixed_point(frac));
2859 }
2860
2861 #[mz_ore::test]
2862 fn float_to_fixed_point_subnormals_round_to_zero() {
2863 assert_eq!(float_to_fixed_point(0.0), 0);
2864 assert_eq!(float_to_fixed_point(-0.0), 0);
2865 assert_eq!(float_to_fixed_point(f64::MIN_POSITIVE / 2.0), 0);
2866 assert_eq!(float_to_fixed_point(5e-324), 0); }
2868
2869 #[mz_ore::test]
2870 fn float_to_fixed_point_cancels_large_finite_values() {
2871 for &n in &[1.1e31_f64, 1e32, 5e33, 1e284] {
2876 assert_eq!(
2877 float_to_fixed_point(n).wrapping_add(float_to_fixed_point(-n)),
2878 0,
2879 "n = {n} did not cancel with -n"
2880 );
2881 }
2882 }
2883
2884 #[mz_ore::test]
2885 fn float_to_fixed_point_sum_via_accumulator() {
2886 let func = AggregateFunc::SumFloat64;
2888 let mut acc = accumulable_zero(&func);
2889 acc.plus_equals(&datum_to_accumulator(&func, Datum::from(1.1e31_f64)));
2890 acc.plus_equals(&datum_to_accumulator(&func, Datum::from(-1.1e31_f64)));
2891 let datum = finalize_accum(&func, &acc, Diff::from(2_i64));
2892 assert_eq!(datum, Datum::from(0.0_f64));
2893 }
2894
2895 fn sample_accums() -> Vec<Accum> {
2897 let mut cx = numeric::cx_datum();
2898 let mut numeric = |s: &str| Datum::from(cx.parse(s).unwrap());
2899 let cases: Vec<(AggregateFunc, Vec<Datum>)> = vec![
2900 (AggregateFunc::Count, vec![Datum::Null, Datum::Int64(5)]),
2901 (
2902 AggregateFunc::SumInt64,
2903 vec![Datum::Int64(-7), Datum::Int64(i64::MAX)],
2904 ),
2905 (
2906 AggregateFunc::SumUInt16,
2907 vec![Datum::UInt16(3), Datum::Null],
2908 ),
2909 (
2910 AggregateFunc::Any,
2911 vec![Datum::True, Datum::False, Datum::Null],
2912 ),
2913 (
2914 AggregateFunc::SumFloat64,
2915 vec![
2916 Datum::from(1.5_f64),
2917 Datum::from(f64::NAN),
2918 Datum::from(f64::NEG_INFINITY),
2919 ],
2920 ),
2921 (
2922 AggregateFunc::SumNumeric,
2923 vec![
2924 numeric("-12345.678"),
2925 numeric("9e39"),
2926 numeric("NaN"),
2927 numeric("Infinity"),
2928 Datum::Null,
2929 ],
2930 ),
2931 (
2932 AggregateFunc::SumInterval,
2933 vec![
2934 Datum::Interval(Interval::new(-13, 40, 1_234_567)),
2935 Datum::Interval(Interval::new(i32::MAX, i32::MIN, i64::MAX)),
2936 Datum::Null,
2937 ],
2938 ),
2939 ];
2940 let mut accums = Vec::new();
2941 for (func, datums) in cases {
2942 let mut sum = accumulable_zero(&func);
2943 accums.push(sum);
2944 for datum in datums {
2945 let accum = datum_to_accumulator(&func, datum);
2946 sum.plus_equals(&accum);
2947 accums.push(accum);
2948 accums.push(accum.multiply(&Diff::from(-1_i64)));
2949 }
2950 accums.push(sum);
2951 }
2952 accums
2953 }
2954
2955 #[mz_ore::test]
2956 fn accum_columnar_round_trip() {
2957 use columnar::bytes::indexed::{DecodedStore, encode};
2958 use columnar::{AsBytes, Borrow, BorrowedOf, FromBytes, Index, Len};
2959 use differential_dataflow::trace::implementations::BatchContainer;
2960
2961 let accums = sample_accums();
2962 let container = Accum::as_columns(accums.iter());
2963 assert_eq!(container.len(), accums.len());
2964 let borrowed = container.borrow();
2965 for (index, accum) in accums.iter().enumerate() {
2966 assert_eq!(Accum::into_owned(borrowed.get(index)), *accum);
2967 }
2968 for (i, a) in accums.iter().enumerate() {
2969 for (j, b) in accums.iter().enumerate() {
2970 assert_eq!(borrowed.get(i).cmp(&borrowed.get(j)), a.cmp(b));
2971 }
2972 }
2973
2974 let bytes: Vec<&[u8]> = borrowed.as_bytes().map(|(_align, bytes)| bytes).collect();
2975 let decoded = BorrowedOf::<Accum>::from_bytes(&mut bytes.into_iter());
2976 for (index, accum) in accums.iter().enumerate() {
2977 assert_eq!(Accum::into_owned(decoded.get(index)), *accum);
2978 }
2979 let mut words = Vec::new();
2982 encode(&mut words, &borrowed);
2983 let decoded = BorrowedOf::<Accum>::from_store(&DecodedStore::new(&words), &mut 0);
2984 for (index, accum) in accums.iter().enumerate() {
2985 assert_eq!(Accum::into_owned(decoded.get(index)), *accum);
2986 }
2987
2988 let diffs: Vec<(Vec<Accum>, Diff)> = accums
2990 .chunks(3)
2991 .map(|chunk| (chunk.to_vec(), Diff::ONE))
2992 .collect();
2993 let mut coltainer = Coltainer::<(Vec<Accum>, Diff)>::default();
2994 for diff in &diffs {
2995 coltainer.push_own(diff);
2996 }
2997 assert_eq!(coltainer.len(), diffs.len());
2998 for (index, diff) in diffs.iter().enumerate() {
2999 assert_eq!(
3000 <Coltainer<(Vec<Accum>, Diff)>>::into_owned(coltainer.index(index)),
3001 *diff
3002 );
3003 }
3004 let mut sum = <Coltainer<(Vec<Accum>, Diff)>>::into_owned(coltainer.index(0));
3005 sum.plus_equals(&sum.clone().multiply(&Diff::from(-1_i64)));
3006 assert!(sum.is_zero());
3007 }
3008}