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