1use std::cell::RefCell;
15use std::collections::BTreeMap;
16use std::rc::Rc;
17
18use columnar::{Columnar, Index};
19use differential_dataflow::AsCollection;
20use differential_dataflow::hashable::Hashable;
21use differential_dataflow::lattice::Lattice;
22use differential_dataflow::operators::arrange::{Arranged, TraceAgent};
23use differential_dataflow::operators::iterate::Variable as SemigroupVariable;
24use differential_dataflow::trace::cursor::{BatchCursor, BatchValOwn};
25use differential_dataflow::trace::{Builder, Cursor, Navigable, Trace};
26use differential_dataflow::{Data, VecCollection};
27use mz_compute_types::dyncfgs::{ENABLE_COMPUTE_TEMPORAL_BUCKETING, TEMPORAL_BUCKETING_SUMMARY};
28use mz_compute_types::plan::ArrangementStrategy;
29use mz_compute_types::plan::scalar::LirScalarExpr;
30use mz_compute_types::plan::top_k::{
31 BasicTopKPlan, MonotonicTop1Plan, MonotonicTopKPlan, TopKPlan,
32};
33use mz_expr::func::CastUint64ToInt64;
34use mz_expr::{BinaryFunc, Columns, Eval, EvalError, UnaryFunc, func, permutation_for_arrangement};
35use mz_ore::cast::CastFrom;
36use mz_ore::soft_assert_or_log;
37use mz_repr::fixed_length::ExtendDatums;
38use mz_repr::{Datum, DatumVec, Diff, ReprScalarType, Row, SharedRow};
39use mz_timely_util::columnar::builder::ColumnBuilder;
40use mz_timely_util::columnation::ColumnationChunker;
41use mz_timely_util::operator::CollectionExt;
42use timely::Container;
43use timely::container::{CapacityContainerBuilder, PushInto};
44use timely::dataflow::channels::pact::Pipeline;
45use timely::dataflow::operators::Operator;
46use timely::dataflow::operators::generic::OutputBuilder;
47use timely::dataflow::operators::generic::builder_rc::OperatorBuilder;
48
49use crate::extensions::arrange::{ArrangementSize, KeyCollection, MzArrange};
50use crate::extensions::reduce::{ClearContainer, MzReduce};
51use crate::render::Pairer;
52use crate::render::columnar::{ColCollection, flat_map_datums};
53use crate::render::context::{ArrangementFlavor, CollectionBundle, Context};
54use crate::render::errors::DataflowErrorSer;
55use crate::render::errors::MaybeValidatingRow;
56use crate::typedefs::{ErrBatcher, ErrBuilder, KeyBatcher, MzTimestamp, RowRowSpine, RowSpine};
57use mz_row_spine::{
58 DatumContainer, DatumSeq, RowBatcher, RowBuilder, RowRowBatcher, RowRowBuilder, RowValBuilder,
59 RowValSpine,
60};
61
62impl<'scope, T: crate::render::RenderTimestamp + crate::render::MaybeBucketByTime>
64 Context<'scope, T>
65{
66 pub(crate) fn render_topk(
67 &self,
68 input: CollectionBundle<'scope, T>,
69 top_k_plan: TopKPlan,
70 temporal_bucketing_strategy: ArrangementStrategy,
71 ) -> CollectionBundle<'scope, T> {
72 let (ok_input, err_input) = input
75 .collection
76 .clone()
77 .expect("The unarranged collection doesn't exist.");
78
79 if matches!(
97 temporal_bucketing_strategy,
98 ArrangementStrategy::TemporalBucketing
99 ) {
100 let must_consolidate = match &top_k_plan {
101 TopKPlan::MonotonicTop1(p) => p.must_consolidate,
102 TopKPlan::MonotonicTopK(p) => p.must_consolidate,
103 TopKPlan::Basic(_) => true,
104 };
105 soft_assert_or_log!(
106 must_consolidate,
107 "TopK with `TemporalBucketing` should not have `must_consolidate = false`; \
108 `RelaxMustConsolidate` only runs on single-time dataflows where \
109 `mz_now()` has been const-folded and no temporal bucketing is set",
110 );
111 }
112 let ok_input = if matches!(
115 temporal_bucketing_strategy,
116 ArrangementStrategy::TemporalBucketing
117 ) && ENABLE_COMPUTE_TEMPORAL_BUCKETING.get(&self.config_set)
118 {
119 let summary: mz_repr::Timestamp = TEMPORAL_BUCKETING_SUMMARY
120 .get(&self.config_set)
121 .try_into()
122 .expect("must fit");
123 T::maybe_apply_temporal_bucketing(ok_input.inner, self.as_of_frontier.clone(), summary)
124 } else {
125 ok_input
126 };
127
128 let outer_scope = ok_input.scope();
130 let bundle = outer_scope.clone().region_named("TopK", |inner| {
131 let ok_input = ok_input.enter_region(inner);
132 let mut err_collection = err_input.enter_region(inner);
133
134 match top_k_plan.limit().map(|l| (l.as_literal(), l)) {
142 None => {}
143 Some((Some(Ok(literal)), _))
144 if literal == Datum::Null || literal.unwrap_int64() >= 0 => {}
145 Some((_, expr)) => {
146 let expr = expr.clone();
152 let (_, errors) = flat_map_datums::<
157 _,
158 CapacityContainerBuilder<Vec<(Row, T, Diff)>>,
159 _,
160 >(
161 ok_input.clone(),
162 "TopKLimitCheck",
163 usize::MAX,
164 {
165 let mut datum_vec = mz_repr::DatumVec::new();
166 move |row_datums, time, diff, _ok_session, err_session| {
167 let temp_storage = mz_repr::RowArena::new();
168 let mut datums = datum_vec.borrow();
172 datums.extend(row_datums.iter());
173 match expr.eval(&datums[..], &temp_storage) {
174 Ok(l) if l != Datum::Null && l.unwrap_int64() < 0 => {
175 err_session.give((EvalError::NegLimit.into(), time, diff));
176 1
177 }
178 Ok(_) => 0,
179 Err(e) => {
180 err_session.give((e.into(), time, diff));
181 1
182 }
183 }
184 }
185 },
186 );
187 err_collection = err_collection.concat(errors.as_collection());
188 }
189 }
190
191 let bundle = match top_k_plan {
192 TopKPlan::MonotonicTop1(MonotonicTop1Plan {
193 group_key,
194 order_key,
195 arity,
196 must_consolidate,
197 }) => {
198 let (arrangement, errs) = self.render_top1_monotonic(
199 ok_input,
200 group_key.clone(),
201 order_key,
202 arity,
203 must_consolidate,
204 );
205 err_collection = err_collection.concat(errs);
206
207 let errs: KeyCollection<_, _, _> = err_collection.clone().into();
213 let err_arrangement = errs
214 .mz_arrange::<ColumnationChunker<_>, ErrBatcher<_, _>, ErrBuilder<_, _>, _>(
215 "Arrange bundle err",
216 );
217 CollectionBundle::from_columns(
218 group_key.iter().copied(),
219 ArrangementFlavor::Local(arrangement, err_arrangement),
220 )
221 }
222 TopKPlan::MonotonicTopK(MonotonicTopKPlan {
223 order_key,
224 group_key,
225 arity,
226 mut limit,
227 must_consolidate,
228 }) => {
229 if let Some(expr) = limit.as_mut() {
231 let mut map = BTreeMap::new();
232 for (index, column) in group_key.iter().enumerate() {
233 map.insert(*column, index);
234 }
235 expr.permute_map(&map);
236 }
237
238 let ok_scope = ok_input.scope();
240 let collection =
241 map_topk_key(ok_input, "MonotonicTopK input", move |datums, _row| {
242 SharedRow::pack(group_key.iter().map(|i| datums[*i]))
243 })
244 .consolidate_named_if::<KeyBatcher<_, _, _>>(
245 must_consolidate,
246 "Consolidated MonotonicTopK input",
247 );
248
249 let error_logger = self.error_logger();
251 let (collection, errs) = collection.ensure_monotonic(move |data, diff| {
252 error_logger.log(
253 "Non-monotonic input to MonotonicTopK",
254 &format!("data={data:?}, diff={diff}"),
255 );
256 let m = "tried to build monotonic top-k on non-monotonic input".into();
257 (DataflowErrorSer::from(EvalError::Internal(m)), Diff::ONE)
258 });
259 err_collection = err_collection.concat(errs);
260
261 let collection = if let Some(limit) = limit.clone() {
267 render_intra_ts_thinning(collection, order_key.clone(), limit)
268 } else {
269 collection
270 };
271
272 let pairer = Pairer::new(1);
273 let collection = collection.map(move |(group_row, row)| {
274 let hash = row.hashed();
275 let hash_key = pairer.merge(std::iter::once(Datum::from(hash)), &group_row);
276 (hash_key, row)
277 });
278
279 let delay = std::time::Duration::from_secs(10);
288 let (retractions_var, retractions) = SemigroupVariable::new(
289 ok_scope,
290 <T as crate::render::RenderTimestamp>::system_delay(
291 delay.try_into().expect("must fit"),
292 ),
293 );
294 let thinned = collection.clone().concat(retractions.negate());
295
296 let (result, errs) =
302 self.build_topk_stage(thinned, order_key, 1u64, 0, limit, arity, false);
303 let result = CollectionExt::consolidate_named::<KeyBatcher<_, _, _>>(
305 result,
306 "Monotonic TopK final consolidate",
307 );
308 retractions_var.set(collection.concat(result.clone().negate()));
309 soft_assert_or_log!(
310 errs.is_none(),
311 "requested no validation, but received error collection"
312 );
313
314 CollectionBundle::from_edge(topk_result_to_columnar(result), err_collection)
315 }
316 TopKPlan::Basic(BasicTopKPlan {
317 group_key,
318 order_key,
319 offset,
320 mut limit,
321 arity,
322 buckets,
323 }) => {
324 if let Some(expr) = limit.as_mut() {
326 let mut map = BTreeMap::new();
327 for (index, column) in group_key.iter().enumerate() {
328 map.insert(*column, index);
329 }
330 expr.permute_map(&map);
331 }
332
333 let (oks, errs) = self.build_topk(
334 ok_input, group_key, order_key, offset, limit, arity, buckets,
335 );
336 err_collection = err_collection.concat(errs);
337 CollectionBundle::from_edge(oks, err_collection)
338 }
339 };
340
341 bundle.leave_region(outer_scope)
343 });
344
345 bundle
346 }
347
348 fn build_topk<'s>(
350 &self,
351 collection: ColCollection<'s, T>,
352 group_key: Vec<usize>,
353 order_key: Vec<mz_expr::ColumnOrder>,
354 offset: usize,
355 limit: Option<LirScalarExpr>,
356 arity: usize,
357 buckets: Vec<u64>,
358 ) -> (
359 ColCollection<'s, T>,
360 VecCollection<'s, T, DataflowErrorSer, Diff>,
361 ) {
362 let pairer = Pairer::new(1);
363 let mut collection = map_topk_key(collection, "TopK input", move |datums, row| {
364 let row_hash = row.hashed();
365 let iterator = group_key.iter().map(|i| datums[*i]);
366 pairer.merge(std::iter::once(Datum::from(row_hash)), iterator)
367 });
368
369 let mut validating = true;
370 let mut err_collection: Option<VecCollection<'s, T, _, _>> = None;
371
372 if let Some(mut limit) = limit.clone() {
373 if offset > 0 {
376 let new_limit = (|| {
377 let limit = limit.as_literal_int64()?;
378 let offset = i64::try_from(offset).ok()?;
379 limit.checked_add(offset)
380 })();
381
382 if let Some(new_limit) = new_limit {
383 limit =
384 LirScalarExpr::literal_ok(Datum::Int64(new_limit), ReprScalarType::Int64);
385 } else {
386 limit = limit.call_binary(
387 LirScalarExpr::literal_ok(
388 Datum::UInt64(u64::cast_from(offset)),
389 ReprScalarType::UInt64,
390 )
391 .call_unary(UnaryFunc::CastUint64ToInt64(CastUint64ToInt64)),
392 BinaryFunc::AddInt64(func::AddInt64),
393 );
394 }
395 }
396
397 for bucket in buckets.into_iter() {
401 let (oks, errs) = self.build_topk_stage(
405 collection,
406 order_key.clone(),
407 bucket,
408 0,
409 Some(limit.clone()),
410 arity,
411 validating,
412 );
413 collection = oks;
414 if validating {
415 err_collection = errs;
416 validating = false;
417 }
418 }
419 }
420
421 let (oks, errs) = self.build_topk_stage(
425 collection, order_key, 1u64, offset, limit, arity, validating,
426 );
427 let oks =
429 CollectionExt::consolidate_named::<KeyBatcher<_, _, _>>(oks, "TopK final consolidate");
430 collection = oks;
431 if validating {
432 err_collection = errs;
433 }
434 (
435 topk_result_to_columnar(collection),
436 err_collection.expect("at least one stage validated its inputs"),
437 )
438 }
439
440 fn build_topk_stage<'s>(
473 &self,
474 collection: VecCollection<'s, T, (Row, Row), Diff>,
475 order_key: Vec<mz_expr::ColumnOrder>,
476 modulus: u64,
477 offset: usize,
478 limit: Option<LirScalarExpr>,
479 arity: usize,
480 validating: bool,
481 ) -> (
482 VecCollection<'s, T, (Row, Row), Diff>,
483 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
484 ) {
485 let input = collection.map(move |(hash_key, row)| {
488 let mut hash_key_iter = hash_key.iter();
489 let hash = hash_key_iter.next().unwrap().unwrap_uint64() % modulus;
490 let hash_key = SharedRow::pack(std::iter::once(hash.into()).chain(hash_key_iter));
491 (hash_key, row)
492 });
493
494 let (input, oks, errs) = if validating {
496 let (input, stage) = build_topk_negated_stage::<
498 T,
499 RowValBuilder<_, _, _>,
500 RowValSpine<Result<Row, Row>, _, _>,
501 >(&input, order_key, offset, limit, arity);
502 let stage = stage.as_collection(|k, v| (k.to_row(), v.clone()));
503
504 let error_logger = self.error_logger();
506 type CB<C> = CapacityContainerBuilder<C>;
507 let (oks, errs) = stage.map_fallible::<CB<_>, CB<_>, _, _, _>(
508 "Demuxing Errors",
509 move |(hk, result)| match result {
510 Err(v) => {
511 let mut hk_iter = hk.iter();
512 let h = hk_iter.next().unwrap().unwrap_uint64();
513 let k = SharedRow::pack(hk_iter);
514 let message = "Negative multiplicities in TopK";
515 error_logger.log(message, &format!("k={k:?}, h={h}, v={v:?}"));
516 Err(EvalError::Internal(message.into()).into())
517 }
518 Ok(t) => Ok((hk, t)),
519 },
520 );
521 (input, oks, Some(errs))
522 } else {
523 let (input, stage) =
525 build_topk_negated_stage::<T, RowRowBuilder<_, _>, RowRowSpine<_, _>>(
526 &input, order_key, offset, limit, arity,
527 );
528 let stage = stage.as_collection(|k, v| (k.to_row(), v.to_row()));
530
531 (input, stage, None)
532 };
533 let input = input.as_collection(|k, v| (k.to_row(), v.to_row()));
534 (oks.concat(input), errs)
535 }
536
537 fn render_top1_monotonic<'s>(
538 &self,
539 collection: ColCollection<'s, T>,
540 group_key: Vec<usize>,
541 order_key: Vec<mz_expr::ColumnOrder>,
542 arity: usize,
543 must_consolidate: bool,
544 ) -> (
545 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
546 VecCollection<'s, T, DataflowErrorSer, Diff>,
547 ) {
548 let key: Vec<LirScalarExpr> = group_key
555 .iter()
556 .map(|c| LirScalarExpr::column(*c))
557 .collect();
558 let (_permutation, thinning) = permutation_for_arrangement(&key, arity);
559
560 let collection = map_topk_key(collection, "MonotonicTop1 input", move |datums, _row| {
565 SharedRow::pack(group_key.iter().map(|i| datums[*i]))
566 })
567 .consolidate_named_if::<KeyBatcher<_, _, _>>(
568 must_consolidate,
569 "Consolidated MonotonicTop1 input",
570 );
571
572 let error_logger = self.error_logger();
574 let (partial, errs) = collection.ensure_monotonic(move |data, diff| {
575 error_logger.log(
576 "Non-monotonic input to MonotonicTop1",
577 &format!("data={data:?}, diff={diff}"),
578 );
579 let m = "tried to build monotonic top-1 on non-monotonic input".into();
580 (EvalError::Internal(m).into(), Diff::ONE)
581 });
582 let partial: KeyCollection<_, _, _> = partial
583 .explode_one(move |(group_key, row)| {
584 (
585 group_key,
586 monoids::Top1Monoid {
587 row,
588 order_key: order_key.clone(),
589 },
590 )
591 })
592 .into();
593 let result = partial
594 .mz_arrange::<
595 ColumnationChunker<_>,
596 RowBatcher<_, _>,
597 RowBuilder<_, _>,
598 RowSpine<_, _>,
599 >(
600 "Arranged MonotonicTop1 partial [val: empty]",
601 )
602 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
603 "MonotonicTop1",
604 {
605 let mut datum_vec = mz_repr::DatumVec::new();
606 move |_key, input, output| {
607 let accum: &monoids::Top1Monoid = &input[0].1;
608 let datums = datum_vec.borrow_with(&accum.row);
609 let value = SharedRow::pack(thinning.iter().map(|i| datums[*i]));
610 output.push((value, Diff::ONE));
611 }
612 },
613 );
614 (result, errs)
615 }
616}
617
618fn map_topk_key<'s, T, L>(
628 edge: ColCollection<'s, T>,
629 name: &str,
630 mut key: L,
631) -> VecCollection<'s, T, (Row, Row), Diff>
632where
633 T: crate::render::RenderTimestamp,
634 L: FnMut(&[Datum], &Row) -> Row + 'static,
635{
636 let mut builder = OperatorBuilder::new(name.to_string(), edge.inner.scope());
637 let (output, stream) = builder.new_output();
638 let mut output =
639 OutputBuilder::<_, CapacityContainerBuilder<Vec<((Row, Row), T, Diff)>>>::from(output);
640 let mut input = builder.new_input(edge.inner, Pipeline);
641 builder.build(move |_capabilities| {
642 let mut datum_vec = mz_repr::DatumVec::new();
643 move |_frontiers| {
644 let mut output = output.activate();
645 input.for_each(|time, data| {
646 let mut session = output.session_with_builder(&time);
647 for (row, t, d) in data.borrow().into_index_iter() {
648 let value_row: Row = Columnar::into_owned(row);
649 let key_row = {
650 let datums = datum_vec.borrow_with(&value_row);
651 key(&datums, &value_row)
652 };
653 session.give((
654 (key_row, value_row),
655 Columnar::into_owned(t),
656 Columnar::into_owned(d),
657 ));
658 }
659 });
660 }
661 });
662 stream.as_collection()
663}
664
665fn topk_result_to_columnar<'s, T>(
675 collection: VecCollection<'s, T, (Row, Row), Diff>,
676) -> ColCollection<'s, T>
677where
678 T: crate::render::RenderTimestamp,
679{
680 let stream = collection
681 .inner
682 .unary::<ColumnBuilder<(Row, T, Diff)>, _, _, _>(Pipeline, "TopKUnkey", |_cap, _info| {
683 move |input, output| {
684 input.for_each(|time, data| {
685 let mut session = output.session_with_builder(&time);
686 for ((_key_hash, row), t, d) in data.drain(..) {
687 session.give((&row, &t, &d));
688 }
689 });
690 }
691 });
692 stream.as_collection()
693}
694
695fn build_topk_negated_stage<'s, T, Bu, Tr>(
703 input: &VecCollection<'s, T, (Row, Row), Diff>,
704 order_key: Vec<mz_expr::ColumnOrder>,
705 offset: usize,
706 limit: Option<LirScalarExpr>,
707 arity: usize,
708) -> (
709 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
710 Arranged<'s, TraceAgent<Tr>>,
711)
712where
713 T: MzTimestamp,
714 Bu: Builder<
715 Time = T,
716 Input: Container + ClearContainer + PushInto<((Row, BatchValOwn<Tr>), T, Diff)>,
717 Output = Tr::Batch,
718 > + 'static,
719 Tr: Trace<Batch: Navigable, Time = T> + 'static,
720 for<'a> BatchCursor<Tr>: Cursor<
721 Key<'a> = DatumSeq<'a>,
722 KeyContainer = DatumContainer,
723 ValOwn: Data + MaybeValidatingRow<Row, Row>,
724 Time = T,
725 Diff = Diff,
726 >,
727 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
728{
729 let mut datum_vec = mz_repr::DatumVec::new();
730
731 let arranged = input
736 .clone()
737 .mz_arrange::<
738 ColumnationChunker<_>,
739 RowRowBatcher<_, _>,
740 RowRowBuilder<_, _>,
741 RowRowSpine<_, _>,
742 >(
743 "Arranged TopK input",
744 );
745
746 let limit = limit.map(|l| match l.as_literal() {
748 Some(Ok(Datum::Null)) => Ok(Diff::MAX),
749 Some(Ok(d)) => Ok(Diff::from(d.unwrap_int64())),
750 _ => Err(l),
751 });
752
753 let reduced = arranged
754 .clone()
755 .mz_reduce_abelian::<_, Bu, Tr, _>("Reduced TopK input", {
756 move |hash_key, source, target: &mut Vec<(BatchValOwn<Tr>, Diff)>| {
757 let limit = match &limit {
759 Some(Ok(lit)) => Some(*lit),
760 Some(Err(expr)) => {
761 let temp_storage = mz_repr::RowArena::new();
764 let mut key_datums = datum_vec.borrow();
765 hash_key.extend_datums(&temp_storage, &mut key_datums, None);
766 let datum_limit = expr
768 .eval(&key_datums[1..], &temp_storage)
769 .unwrap_or(Datum::Int64(0));
770 Some(match datum_limit {
771 Datum::Null => Diff::MAX,
772 d => Diff::from(d.unwrap_int64()),
773 })
774 }
775 None => None,
776 };
777
778 if let Some(err) = BatchValOwn::<Tr>::into_error() {
779 for (datums, diff) in source.iter() {
780 if diff.is_positive() {
781 continue;
782 }
783 target.push((err((*datums).to_row()), Diff::ONE));
784 return;
785 }
786 }
787
788 let must_shrink = offset > 0
790 || limit
791 .map(|l| source.iter().map(|(_, d)| *d).sum::<Diff>() > l)
792 .unwrap_or(false);
793 if !must_shrink {
794 return;
795 }
796
797 target.reserve(source.len());
801 for (datums, diff) in source.iter() {
802 target.push((BatchValOwn::<Tr>::ok((*datums).to_row()), -diff));
803 }
804 let mut offset = offset;
806 let mut limit = limit;
807
808 let mut indexes = (0..source.len()).collect::<Vec<_>>();
810 let temp_storage = mz_repr::RowArena::new();
813 let mut buffer = Vec::with_capacity(arity * source.len());
814 for (index, (datums, _)) in source.iter().enumerate() {
815 datums.extend_datums(&temp_storage, &mut buffer, None);
816 assert_eq!(buffer.len(), arity * (index + 1));
817 }
818 let width = buffer.len() / source.len();
819
820 indexes.sort_by(|left, right| {
822 let left = &buffer[left * width..][..width];
823 let right = &buffer[right * width..][..width];
824 mz_expr::compare_columns(&order_key, left, right, || left.cmp(right))
827 });
828
829 for index in indexes.into_iter() {
832 let (datums, mut diff) = source[index];
833 if !diff.is_positive() {
834 continue;
835 }
836 if offset > 0 {
838 let to_skip =
839 std::cmp::min(offset, usize::try_from(diff.into_inner()).unwrap());
840 offset -= to_skip;
841 diff -= Diff::try_from(to_skip).unwrap();
842 }
843 if let Some(limit) = &mut limit {
845 diff = std::cmp::min(diff, Diff::from(*limit));
846 *limit -= diff;
847 }
848 if diff.is_positive() {
850 target.push((BatchValOwn::<Tr>::ok(datums.to_row()), diff));
853 }
854 }
855 }
856 });
857 (arranged, reduced)
858}
859
860fn render_intra_ts_thinning<'s, T>(
861 collection: VecCollection<'s, T, (Row, Row), Diff>,
862 order_key: Vec<mz_expr::ColumnOrder>,
863 limit: LirScalarExpr,
864) -> VecCollection<'s, T, (Row, Row), Diff>
865where
866 T: timely::progress::Timestamp + Lattice,
867{
868 let mut datum_vec = mz_repr::DatumVec::new();
869
870 let mut aggregates = BTreeMap::new();
871 let shared = Rc::new(RefCell::new(monoids::Top1MonoidShared {
872 order_key,
873 left: DatumVec::new(),
874 right: DatumVec::new(),
875 }));
876 collection
877 .inner
878 .unary_notify(
879 Pipeline,
880 "TopKIntraTimeThinning",
881 [],
882 move |input, output, notificator| {
883 input.for_each_time(|time, data| {
884 let agg_time = aggregates
885 .entry(time.time().clone())
886 .or_insert_with(BTreeMap::new);
887 for ((grp_row, row), record_time, diff) in data.flat_map(|data| data.drain(..))
888 {
889 let monoid = monoids::Top1MonoidLocal {
890 row,
891 shared: Rc::clone(&shared),
892 };
893
894 let limit = if let Some(l) = limit.as_literal_int64() {
896 l
897 } else {
898 let temp_storage = mz_repr::RowArena::new();
899 let key_datums = datum_vec.borrow_with(&grp_row);
900 let datum_limit = limit
903 .eval(&key_datums, &temp_storage)
904 .unwrap_or(mz_repr::Datum::Int64(0));
905 if datum_limit == Datum::Null {
906 i64::MAX
907 } else {
908 datum_limit.unwrap_int64()
909 }
910 };
911
912 let topk = agg_time
913 .entry((grp_row, record_time))
914 .or_insert_with(move || topk_agg::TopKBatch::new(limit));
915 topk.update(monoid, diff.into_inner());
916 }
917 notificator.notify_at(time.retain(0));
918 });
919
920 notificator.for_each(|time, _, _| {
921 if let Some(aggs) = aggregates.remove(time.time()) {
922 let mut session = output.session(&time);
923 for ((grp_row, record_time), topk) in aggs {
924 session.give_iterator(topk.into_iter().map(|(monoid, diff)| {
925 (
926 (grp_row.clone(), monoid.into_row()),
927 record_time.clone(),
928 diff.into(),
929 )
930 }))
931 }
932 }
933 });
934 },
935 )
936 .as_collection()
937}
938
939pub mod topk_agg {
941 use differential_dataflow::consolidation;
942 use smallvec::SmallVec;
943
944 pub struct TopKBatch<T> {
951 updates: SmallVec<[(T, i64); 16]>,
952 clean: usize,
953 limit: i64,
954 }
955
956 impl<T: Ord> TopKBatch<T> {
957 pub fn new(limit: i64) -> Self {
958 Self {
959 updates: SmallVec::new(),
960 clean: 0,
961 limit,
962 }
963 }
964
965 #[inline]
972 pub fn update(&mut self, item: T, value: i64) {
973 self.updates.push((item, value));
974 self.maintain_bounds();
975 }
976
977 #[inline]
983 pub fn compact(&mut self) {
984 if self.clean < self.updates.len() && self.updates.len() > 1 {
985 let len = consolidation::consolidate_slice(&mut self.updates);
986 self.updates.truncate(len);
987
988 let mut limit = self.limit;
990 self.updates.retain(|x| {
991 if limit > 0 {
992 limit -= x.1;
993 true
994 } else {
995 false
996 }
997 });
998 if limit < 0 {
1006 if let Some(item) = self.updates.last_mut() {
1007 item.1 -= -limit;
1010 }
1011 }
1012 }
1013 self.clean = self.updates.len();
1014 }
1015
1016 fn maintain_bounds(&mut self) {
1019 if self.updates.len() > 32 && self.updates.len() >> 1 >= self.clean {
1021 self.compact()
1022 }
1023 }
1024 }
1025
1026 impl<T: Ord> IntoIterator for TopKBatch<T> {
1027 type Item = (T, i64);
1028 type IntoIter = smallvec::IntoIter<[(T, i64); 16]>;
1029
1030 fn into_iter(mut self) -> Self::IntoIter {
1031 self.compact();
1032 self.updates.into_iter()
1033 }
1034 }
1035}
1036
1037pub mod monoids {
1039 use std::cell::RefCell;
1040 use std::cmp::Ordering;
1041 use std::hash::{Hash, Hasher};
1042 use std::rc::Rc;
1043
1044 use columnation::{Columnation, Region};
1045 use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
1046 use mz_expr::ColumnOrder;
1047 use mz_repr::{DatumVec, Diff, Row};
1048 use serde::{Deserialize, Serialize};
1049
1050 #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Hash, Default)]
1052 pub struct Top1Monoid {
1053 pub row: Row,
1054 pub order_key: Vec<ColumnOrder>,
1055 }
1056
1057 impl Clone for Top1Monoid {
1058 #[inline]
1059 fn clone(&self) -> Self {
1060 Self {
1061 row: self.row.clone(),
1062 order_key: self.order_key.clone(),
1063 }
1064 }
1065
1066 #[inline]
1067 fn clone_from(&mut self, source: &Self) {
1068 self.row.clone_from(&source.row);
1069 self.order_key.clone_from(&source.order_key);
1070 }
1071 }
1072
1073 impl Multiply<Diff> for Top1Monoid {
1074 type Output = Self;
1075
1076 fn multiply(self, factor: &Diff) -> Self {
1077 assert!(factor.is_positive());
1082 self
1083 }
1084 }
1085
1086 impl Ord for Top1Monoid {
1087 fn cmp(&self, other: &Self) -> Ordering {
1088 mz_ore::soft_assert_eq_no_log!(self.order_key, other.order_key);
1089
1090 let left: Vec<_> = self.row.unpack();
1093 let right: Vec<_> = other.row.unpack();
1094 mz_expr::compare_columns(&self.order_key, &left, &right, || left.cmp(&right))
1095 }
1096 }
1097 impl PartialOrd for Top1Monoid {
1098 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1099 Some(self.cmp(other))
1100 }
1101 }
1102
1103 impl Semigroup for Top1Monoid {
1104 fn plus_equals(&mut self, rhs: &Self) {
1105 let cmp = (*self).cmp(rhs);
1106 if cmp == Ordering::Greater {
1108 self.clone_from(rhs);
1109 }
1110 }
1111 }
1112
1113 impl IsZero for Top1Monoid {
1114 fn is_zero(&self) -> bool {
1115 false
1116 }
1117 }
1118
1119 impl Columnation for Top1Monoid {
1120 type InnerRegion = Top1MonoidRegion;
1121 }
1122
1123 #[derive(Default)]
1124 pub struct Top1MonoidRegion {
1125 row_region: <Row as Columnation>::InnerRegion,
1126 order_key_region: <Vec<ColumnOrder> as Columnation>::InnerRegion,
1127 }
1128
1129 impl Region for Top1MonoidRegion {
1130 type Item = Top1Monoid;
1131
1132 unsafe fn copy(&mut self, item: &Self::Item) -> Self::Item {
1133 let row = unsafe { self.row_region.copy(&item.row) };
1134 let order_key = unsafe { self.order_key_region.copy(&item.order_key) };
1135 Self::Item { row, order_key }
1136 }
1137
1138 fn clear(&mut self) {
1139 self.row_region.clear();
1140 self.order_key_region.clear();
1141 }
1142
1143 fn reserve_items<'a, I>(&mut self, items1: I)
1144 where
1145 Self: 'a,
1146 I: Iterator<Item = &'a Self::Item> + Clone,
1147 {
1148 let items2 = items1.clone();
1149 self.row_region
1150 .reserve_items(items1.into_iter().map(|s| &s.row));
1151 self.order_key_region
1152 .reserve_items(items2.into_iter().map(|s| &s.order_key));
1153 }
1154
1155 fn reserve_regions<'a, I>(&mut self, regions1: I)
1156 where
1157 Self: 'a,
1158 I: Iterator<Item = &'a Self> + Clone,
1159 {
1160 let regions2 = regions1.clone();
1161 self.row_region
1162 .reserve_regions(regions1.into_iter().map(|s| &s.row_region));
1163 self.order_key_region
1164 .reserve_regions(regions2.into_iter().map(|s| &s.order_key_region));
1165 }
1166
1167 fn heap_size(&self, mut callback: impl FnMut(usize, usize)) {
1168 self.row_region.heap_size(&mut callback);
1169 self.order_key_region.heap_size(callback);
1170 }
1171 }
1172
1173 #[derive(Debug)]
1175 pub struct Top1MonoidShared {
1176 pub order_key: Vec<ColumnOrder>,
1177 pub left: DatumVec,
1178 pub right: DatumVec,
1179 }
1180
1181 #[derive(Debug, Clone)]
1184 pub struct Top1MonoidLocal {
1185 pub row: Row,
1186 pub shared: Rc<RefCell<Top1MonoidShared>>,
1187 }
1188
1189 impl Top1MonoidLocal {
1190 pub fn into_row(self) -> Row {
1191 self.row
1192 }
1193 }
1194
1195 impl PartialEq for Top1MonoidLocal {
1196 fn eq(&self, other: &Self) -> bool {
1197 self.row.eq(&other.row)
1198 }
1199 }
1200
1201 impl Eq for Top1MonoidLocal {}
1202
1203 impl Hash for Top1MonoidLocal {
1204 fn hash<H: Hasher>(&self, state: &mut H) {
1205 self.row.hash(state);
1206 }
1207 }
1208
1209 impl Ord for Top1MonoidLocal {
1210 fn cmp(&self, other: &Self) -> Ordering {
1211 mz_ore::soft_assert_no_log!(Rc::ptr_eq(&self.shared, &other.shared));
1212 let Top1MonoidShared {
1213 left,
1214 right,
1215 order_key,
1216 } = &mut *self.shared.borrow_mut();
1217
1218 let left = left.borrow_with(&self.row);
1219 let right = right.borrow_with(&other.row);
1220 mz_expr::compare_columns(order_key, &left, &right, || left.cmp(&right))
1221 }
1222 }
1223
1224 impl PartialOrd for Top1MonoidLocal {
1225 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1226 Some(self.cmp(other))
1227 }
1228 }
1229
1230 impl Semigroup for Top1MonoidLocal {
1231 fn plus_equals(&mut self, rhs: &Self) {
1232 let cmp = (*self).cmp(rhs);
1233 if cmp == Ordering::Greater {
1235 self.clone_from(rhs);
1236 }
1237 }
1238 }
1239
1240 impl IsZero for Top1MonoidLocal {
1241 fn is_zero(&self) -> bool {
1242 false
1243 }
1244 }
1245}
1246
1247#[cfg(test)]
1248mod tests {
1249 use differential_dataflow::input::Input;
1250 use mz_repr::{Datum, Timestamp};
1251 use timely::dataflow::operators::Capture;
1252 use timely::dataflow::operators::capture::{Event, Extract};
1253
1254 use super::*;
1255 use crate::render::columnar::{columnar_to_vec, vec_to_columnar};
1256
1257 type KeyedUpdate = ((Row, Row), Timestamp, Diff);
1258 type Captured = std::sync::mpsc::Receiver<Event<Timestamp, Vec<KeyedUpdate>>>;
1259
1260 fn extract_sorted(captured: Captured) -> Vec<KeyedUpdate> {
1261 let mut updates: Vec<_> = captured
1262 .extract()
1263 .into_iter()
1264 .flat_map(|(_, data)| data)
1265 .collect();
1266 updates.sort();
1267 updates
1268 }
1269
1270 fn test_input() -> Vec<(Row, u64, Diff)> {
1274 vec![
1275 (
1276 Row::pack_slice(&[Datum::Int32(1), Datum::String("a")]),
1277 0,
1278 Diff::ONE,
1279 ),
1280 (
1281 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
1282 1,
1283 Diff::ONE,
1284 ),
1285 (
1286 Row::pack_slice(&[Datum::Int32(1), Datum::String("a")]),
1287 2,
1288 Diff::ONE,
1289 ),
1290 (
1291 Row::pack_slice(&[Datum::Int32(3), Datum::Null]),
1292 2,
1293 Diff::ONE,
1294 ),
1295 (
1296 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
1297 2,
1298 -Diff::ONE,
1299 ),
1300 (
1301 Row::pack_slice(&[Datum::Int32(4), Datum::String("d")]),
1302 1,
1303 -Diff::ONE,
1304 ),
1305 ]
1306 }
1307
1308 fn run_columnar(input: Vec<(Row, u64, Diff)>) -> Vec<KeyedUpdate> {
1311 let captured = timely::execute_directly(move |worker| {
1312 worker.dataflow::<Timestamp, _, _>(|scope| {
1313 let (mut handle, collection) = scope.new_collection();
1314 let pairer = Pairer::new(1);
1315 let group_key = [0usize];
1316 let keyed =
1317 map_topk_key(vec_to_columnar(collection), "test", move |datums, row| {
1318 let hash = row.hashed();
1319 let iterator = group_key.iter().map(|i| datums[*i]);
1320 pairer.merge(std::iter::once(Datum::from(hash)), iterator)
1321 });
1322 let captured = keyed.inner.capture();
1323 for (row, time, diff) in input {
1324 handle.update_at(row, Timestamp::from(time), diff);
1325 }
1326 handle.advance_to(Timestamp::from(3_u64));
1327 handle.flush();
1328 captured
1329 })
1330 });
1331 extract_sorted(captured)
1332 }
1333
1334 #[mz_ore::test]
1336 fn map_topk_key_forms_key() {
1337 let updates = run_columnar(test_input());
1338 assert!(!updates.is_empty());
1339 assert!(updates.iter().any(|(_, _, d)| *d < Diff::ZERO));
1342 for ((key, value), _t, _d) in &updates {
1345 let key_datums: Vec<_> = key.iter().collect();
1346 let value_datums: Vec<_> = value.iter().collect();
1347 assert_eq!(key_datums.len(), 2);
1348 assert_eq!(key_datums[1], value_datums[0]);
1349 }
1350 }
1351
1352 #[mz_ore::test]
1353 fn topk_result_to_columnar_drops_key() {
1354 let key = Row::pack_slice(&[Datum::Int64(7)]);
1355 let rows = vec![
1356 (
1357 (key.clone(), Row::pack_slice(&[Datum::Int32(1)])),
1358 0u64,
1359 Diff::ONE,
1360 ),
1361 (
1362 (key.clone(), Row::pack_slice(&[Datum::Int32(2)])),
1363 1u64,
1364 Diff::ONE,
1365 ),
1366 (
1369 (key.clone(), Row::pack_slice(&[Datum::Int32(1)])),
1370 2u64,
1371 -Diff::ONE,
1372 ),
1373 ];
1374 let mut expected: Vec<(Row, Timestamp, Diff)> = rows
1375 .iter()
1376 .map(|((_, v), t, d)| (v.clone(), Timestamp::from(*t), *d))
1377 .collect();
1378 expected.sort();
1379
1380 let captured = timely::execute_directly(move |worker| {
1381 worker.dataflow::<Timestamp, _, _>(|scope| {
1382 let (mut handle, collection) = scope.new_collection();
1383 let edge = topk_result_to_columnar(collection);
1384 let captured = columnar_to_vec(edge).inner.capture();
1385 for (kv, time, diff) in rows {
1386 handle.update_at(kv, Timestamp::from(time), diff);
1387 }
1388 handle.advance_to(Timestamp::from(3u64));
1389 handle.flush();
1390 captured
1391 })
1392 });
1393
1394 let mut got: Vec<(Row, Timestamp, Diff)> = captured
1395 .extract()
1396 .into_iter()
1397 .flat_map(|(_, data)| data)
1398 .collect();
1399 got.sort();
1400 assert_eq!(got, expected);
1401 }
1402}