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::{CollectionEdge, 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 >(ok_input.clone(), usize::MAX, {
161 let mut datum_vec = mz_repr::DatumVec::new();
162 move |row_datums, time, diff, _ok_session, err_session| {
163 let temp_storage = mz_repr::RowArena::new();
164 let mut datums = datum_vec.borrow();
168 datums.extend(row_datums.iter());
169 match expr.eval(&datums[..], &temp_storage) {
170 Ok(l) if l != Datum::Null && l.unwrap_int64() < 0 => {
171 err_session.give((EvalError::NegLimit.into(), time, diff));
172 1
173 }
174 Ok(_) => 0,
175 Err(e) => {
176 err_session.give((e.into(), time, diff));
177 1
178 }
179 }
180 }
181 });
182 err_collection = err_collection.concat(errors.as_collection());
183 }
184 }
185
186 let bundle = match top_k_plan {
187 TopKPlan::MonotonicTop1(MonotonicTop1Plan {
188 group_key,
189 order_key,
190 arity,
191 must_consolidate,
192 }) => {
193 let (arrangement, errs) = self.render_top1_monotonic(
194 ok_input,
195 group_key.clone(),
196 order_key,
197 arity,
198 must_consolidate,
199 );
200 err_collection = err_collection.concat(errs);
201
202 let errs: KeyCollection<_, _, _> = err_collection.clone().into();
208 let err_arrangement = errs
209 .mz_arrange::<ColumnationChunker<_>, ErrBatcher<_, _>, ErrBuilder<_, _>, _>(
210 "Arrange bundle err",
211 );
212 CollectionBundle::from_columns(
213 group_key.iter().copied(),
214 ArrangementFlavor::Local(arrangement, err_arrangement),
215 )
216 }
217 TopKPlan::MonotonicTopK(MonotonicTopKPlan {
218 order_key,
219 group_key,
220 arity,
221 mut limit,
222 must_consolidate,
223 }) => {
224 if let Some(expr) = limit.as_mut() {
226 let mut map = BTreeMap::new();
227 for (index, column) in group_key.iter().enumerate() {
228 map.insert(*column, index);
229 }
230 expr.permute_map(&map);
231 }
232
233 let ok_scope = ok_input.scope();
235 let collection =
236 map_topk_key(ok_input, "MonotonicTopK input", move |datums, _row| {
237 SharedRow::pack(group_key.iter().map(|i| datums[*i]))
238 })
239 .consolidate_named_if::<KeyBatcher<_, _, _>>(
240 must_consolidate,
241 "Consolidated MonotonicTopK input",
242 );
243
244 let error_logger = self.error_logger();
246 let (collection, errs) = collection.ensure_monotonic(move |data, diff| {
247 error_logger.log(
248 "Non-monotonic input to MonotonicTopK",
249 &format!("data={data:?}, diff={diff}"),
250 );
251 let m = "tried to build monotonic top-k on non-monotonic input".into();
252 (DataflowErrorSer::from(EvalError::Internal(m)), Diff::ONE)
253 });
254 err_collection = err_collection.concat(errs);
255
256 let collection = if let Some(limit) = limit.clone() {
262 render_intra_ts_thinning(collection, order_key.clone(), limit)
263 } else {
264 collection
265 };
266
267 let pairer = Pairer::new(1);
268 let collection = collection.map(move |(group_row, row)| {
269 let hash = row.hashed();
270 let hash_key = pairer.merge(std::iter::once(Datum::from(hash)), &group_row);
271 (hash_key, row)
272 });
273
274 let delay = std::time::Duration::from_secs(10);
283 let (retractions_var, retractions) = SemigroupVariable::new(
284 ok_scope,
285 <T as crate::render::RenderTimestamp>::system_delay(
286 delay.try_into().expect("must fit"),
287 ),
288 );
289 let thinned = collection.clone().concat(retractions.negate());
290
291 let (result, errs) =
297 self.build_topk_stage(thinned, order_key, 1u64, 0, limit, arity, false);
298 let result = CollectionExt::consolidate_named::<KeyBatcher<_, _, _>>(
300 result,
301 "Monotonic TopK final consolidate",
302 );
303 retractions_var.set(collection.concat(result.clone().negate()));
304 soft_assert_or_log!(
305 errs.is_none(),
306 "requested no validation, but received error collection"
307 );
308
309 CollectionBundle::from_edge(topk_result_to_columnar(result), err_collection)
310 }
311 TopKPlan::Basic(BasicTopKPlan {
312 group_key,
313 order_key,
314 offset,
315 mut limit,
316 arity,
317 buckets,
318 }) => {
319 if let Some(expr) = limit.as_mut() {
321 let mut map = BTreeMap::new();
322 for (index, column) in group_key.iter().enumerate() {
323 map.insert(*column, index);
324 }
325 expr.permute_map(&map);
326 }
327
328 let (oks, errs) = self.build_topk(
329 ok_input, group_key, order_key, offset, limit, arity, buckets,
330 );
331 err_collection = err_collection.concat(errs);
332 CollectionBundle::from_edge(oks, err_collection)
333 }
334 };
335
336 bundle.leave_region(outer_scope)
338 });
339
340 bundle
341 }
342
343 fn build_topk<'s>(
345 &self,
346 collection: CollectionEdge<'s, T>,
347 group_key: Vec<usize>,
348 order_key: Vec<mz_expr::ColumnOrder>,
349 offset: usize,
350 limit: Option<LirScalarExpr>,
351 arity: usize,
352 buckets: Vec<u64>,
353 ) -> (
354 CollectionEdge<'s, T>,
355 VecCollection<'s, T, DataflowErrorSer, Diff>,
356 ) {
357 let pairer = Pairer::new(1);
358 let mut collection = map_topk_key(collection, "TopK input", move |datums, row| {
359 let row_hash = row.hashed();
360 let iterator = group_key.iter().map(|i| datums[*i]);
361 pairer.merge(std::iter::once(Datum::from(row_hash)), iterator)
362 });
363
364 let mut validating = true;
365 let mut err_collection: Option<VecCollection<'s, T, _, _>> = None;
366
367 if let Some(mut limit) = limit.clone() {
368 if offset > 0 {
371 let new_limit = (|| {
372 let limit = limit.as_literal_int64()?;
373 let offset = i64::try_from(offset).ok()?;
374 limit.checked_add(offset)
375 })();
376
377 if let Some(new_limit) = new_limit {
378 limit =
379 LirScalarExpr::literal_ok(Datum::Int64(new_limit), ReprScalarType::Int64);
380 } else {
381 limit = limit.call_binary(
382 LirScalarExpr::literal_ok(
383 Datum::UInt64(u64::cast_from(offset)),
384 ReprScalarType::UInt64,
385 )
386 .call_unary(UnaryFunc::CastUint64ToInt64(CastUint64ToInt64)),
387 BinaryFunc::AddInt64(func::AddInt64),
388 );
389 }
390 }
391
392 for bucket in buckets.into_iter() {
396 let (oks, errs) = self.build_topk_stage(
400 collection,
401 order_key.clone(),
402 bucket,
403 0,
404 Some(limit.clone()),
405 arity,
406 validating,
407 );
408 collection = oks;
409 if validating {
410 err_collection = errs;
411 validating = false;
412 }
413 }
414 }
415
416 let (oks, errs) = self.build_topk_stage(
420 collection, order_key, 1u64, offset, limit, arity, validating,
421 );
422 let oks =
424 CollectionExt::consolidate_named::<KeyBatcher<_, _, _>>(oks, "TopK final consolidate");
425 collection = oks;
426 if validating {
427 err_collection = errs;
428 }
429 (
430 topk_result_to_columnar(collection),
431 err_collection.expect("at least one stage validated its inputs"),
432 )
433 }
434
435 fn build_topk_stage<'s>(
468 &self,
469 collection: VecCollection<'s, T, (Row, Row), Diff>,
470 order_key: Vec<mz_expr::ColumnOrder>,
471 modulus: u64,
472 offset: usize,
473 limit: Option<LirScalarExpr>,
474 arity: usize,
475 validating: bool,
476 ) -> (
477 VecCollection<'s, T, (Row, Row), Diff>,
478 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
479 ) {
480 let input = collection.map(move |(hash_key, row)| {
483 let mut hash_key_iter = hash_key.iter();
484 let hash = hash_key_iter.next().unwrap().unwrap_uint64() % modulus;
485 let hash_key = SharedRow::pack(std::iter::once(hash.into()).chain(hash_key_iter));
486 (hash_key, row)
487 });
488
489 let (input, oks, errs) = if validating {
491 let (input, stage) = build_topk_negated_stage::<
493 T,
494 RowValBuilder<_, _, _>,
495 RowValSpine<Result<Row, Row>, _, _>,
496 >(&input, order_key, offset, limit, arity);
497 let stage = stage.as_collection(|k, v| (k.to_row(), v.clone()));
498
499 let error_logger = self.error_logger();
501 type CB<C> = CapacityContainerBuilder<C>;
502 let (oks, errs) = stage.map_fallible::<CB<_>, CB<_>, _, _, _>(
503 "Demuxing Errors",
504 move |(hk, result)| match result {
505 Err(v) => {
506 let mut hk_iter = hk.iter();
507 let h = hk_iter.next().unwrap().unwrap_uint64();
508 let k = SharedRow::pack(hk_iter);
509 let message = "Negative multiplicities in TopK";
510 error_logger.log(message, &format!("k={k:?}, h={h}, v={v:?}"));
511 Err(EvalError::Internal(message.into()).into())
512 }
513 Ok(t) => Ok((hk, t)),
514 },
515 );
516 (input, oks, Some(errs))
517 } else {
518 let (input, stage) =
520 build_topk_negated_stage::<T, RowRowBuilder<_, _>, RowRowSpine<_, _>>(
521 &input, order_key, offset, limit, arity,
522 );
523 let stage = stage.as_collection(|k, v| (k.to_row(), v.to_row()));
525
526 (input, stage, None)
527 };
528 let input = input.as_collection(|k, v| (k.to_row(), v.to_row()));
529 (oks.concat(input), errs)
530 }
531
532 fn render_top1_monotonic<'s>(
533 &self,
534 collection: CollectionEdge<'s, T>,
535 group_key: Vec<usize>,
536 order_key: Vec<mz_expr::ColumnOrder>,
537 arity: usize,
538 must_consolidate: bool,
539 ) -> (
540 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
541 VecCollection<'s, T, DataflowErrorSer, Diff>,
542 ) {
543 let key: Vec<LirScalarExpr> = group_key
550 .iter()
551 .map(|c| LirScalarExpr::column(*c))
552 .collect();
553 let (_permutation, thinning) = permutation_for_arrangement(&key, arity);
554
555 let collection = map_topk_key(collection, "MonotonicTop1 input", move |datums, _row| {
560 SharedRow::pack(group_key.iter().map(|i| datums[*i]))
561 })
562 .consolidate_named_if::<KeyBatcher<_, _, _>>(
563 must_consolidate,
564 "Consolidated MonotonicTop1 input",
565 );
566
567 let error_logger = self.error_logger();
569 let (partial, errs) = collection.ensure_monotonic(move |data, diff| {
570 error_logger.log(
571 "Non-monotonic input to MonotonicTop1",
572 &format!("data={data:?}, diff={diff}"),
573 );
574 let m = "tried to build monotonic top-1 on non-monotonic input".into();
575 (EvalError::Internal(m).into(), Diff::ONE)
576 });
577 let partial: KeyCollection<_, _, _> = partial
578 .explode_one(move |(group_key, row)| {
579 (
580 group_key,
581 monoids::Top1Monoid {
582 row,
583 order_key: order_key.clone(),
584 },
585 )
586 })
587 .into();
588 let result = partial
589 .mz_arrange::<
590 ColumnationChunker<_>,
591 RowBatcher<_, _>,
592 RowBuilder<_, _>,
593 RowSpine<_, _>,
594 >(
595 "Arranged MonotonicTop1 partial [val: empty]",
596 )
597 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
598 "MonotonicTop1",
599 {
600 let mut datum_vec = mz_repr::DatumVec::new();
601 move |_key, input, output| {
602 let accum: &monoids::Top1Monoid = &input[0].1;
603 let datums = datum_vec.borrow_with(&accum.row);
604 let value = SharedRow::pack(thinning.iter().map(|i| datums[*i]));
605 output.push((value, Diff::ONE));
606 }
607 },
608 );
609 (result, errs)
610 }
611}
612
613fn map_topk_key<'s, T, L>(
623 edge: CollectionEdge<'s, T>,
624 name: &str,
625 mut key: L,
626) -> VecCollection<'s, T, (Row, Row), Diff>
627where
628 T: crate::render::RenderTimestamp,
629 L: FnMut(&[Datum], &Row) -> Row + 'static,
630{
631 let mut builder = OperatorBuilder::new(name.to_string(), edge.inner.scope());
632 let (output, stream) = builder.new_output();
633 let mut output =
634 OutputBuilder::<_, CapacityContainerBuilder<Vec<((Row, Row), T, Diff)>>>::from(output);
635 let mut input = builder.new_input(edge.inner, Pipeline);
636 builder.build(move |_capabilities| {
637 let mut datum_vec = mz_repr::DatumVec::new();
638 move |_frontiers| {
639 let mut output = output.activate();
640 input.for_each(|time, data| {
641 let mut session = output.session_with_builder(&time);
642 for (row, t, d) in data.borrow().into_index_iter() {
643 let value_row: Row = Columnar::into_owned(row);
644 let key_row = {
645 let datums = datum_vec.borrow_with(&value_row);
646 key(&datums, &value_row)
647 };
648 session.give((
649 (key_row, value_row),
650 Columnar::into_owned(t),
651 Columnar::into_owned(d),
652 ));
653 }
654 });
655 }
656 });
657 stream.as_collection()
658}
659
660fn topk_result_to_columnar<'s, T>(
670 collection: VecCollection<'s, T, (Row, Row), Diff>,
671) -> CollectionEdge<'s, T>
672where
673 T: crate::render::RenderTimestamp,
674{
675 let stream = collection
676 .inner
677 .unary::<ColumnBuilder<(Row, T, Diff)>, _, _, _>(Pipeline, "TopKUnkey", |_cap, _info| {
678 move |input, output| {
679 input.for_each(|time, data| {
680 let mut session = output.session_with_builder(&time);
681 for ((_key_hash, row), t, d) in data.drain(..) {
682 session.give((&row, &t, &d));
683 }
684 });
685 }
686 });
687 stream.as_collection()
688}
689
690fn build_topk_negated_stage<'s, T, Bu, Tr>(
698 input: &VecCollection<'s, T, (Row, Row), Diff>,
699 order_key: Vec<mz_expr::ColumnOrder>,
700 offset: usize,
701 limit: Option<LirScalarExpr>,
702 arity: usize,
703) -> (
704 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
705 Arranged<'s, TraceAgent<Tr>>,
706)
707where
708 T: MzTimestamp,
709 Bu: Builder<
710 Time = T,
711 Input: Container + ClearContainer + PushInto<((Row, BatchValOwn<Tr>), T, Diff)>,
712 Output = Tr::Batch,
713 > + 'static,
714 Tr: Trace<Batch: Navigable, Time = T> + 'static,
715 for<'a> BatchCursor<Tr>: Cursor<
716 Key<'a> = DatumSeq<'a>,
717 KeyContainer = DatumContainer,
718 ValOwn: Data + MaybeValidatingRow<Row, Row>,
719 Time = T,
720 Diff = Diff,
721 >,
722 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
723{
724 let mut datum_vec = mz_repr::DatumVec::new();
725
726 let arranged = input
731 .clone()
732 .mz_arrange::<
733 ColumnationChunker<_>,
734 RowRowBatcher<_, _>,
735 RowRowBuilder<_, _>,
736 RowRowSpine<_, _>,
737 >(
738 "Arranged TopK input",
739 );
740
741 let limit = limit.map(|l| match l.as_literal() {
743 Some(Ok(Datum::Null)) => Ok(Diff::MAX),
744 Some(Ok(d)) => Ok(Diff::from(d.unwrap_int64())),
745 _ => Err(l),
746 });
747
748 let reduced = arranged
749 .clone()
750 .mz_reduce_abelian::<_, Bu, Tr, _>("Reduced TopK input", {
751 move |hash_key, source, target: &mut Vec<(BatchValOwn<Tr>, Diff)>| {
752 let limit = match &limit {
754 Some(Ok(lit)) => Some(*lit),
755 Some(Err(expr)) => {
756 let temp_storage = mz_repr::RowArena::new();
759 let mut key_datums = datum_vec.borrow();
760 hash_key.extend_datums(&temp_storage, &mut key_datums, None);
761 let datum_limit = expr
763 .eval(&key_datums[1..], &temp_storage)
764 .unwrap_or(Datum::Int64(0));
765 Some(match datum_limit {
766 Datum::Null => Diff::MAX,
767 d => Diff::from(d.unwrap_int64()),
768 })
769 }
770 None => None,
771 };
772
773 if let Some(err) = BatchValOwn::<Tr>::into_error() {
774 for (datums, diff) in source.iter() {
775 if diff.is_positive() {
776 continue;
777 }
778 target.push((err((*datums).to_row()), Diff::ONE));
779 return;
780 }
781 }
782
783 let must_shrink = offset > 0
785 || limit
786 .map(|l| source.iter().map(|(_, d)| *d).sum::<Diff>() > l)
787 .unwrap_or(false);
788 if !must_shrink {
789 return;
790 }
791
792 target.reserve(source.len());
796 for (datums, diff) in source.iter() {
797 target.push((BatchValOwn::<Tr>::ok((*datums).to_row()), -diff));
798 }
799 let mut offset = offset;
801 let mut limit = limit;
802
803 let mut indexes = (0..source.len()).collect::<Vec<_>>();
805 let temp_storage = mz_repr::RowArena::new();
808 let mut buffer = Vec::with_capacity(arity * source.len());
809 for (index, (datums, _)) in source.iter().enumerate() {
810 datums.extend_datums(&temp_storage, &mut buffer, None);
811 assert_eq!(buffer.len(), arity * (index + 1));
812 }
813 let width = buffer.len() / source.len();
814
815 indexes.sort_by(|left, right| {
817 let left = &buffer[left * width..][..width];
818 let right = &buffer[right * width..][..width];
819 mz_expr::compare_columns(&order_key, left, right, || left.cmp(right))
822 });
823
824 for index in indexes.into_iter() {
827 let (datums, mut diff) = source[index];
828 if !diff.is_positive() {
829 continue;
830 }
831 if offset > 0 {
833 let to_skip =
834 std::cmp::min(offset, usize::try_from(diff.into_inner()).unwrap());
835 offset -= to_skip;
836 diff -= Diff::try_from(to_skip).unwrap();
837 }
838 if let Some(limit) = &mut limit {
840 diff = std::cmp::min(diff, Diff::from(*limit));
841 *limit -= diff;
842 }
843 if diff.is_positive() {
845 target.push((BatchValOwn::<Tr>::ok(datums.to_row()), diff));
848 }
849 }
850 }
851 });
852 (arranged, reduced)
853}
854
855fn render_intra_ts_thinning<'s, T>(
856 collection: VecCollection<'s, T, (Row, Row), Diff>,
857 order_key: Vec<mz_expr::ColumnOrder>,
858 limit: LirScalarExpr,
859) -> VecCollection<'s, T, (Row, Row), Diff>
860where
861 T: timely::progress::Timestamp + Lattice,
862{
863 let mut datum_vec = mz_repr::DatumVec::new();
864
865 let mut aggregates = BTreeMap::new();
866 let shared = Rc::new(RefCell::new(monoids::Top1MonoidShared {
867 order_key,
868 left: DatumVec::new(),
869 right: DatumVec::new(),
870 }));
871 collection
872 .inner
873 .unary_notify(
874 Pipeline,
875 "TopKIntraTimeThinning",
876 [],
877 move |input, output, notificator| {
878 input.for_each_time(|time, data| {
879 let agg_time = aggregates
880 .entry(time.time().clone())
881 .or_insert_with(BTreeMap::new);
882 for ((grp_row, row), record_time, diff) in data.flat_map(|data| data.drain(..))
883 {
884 let monoid = monoids::Top1MonoidLocal {
885 row,
886 shared: Rc::clone(&shared),
887 };
888
889 let limit = if let Some(l) = limit.as_literal_int64() {
891 l
892 } else {
893 let temp_storage = mz_repr::RowArena::new();
894 let key_datums = datum_vec.borrow_with(&grp_row);
895 let datum_limit = limit
898 .eval(&key_datums, &temp_storage)
899 .unwrap_or(mz_repr::Datum::Int64(0));
900 if datum_limit == Datum::Null {
901 i64::MAX
902 } else {
903 datum_limit.unwrap_int64()
904 }
905 };
906
907 let topk = agg_time
908 .entry((grp_row, record_time))
909 .or_insert_with(move || topk_agg::TopKBatch::new(limit));
910 topk.update(monoid, diff.into_inner());
911 }
912 notificator.notify_at(time.retain(0));
913 });
914
915 notificator.for_each(|time, _, _| {
916 if let Some(aggs) = aggregates.remove(time.time()) {
917 let mut session = output.session(&time);
918 for ((grp_row, record_time), topk) in aggs {
919 session.give_iterator(topk.into_iter().map(|(monoid, diff)| {
920 (
921 (grp_row.clone(), monoid.into_row()),
922 record_time.clone(),
923 diff.into(),
924 )
925 }))
926 }
927 }
928 });
929 },
930 )
931 .as_collection()
932}
933
934pub mod topk_agg {
936 use differential_dataflow::consolidation;
937 use smallvec::SmallVec;
938
939 pub struct TopKBatch<T> {
946 updates: SmallVec<[(T, i64); 16]>,
947 clean: usize,
948 limit: i64,
949 }
950
951 impl<T: Ord> TopKBatch<T> {
952 pub fn new(limit: i64) -> Self {
953 Self {
954 updates: SmallVec::new(),
955 clean: 0,
956 limit,
957 }
958 }
959
960 #[inline]
967 pub fn update(&mut self, item: T, value: i64) {
968 self.updates.push((item, value));
969 self.maintain_bounds();
970 }
971
972 #[inline]
978 pub fn compact(&mut self) {
979 if self.clean < self.updates.len() && self.updates.len() > 1 {
980 let len = consolidation::consolidate_slice(&mut self.updates);
981 self.updates.truncate(len);
982
983 let mut limit = self.limit;
985 self.updates.retain(|x| {
986 if limit > 0 {
987 limit -= x.1;
988 true
989 } else {
990 false
991 }
992 });
993 if limit < 0 {
1001 if let Some(item) = self.updates.last_mut() {
1002 item.1 -= -limit;
1005 }
1006 }
1007 }
1008 self.clean = self.updates.len();
1009 }
1010
1011 fn maintain_bounds(&mut self) {
1014 if self.updates.len() > 32 && self.updates.len() >> 1 >= self.clean {
1016 self.compact()
1017 }
1018 }
1019 }
1020
1021 impl<T: Ord> IntoIterator for TopKBatch<T> {
1022 type Item = (T, i64);
1023 type IntoIter = smallvec::IntoIter<[(T, i64); 16]>;
1024
1025 fn into_iter(mut self) -> Self::IntoIter {
1026 self.compact();
1027 self.updates.into_iter()
1028 }
1029 }
1030}
1031
1032pub mod monoids {
1034 use std::cell::RefCell;
1035 use std::cmp::Ordering;
1036 use std::hash::{Hash, Hasher};
1037 use std::rc::Rc;
1038
1039 use columnation::{Columnation, Region};
1040 use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
1041 use mz_expr::ColumnOrder;
1042 use mz_repr::{DatumVec, Diff, Row};
1043 use serde::{Deserialize, Serialize};
1044
1045 #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Hash, Default)]
1047 pub struct Top1Monoid {
1048 pub row: Row,
1049 pub order_key: Vec<ColumnOrder>,
1050 }
1051
1052 impl Clone for Top1Monoid {
1053 #[inline]
1054 fn clone(&self) -> Self {
1055 Self {
1056 row: self.row.clone(),
1057 order_key: self.order_key.clone(),
1058 }
1059 }
1060
1061 #[inline]
1062 fn clone_from(&mut self, source: &Self) {
1063 self.row.clone_from(&source.row);
1064 self.order_key.clone_from(&source.order_key);
1065 }
1066 }
1067
1068 impl Multiply<Diff> for Top1Monoid {
1069 type Output = Self;
1070
1071 fn multiply(self, factor: &Diff) -> Self {
1072 assert!(factor.is_positive());
1077 self
1078 }
1079 }
1080
1081 impl Ord for Top1Monoid {
1082 fn cmp(&self, other: &Self) -> Ordering {
1083 mz_ore::soft_assert_eq_no_log!(self.order_key, other.order_key);
1084
1085 let left: Vec<_> = self.row.unpack();
1088 let right: Vec<_> = other.row.unpack();
1089 mz_expr::compare_columns(&self.order_key, &left, &right, || left.cmp(&right))
1090 }
1091 }
1092 impl PartialOrd for Top1Monoid {
1093 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1094 Some(self.cmp(other))
1095 }
1096 }
1097
1098 impl Semigroup for Top1Monoid {
1099 fn plus_equals(&mut self, rhs: &Self) {
1100 let cmp = (*self).cmp(rhs);
1101 if cmp == Ordering::Greater {
1103 self.clone_from(rhs);
1104 }
1105 }
1106 }
1107
1108 impl IsZero for Top1Monoid {
1109 fn is_zero(&self) -> bool {
1110 false
1111 }
1112 }
1113
1114 impl Columnation for Top1Monoid {
1115 type InnerRegion = Top1MonoidRegion;
1116 }
1117
1118 #[derive(Default)]
1119 pub struct Top1MonoidRegion {
1120 row_region: <Row as Columnation>::InnerRegion,
1121 order_key_region: <Vec<ColumnOrder> as Columnation>::InnerRegion,
1122 }
1123
1124 impl Region for Top1MonoidRegion {
1125 type Item = Top1Monoid;
1126
1127 unsafe fn copy(&mut self, item: &Self::Item) -> Self::Item {
1128 let row = unsafe { self.row_region.copy(&item.row) };
1129 let order_key = unsafe { self.order_key_region.copy(&item.order_key) };
1130 Self::Item { row, order_key }
1131 }
1132
1133 fn clear(&mut self) {
1134 self.row_region.clear();
1135 self.order_key_region.clear();
1136 }
1137
1138 fn reserve_items<'a, I>(&mut self, items1: I)
1139 where
1140 Self: 'a,
1141 I: Iterator<Item = &'a Self::Item> + Clone,
1142 {
1143 let items2 = items1.clone();
1144 self.row_region
1145 .reserve_items(items1.into_iter().map(|s| &s.row));
1146 self.order_key_region
1147 .reserve_items(items2.into_iter().map(|s| &s.order_key));
1148 }
1149
1150 fn reserve_regions<'a, I>(&mut self, regions1: I)
1151 where
1152 Self: 'a,
1153 I: Iterator<Item = &'a Self> + Clone,
1154 {
1155 let regions2 = regions1.clone();
1156 self.row_region
1157 .reserve_regions(regions1.into_iter().map(|s| &s.row_region));
1158 self.order_key_region
1159 .reserve_regions(regions2.into_iter().map(|s| &s.order_key_region));
1160 }
1161
1162 fn heap_size(&self, mut callback: impl FnMut(usize, usize)) {
1163 self.row_region.heap_size(&mut callback);
1164 self.order_key_region.heap_size(callback);
1165 }
1166 }
1167
1168 #[derive(Debug)]
1170 pub struct Top1MonoidShared {
1171 pub order_key: Vec<ColumnOrder>,
1172 pub left: DatumVec,
1173 pub right: DatumVec,
1174 }
1175
1176 #[derive(Debug, Clone)]
1179 pub struct Top1MonoidLocal {
1180 pub row: Row,
1181 pub shared: Rc<RefCell<Top1MonoidShared>>,
1182 }
1183
1184 impl Top1MonoidLocal {
1185 pub fn into_row(self) -> Row {
1186 self.row
1187 }
1188 }
1189
1190 impl PartialEq for Top1MonoidLocal {
1191 fn eq(&self, other: &Self) -> bool {
1192 self.row.eq(&other.row)
1193 }
1194 }
1195
1196 impl Eq for Top1MonoidLocal {}
1197
1198 impl Hash for Top1MonoidLocal {
1199 fn hash<H: Hasher>(&self, state: &mut H) {
1200 self.row.hash(state);
1201 }
1202 }
1203
1204 impl Ord for Top1MonoidLocal {
1205 fn cmp(&self, other: &Self) -> Ordering {
1206 mz_ore::soft_assert_no_log!(Rc::ptr_eq(&self.shared, &other.shared));
1207 let Top1MonoidShared {
1208 left,
1209 right,
1210 order_key,
1211 } = &mut *self.shared.borrow_mut();
1212
1213 let left = left.borrow_with(&self.row);
1214 let right = right.borrow_with(&other.row);
1215 mz_expr::compare_columns(order_key, &left, &right, || left.cmp(&right))
1216 }
1217 }
1218
1219 impl PartialOrd for Top1MonoidLocal {
1220 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1221 Some(self.cmp(other))
1222 }
1223 }
1224
1225 impl Semigroup for Top1MonoidLocal {
1226 fn plus_equals(&mut self, rhs: &Self) {
1227 let cmp = (*self).cmp(rhs);
1228 if cmp == Ordering::Greater {
1230 self.clone_from(rhs);
1231 }
1232 }
1233 }
1234
1235 impl IsZero for Top1MonoidLocal {
1236 fn is_zero(&self) -> bool {
1237 false
1238 }
1239 }
1240}
1241
1242#[cfg(test)]
1243mod tests {
1244 use differential_dataflow::input::Input;
1245 use mz_repr::{Datum, Timestamp};
1246 use timely::dataflow::operators::Capture;
1247 use timely::dataflow::operators::capture::{Event, Extract};
1248
1249 use super::*;
1250 use crate::render::columnar::{columnar_to_vec, vec_to_columnar};
1251
1252 type KeyedUpdate = ((Row, Row), Timestamp, Diff);
1253 type Captured = std::sync::mpsc::Receiver<Event<Timestamp, Vec<KeyedUpdate>>>;
1254
1255 fn extract_sorted(captured: Captured) -> Vec<KeyedUpdate> {
1256 let mut updates: Vec<_> = captured
1257 .extract()
1258 .into_iter()
1259 .flat_map(|(_, data)| data)
1260 .collect();
1261 updates.sort();
1262 updates
1263 }
1264
1265 fn test_input() -> Vec<(Row, u64, Diff)> {
1269 vec![
1270 (
1271 Row::pack_slice(&[Datum::Int32(1), Datum::String("a")]),
1272 0,
1273 Diff::ONE,
1274 ),
1275 (
1276 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
1277 1,
1278 Diff::ONE,
1279 ),
1280 (
1281 Row::pack_slice(&[Datum::Int32(1), Datum::String("a")]),
1282 2,
1283 Diff::ONE,
1284 ),
1285 (
1286 Row::pack_slice(&[Datum::Int32(3), Datum::Null]),
1287 2,
1288 Diff::ONE,
1289 ),
1290 (
1291 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
1292 2,
1293 -Diff::ONE,
1294 ),
1295 (
1296 Row::pack_slice(&[Datum::Int32(4), Datum::String("d")]),
1297 1,
1298 -Diff::ONE,
1299 ),
1300 ]
1301 }
1302
1303 fn run_columnar(input: Vec<(Row, u64, Diff)>) -> Vec<KeyedUpdate> {
1306 let captured = timely::execute_directly(move |worker| {
1307 worker.dataflow::<Timestamp, _, _>(|scope| {
1308 let (mut handle, collection) = scope.new_collection();
1309 let pairer = Pairer::new(1);
1310 let group_key = [0usize];
1311 let keyed =
1312 map_topk_key(vec_to_columnar(collection), "test", move |datums, row| {
1313 let hash = row.hashed();
1314 let iterator = group_key.iter().map(|i| datums[*i]);
1315 pairer.merge(std::iter::once(Datum::from(hash)), iterator)
1316 });
1317 let captured = keyed.inner.capture();
1318 for (row, time, diff) in input {
1319 handle.update_at(row, Timestamp::from(time), diff);
1320 }
1321 handle.advance_to(Timestamp::from(3_u64));
1322 handle.flush();
1323 captured
1324 })
1325 });
1326 extract_sorted(captured)
1327 }
1328
1329 #[mz_ore::test]
1331 fn map_topk_key_forms_key() {
1332 let updates = run_columnar(test_input());
1333 assert!(!updates.is_empty());
1334 assert!(updates.iter().any(|(_, _, d)| *d < Diff::ZERO));
1337 for ((key, value), _t, _d) in &updates {
1340 let key_datums: Vec<_> = key.iter().collect();
1341 let value_datums: Vec<_> = value.iter().collect();
1342 assert_eq!(key_datums.len(), 2);
1343 assert_eq!(key_datums[1], value_datums[0]);
1344 }
1345 }
1346
1347 #[mz_ore::test]
1348 fn topk_result_to_columnar_drops_key() {
1349 let key = Row::pack_slice(&[Datum::Int64(7)]);
1350 let rows = vec![
1351 (
1352 (key.clone(), Row::pack_slice(&[Datum::Int32(1)])),
1353 0u64,
1354 Diff::ONE,
1355 ),
1356 (
1357 (key.clone(), Row::pack_slice(&[Datum::Int32(2)])),
1358 1u64,
1359 Diff::ONE,
1360 ),
1361 (
1364 (key.clone(), Row::pack_slice(&[Datum::Int32(1)])),
1365 2u64,
1366 -Diff::ONE,
1367 ),
1368 ];
1369 let mut expected: Vec<(Row, Timestamp, Diff)> = rows
1370 .iter()
1371 .map(|((_, v), t, d)| (v.clone(), Timestamp::from(*t), *d))
1372 .collect();
1373 expected.sort();
1374
1375 let captured = timely::execute_directly(move |worker| {
1376 worker.dataflow::<Timestamp, _, _>(|scope| {
1377 let (mut handle, collection) = scope.new_collection();
1378 let edge = topk_result_to_columnar(collection);
1379 let captured = columnar_to_vec(edge).inner.capture();
1380 for (kv, time, diff) in rows {
1381 handle.update_at(kv, Timestamp::from(time), diff);
1382 }
1383 handle.advance_to(Timestamp::from(3u64));
1384 handle.flush();
1385 captured
1386 })
1387 });
1388
1389 let mut got: Vec<(Row, Timestamp, Diff)> = captured
1390 .extract()
1391 .into_iter()
1392 .flat_map(|(_, data)| data)
1393 .collect();
1394 got.sort();
1395 assert_eq!(got, expected);
1396 }
1397}