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::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!(
89 temporal_bucketing_strategy,
90 ArrangementStrategy::TemporalBucketing
91 ) {
92 let must_consolidate = match &top_k_plan {
93 TopKPlan::MonotonicTop1(p) => p.must_consolidate,
94 TopKPlan::MonotonicTopK(p) => p.must_consolidate,
95 TopKPlan::Basic(_) => true,
96 };
97 soft_assert_or_log!(
98 must_consolidate,
99 "TopK with `TemporalBucketing` should not have `must_consolidate = false`",
100 );
101 }
102 let ok_input = if matches!(
103 temporal_bucketing_strategy,
104 ArrangementStrategy::TemporalBucketing
105 ) {
106 let summary: mz_repr::Timestamp = TEMPORAL_BUCKETING_SUMMARY
107 .get(&self.config_set)
108 .try_into()
109 .expect("must fit");
110 T::maybe_apply_temporal_bucketing(ok_input.inner, self.as_of_frontier.clone(), summary)
111 } else {
112 ok_input
113 };
114
115 let outer_scope = ok_input.scope();
117 let bundle = outer_scope.clone().region_named("TopK", |inner| {
118 let ok_input = ok_input.enter_region(inner);
119 let mut err_collection = err_input.enter_region(inner);
120
121 match top_k_plan.limit().map(|l| (l.as_literal(), l)) {
129 None => {}
130 Some((Some(Ok(literal)), _))
131 if literal == Datum::Null || literal.unwrap_int64() >= 0 => {}
132 Some((_, expr)) => {
133 let expr = expr.clone();
139 let (_, errors) = flat_map_datums::<
144 _,
145 CapacityContainerBuilder<Vec<(Row, T, Diff)>>,
146 _,
147 >(
148 ok_input.clone(),
149 "TopKLimitCheck",
150 usize::MAX,
151 {
152 let mut datum_vec = mz_repr::DatumVec::new();
153 move |row_datums, time, diff, _ok_session, err_session| {
154 let temp_storage = mz_repr::RowArena::new();
155 let mut datums = datum_vec.borrow();
159 datums.extend(row_datums.iter());
160 match expr.eval(&datums[..], &temp_storage) {
161 Ok(l) if l != Datum::Null && l.unwrap_int64() < 0 => {
162 err_session.give((EvalError::NegLimit.into(), time, diff));
163 1
164 }
165 Ok(_) => 0,
166 Err(e) => {
167 err_session.give((e.into(), time, diff));
168 1
169 }
170 }
171 }
172 },
173 );
174 err_collection = err_collection.concat(errors.as_collection());
175 }
176 }
177
178 let bundle = match top_k_plan {
179 TopKPlan::MonotonicTop1(MonotonicTop1Plan {
180 group_key,
181 order_key,
182 arity,
183 must_consolidate,
184 }) => {
185 let (arrangement, errs) = self.render_top1_monotonic(
186 ok_input,
187 group_key.clone(),
188 order_key,
189 arity,
190 must_consolidate,
191 );
192 err_collection = err_collection.concat(errs);
193
194 let errs: KeyCollection<_, _, _> = err_collection.clone().into();
200 let err_arrangement = errs
201 .mz_arrange::<ColumnationChunker<_>, ErrBatcher<_, _>, ErrBuilder<_, _>, _>(
202 "Arrange bundle err",
203 );
204 CollectionBundle::from_columns(
205 group_key.iter().copied(),
206 ArrangementFlavor::Local(arrangement, err_arrangement),
207 )
208 }
209 TopKPlan::MonotonicTopK(MonotonicTopKPlan {
210 order_key,
211 group_key,
212 arity,
213 mut limit,
214 must_consolidate,
215 }) => {
216 if let Some(expr) = limit.as_mut() {
218 let mut map = BTreeMap::new();
219 for (index, column) in group_key.iter().enumerate() {
220 map.insert(*column, index);
221 }
222 expr.permute_map(&map);
223 }
224
225 let ok_scope = ok_input.scope();
227 let collection =
228 map_topk_key(ok_input, "MonotonicTopK input", move |datums, _row| {
229 SharedRow::pack(group_key.iter().map(|i| datums[*i]))
230 })
231 .consolidate_named_if::<KeyBatcher<_, _, _>>(
232 must_consolidate,
233 "Consolidated MonotonicTopK input",
234 );
235
236 let error_logger = self.error_logger();
238 let (collection, errs) = collection.ensure_monotonic(move |data, diff| {
239 error_logger.log(
240 "Non-monotonic input to MonotonicTopK",
241 &format!("data={data:?}, diff={diff}"),
242 );
243 let m = "tried to build monotonic top-k on non-monotonic input".into();
244 (DataflowErrorSer::from(EvalError::Internal(m)), Diff::ONE)
245 });
246 err_collection = err_collection.concat(errs);
247
248 let collection = if let Some(limit) = limit.clone() {
254 render_intra_ts_thinning(collection, order_key.clone(), limit)
255 } else {
256 collection
257 };
258
259 let pairer = Pairer::new(1);
260 let collection = collection.map(move |(group_row, row)| {
261 let hash = row.hashed();
262 let hash_key = pairer.merge(std::iter::once(Datum::from(hash)), &group_row);
263 (hash_key, row)
264 });
265
266 let delay = std::time::Duration::from_secs(10);
275 let (retractions_var, retractions) = SemigroupVariable::new(
276 ok_scope,
277 <T as crate::render::RenderTimestamp>::system_delay(
278 delay.try_into().expect("must fit"),
279 ),
280 );
281 let thinned = collection.clone().concat(retractions.negate());
282
283 let (result, errs) =
289 self.build_topk_stage(thinned, order_key, 1u64, 0, limit, arity, false);
290 let result = CollectionExt::consolidate_named::<KeyBatcher<_, _, _>>(
292 result,
293 "Monotonic TopK final consolidate",
294 );
295 retractions_var.set(collection.concat(result.clone().negate()));
296 soft_assert_or_log!(
297 errs.is_none(),
298 "requested no validation, but received error collection"
299 );
300
301 CollectionBundle::from_edge(topk_result_to_columnar(result), err_collection)
302 }
303 TopKPlan::Basic(BasicTopKPlan {
304 group_key,
305 order_key,
306 offset,
307 mut limit,
308 arity,
309 buckets,
310 }) => {
311 if let Some(expr) = limit.as_mut() {
313 let mut map = BTreeMap::new();
314 for (index, column) in group_key.iter().enumerate() {
315 map.insert(*column, index);
316 }
317 expr.permute_map(&map);
318 }
319
320 let (oks, errs) = self.build_topk(
321 ok_input, group_key, order_key, offset, limit, arity, buckets,
322 );
323 err_collection = err_collection.concat(errs);
324 CollectionBundle::from_edge(oks, err_collection)
325 }
326 };
327
328 bundle.leave_region(outer_scope)
330 });
331
332 bundle
333 }
334
335 fn build_topk<'s>(
337 &self,
338 collection: ColCollection<'s, T>,
339 group_key: Vec<usize>,
340 order_key: Vec<mz_expr::ColumnOrder>,
341 offset: usize,
342 limit: Option<LirScalarExpr>,
343 arity: usize,
344 buckets: Vec<u64>,
345 ) -> (
346 ColCollection<'s, T>,
347 VecCollection<'s, T, DataflowErrorSer, Diff>,
348 ) {
349 let pairer = Pairer::new(1);
350 let mut collection = map_topk_key(collection, "TopK input", move |datums, row| {
351 let row_hash = row.hashed();
352 let iterator = group_key.iter().map(|i| datums[*i]);
353 pairer.merge(std::iter::once(Datum::from(row_hash)), iterator)
354 });
355
356 let mut validating = true;
357 let mut err_collection: Option<VecCollection<'s, T, _, _>> = None;
358
359 if let Some(mut limit) = limit.clone() {
360 if offset > 0 {
363 let new_limit = (|| {
364 let limit = limit.as_literal_int64()?;
365 let offset = i64::try_from(offset).ok()?;
366 limit.checked_add(offset)
367 })();
368
369 if let Some(new_limit) = new_limit {
370 limit =
371 LirScalarExpr::literal_ok(Datum::Int64(new_limit), ReprScalarType::Int64);
372 } else {
373 limit = limit.call_binary(
374 LirScalarExpr::literal_ok(
375 Datum::UInt64(u64::cast_from(offset)),
376 ReprScalarType::UInt64,
377 )
378 .call_unary(UnaryFunc::CastUint64ToInt64(CastUint64ToInt64)),
379 BinaryFunc::AddInt64(func::AddInt64),
380 );
381 }
382 }
383
384 for bucket in buckets.into_iter() {
388 let (oks, errs) = self.build_topk_stage(
392 collection,
393 order_key.clone(),
394 bucket,
395 0,
396 Some(limit.clone()),
397 arity,
398 validating,
399 );
400 collection = oks;
401 if validating {
402 err_collection = errs;
403 validating = false;
404 }
405 }
406 }
407
408 let (oks, errs) = self.build_topk_stage(
412 collection, order_key, 1u64, offset, limit, arity, validating,
413 );
414 let oks =
416 CollectionExt::consolidate_named::<KeyBatcher<_, _, _>>(oks, "TopK final consolidate");
417 collection = oks;
418 if validating {
419 err_collection = errs;
420 }
421 (
422 topk_result_to_columnar(collection),
423 err_collection.expect("at least one stage validated its inputs"),
424 )
425 }
426
427 fn build_topk_stage<'s>(
460 &self,
461 collection: VecCollection<'s, T, (Row, Row), Diff>,
462 order_key: Vec<mz_expr::ColumnOrder>,
463 modulus: u64,
464 offset: usize,
465 limit: Option<LirScalarExpr>,
466 arity: usize,
467 validating: bool,
468 ) -> (
469 VecCollection<'s, T, (Row, Row), Diff>,
470 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
471 ) {
472 let input = collection.map(move |(hash_key, row)| {
475 let mut hash_key_iter = hash_key.iter();
476 let hash = hash_key_iter.next().unwrap().unwrap_uint64() % modulus;
477 let hash_key = SharedRow::pack(std::iter::once(hash.into()).chain(hash_key_iter));
478 (hash_key, row)
479 });
480
481 let (input, oks, errs) = if validating {
483 let (input, stage) = build_topk_negated_stage::<
485 T,
486 RowValBuilder<_, _, _>,
487 RowValSpine<Result<Row, Row>, _, _>,
488 >(&input, order_key, offset, limit, arity);
489 let stage = stage.as_collection(|k, v| (k.to_row(), v.clone()));
490
491 let error_logger = self.error_logger();
493 type CB<C> = CapacityContainerBuilder<C>;
494 let (oks, errs) = stage.map_fallible::<CB<_>, CB<_>, _, _, _>(
495 "Demuxing Errors",
496 move |(hk, result)| match result {
497 Err(v) => {
498 let mut hk_iter = hk.iter();
499 let h = hk_iter.next().unwrap().unwrap_uint64();
500 let k = SharedRow::pack(hk_iter);
501 let message = "Negative multiplicities in TopK";
502 error_logger.log(message, &format!("k={k:?}, h={h}, v={v:?}"));
503 Err(EvalError::Internal(message.into()).into())
504 }
505 Ok(t) => Ok((hk, t)),
506 },
507 );
508 (input, oks, Some(errs))
509 } else {
510 let (input, stage) =
512 build_topk_negated_stage::<T, RowRowBuilder<_, _>, RowRowSpine<_, _>>(
513 &input, order_key, offset, limit, arity,
514 );
515 let stage = stage.as_collection(|k, v| (k.to_row(), v.to_row()));
517
518 (input, stage, None)
519 };
520 let input = input.as_collection(|k, v| (k.to_row(), v.to_row()));
521 (oks.concat(input), errs)
522 }
523
524 fn render_top1_monotonic<'s>(
525 &self,
526 collection: ColCollection<'s, T>,
527 group_key: Vec<usize>,
528 order_key: Vec<mz_expr::ColumnOrder>,
529 arity: usize,
530 must_consolidate: bool,
531 ) -> (
532 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
533 VecCollection<'s, T, DataflowErrorSer, Diff>,
534 ) {
535 let key: Vec<LirScalarExpr> = group_key
542 .iter()
543 .map(|c| LirScalarExpr::column(*c))
544 .collect();
545 let (_permutation, thinning) = permutation_for_arrangement(&key, arity);
546
547 let collection = map_topk_key(collection, "MonotonicTop1 input", move |datums, _row| {
552 SharedRow::pack(group_key.iter().map(|i| datums[*i]))
553 })
554 .consolidate_named_if::<KeyBatcher<_, _, _>>(
555 must_consolidate,
556 "Consolidated MonotonicTop1 input",
557 );
558
559 let error_logger = self.error_logger();
561 let (partial, errs) = collection.ensure_monotonic(move |data, diff| {
562 error_logger.log(
563 "Non-monotonic input to MonotonicTop1",
564 &format!("data={data:?}, diff={diff}"),
565 );
566 let m = "tried to build monotonic top-1 on non-monotonic input".into();
567 (EvalError::Internal(m).into(), Diff::ONE)
568 });
569 let partial: KeyCollection<_, _, _> = partial
570 .explode_one(move |(group_key, row)| {
571 (
572 group_key,
573 monoids::Top1Monoid {
574 row,
575 order_key: order_key.clone(),
576 },
577 )
578 })
579 .into();
580 let result = partial
581 .mz_arrange::<
582 ColumnationChunker<_>,
583 RowBatcher<_, _>,
584 RowBuilder<_, _>,
585 RowSpine<_, _>,
586 >(
587 "Arranged MonotonicTop1 partial [val: empty]",
588 )
589 .mz_reduce_abelian::<_, RowRowBuilder<_, _>, RowRowSpine<_, _>, _>(
590 "MonotonicTop1",
591 {
592 let mut datum_vec = mz_repr::DatumVec::new();
593 move |_key, input, output| {
594 let accum: &monoids::Top1Monoid = &input[0].1;
595 let datums = datum_vec.borrow_with(&accum.row);
596 let value = SharedRow::pack(thinning.iter().map(|i| datums[*i]));
597 output.push((value, Diff::ONE));
598 }
599 },
600 );
601 (result, errs)
602 }
603}
604
605fn map_topk_key<'s, T, L>(
615 edge: ColCollection<'s, T>,
616 name: &str,
617 mut key: L,
618) -> VecCollection<'s, T, (Row, Row), Diff>
619where
620 T: crate::render::RenderTimestamp,
621 L: FnMut(&[Datum], &Row) -> Row + 'static,
622{
623 let mut builder = OperatorBuilder::new(name.to_string(), edge.inner.scope());
624 let (output, stream) = builder.new_output();
625 let mut output =
626 OutputBuilder::<_, CapacityContainerBuilder<Vec<((Row, Row), T, Diff)>>>::from(output);
627 let mut input = builder.new_input(edge.inner, Pipeline);
628 builder.build(move |_capabilities| {
629 let mut datum_vec = mz_repr::DatumVec::new();
630 move |_frontiers| {
631 let mut output = output.activate();
632 input.for_each(|time, data| {
633 let mut session = output.session_with_builder(&time);
634 for (row, t, d) in data.borrow().into_index_iter() {
635 let value_row: Row = Columnar::into_owned(row);
636 let key_row = {
637 let datums = datum_vec.borrow_with(&value_row);
638 key(&datums, &value_row)
639 };
640 session.give((
641 (key_row, value_row),
642 Columnar::into_owned(t),
643 Columnar::into_owned(d),
644 ));
645 }
646 });
647 }
648 });
649 stream.as_collection()
650}
651
652fn topk_result_to_columnar<'s, T>(
662 collection: VecCollection<'s, T, (Row, Row), Diff>,
663) -> ColCollection<'s, T>
664where
665 T: crate::render::RenderTimestamp,
666{
667 let stream = collection
668 .inner
669 .unary::<ColumnBuilder<(Row, T, Diff)>, _, _, _>(Pipeline, "TopKUnkey", |_cap, _info| {
670 move |input, output| {
671 input.for_each(|time, data| {
672 let mut session = output.session_with_builder(&time);
673 for ((_key_hash, row), t, d) in data.drain(..) {
674 session.give((&row, &t, &d));
675 }
676 });
677 }
678 });
679 stream.as_collection()
680}
681
682fn build_topk_negated_stage<'s, T, Bu, Tr>(
690 input: &VecCollection<'s, T, (Row, Row), Diff>,
691 order_key: Vec<mz_expr::ColumnOrder>,
692 offset: usize,
693 limit: Option<LirScalarExpr>,
694 arity: usize,
695) -> (
696 Arranged<'s, TraceAgent<RowRowSpine<T, Diff>>>,
697 Arranged<'s, TraceAgent<Tr>>,
698)
699where
700 T: MzTimestamp,
701 Bu: Builder<
702 Time = T,
703 Input: Container + ClearContainer + PushInto<((Row, BatchValOwn<Tr>), T, Diff)>,
704 Output = Tr::Batch,
705 > + 'static,
706 Tr: Trace<Batch: Navigable, Time = T> + 'static,
707 for<'a> BatchCursor<Tr>: Cursor<
708 Key<'a> = DatumSeq<'a>,
709 KeyContainer = DatumContainer,
710 ValOwn: Data + MaybeValidatingRow<Row, Row>,
711 Time = T,
712 Diff = Diff,
713 >,
714 Arranged<'s, TraceAgent<Tr>>: ArrangementSize,
715{
716 let mut datum_vec = mz_repr::DatumVec::new();
717
718 let arranged = input
723 .clone()
724 .mz_arrange::<
725 ColumnationChunker<_>,
726 RowRowBatcher<_, _>,
727 RowRowBuilder<_, _>,
728 RowRowSpine<_, _>,
729 >(
730 "Arranged TopK input",
731 );
732
733 let limit = limit.map(|l| match l.as_literal() {
735 Some(Ok(Datum::Null)) => Ok(Diff::MAX),
736 Some(Ok(d)) => Ok(Diff::from(d.unwrap_int64())),
737 _ => Err(l),
738 });
739
740 let reduced = arranged
741 .clone()
742 .mz_reduce_abelian::<_, Bu, Tr, _>("Reduced TopK input", {
743 move |hash_key, source, target: &mut Vec<(BatchValOwn<Tr>, Diff)>| {
744 let limit = match &limit {
746 Some(Ok(lit)) => Some(*lit),
747 Some(Err(expr)) => {
748 let temp_storage = mz_repr::RowArena::new();
751 let mut key_datums = datum_vec.borrow();
752 hash_key.extend_datums(&temp_storage, &mut key_datums, None);
753 let datum_limit = expr
755 .eval(&key_datums[1..], &temp_storage)
756 .unwrap_or(Datum::Int64(0));
757 Some(match datum_limit {
758 Datum::Null => Diff::MAX,
759 d => Diff::from(d.unwrap_int64()),
760 })
761 }
762 None => None,
763 };
764
765 if let Some(err) = BatchValOwn::<Tr>::into_error() {
766 for (datums, diff) in source.iter() {
767 if diff.is_positive() {
768 continue;
769 }
770 target.push((err((*datums).to_row()), Diff::ONE));
771 return;
772 }
773 }
774
775 let must_shrink = offset > 0
777 || limit
778 .map(|l| source.iter().map(|(_, d)| *d).sum::<Diff>() > l)
779 .unwrap_or(false);
780 if !must_shrink {
781 return;
782 }
783
784 target.reserve(source.len());
788 for (datums, diff) in source.iter() {
789 target.push((BatchValOwn::<Tr>::ok((*datums).to_row()), -diff));
790 }
791 let mut offset = offset;
793 let mut limit = limit;
794
795 let mut indexes = (0..source.len()).collect::<Vec<_>>();
797 let temp_storage = mz_repr::RowArena::new();
800 let mut buffer = Vec::with_capacity(arity * source.len());
801 for (index, (datums, _)) in source.iter().enumerate() {
802 datums.extend_datums(&temp_storage, &mut buffer, None);
803 assert_eq!(buffer.len(), arity * (index + 1));
804 }
805 let width = buffer.len() / source.len();
806
807 indexes.sort_by(|left, right| {
809 let left = &buffer[left * width..][..width];
810 let right = &buffer[right * width..][..width];
811 mz_expr::compare_columns(&order_key, left, right, || left.cmp(right))
814 });
815
816 for index in indexes.into_iter() {
819 let (datums, mut diff) = source[index];
820 if !diff.is_positive() {
821 continue;
822 }
823 if offset > 0 {
825 let to_skip =
826 std::cmp::min(offset, usize::try_from(diff.into_inner()).unwrap());
827 offset -= to_skip;
828 diff -= Diff::try_from(to_skip).unwrap();
829 }
830 if let Some(limit) = &mut limit {
832 diff = std::cmp::min(diff, Diff::from(*limit));
833 *limit -= diff;
834 }
835 if diff.is_positive() {
837 target.push((BatchValOwn::<Tr>::ok(datums.to_row()), diff));
840 }
841 }
842 }
843 });
844 (arranged, reduced)
845}
846
847fn render_intra_ts_thinning<'s, T>(
848 collection: VecCollection<'s, T, (Row, Row), Diff>,
849 order_key: Vec<mz_expr::ColumnOrder>,
850 limit: LirScalarExpr,
851) -> VecCollection<'s, T, (Row, Row), Diff>
852where
853 T: timely::progress::Timestamp + Lattice,
854{
855 let mut datum_vec = mz_repr::DatumVec::new();
856
857 let mut aggregates = BTreeMap::new();
858 let shared = Rc::new(RefCell::new(monoids::Top1MonoidShared {
859 order_key,
860 left: DatumVec::new(),
861 right: DatumVec::new(),
862 }));
863 collection
864 .inner
865 .unary_notify(
866 Pipeline,
867 "TopKIntraTimeThinning",
868 [],
869 move |input, output, notificator| {
870 input.for_each_time(|time, data| {
871 let agg_time = aggregates
872 .entry(time.time().clone())
873 .or_insert_with(BTreeMap::new);
874 for ((grp_row, row), record_time, diff) in data.flat_map(|data| data.drain(..))
875 {
876 let monoid = monoids::Top1MonoidLocal {
877 row,
878 shared: Rc::clone(&shared),
879 };
880
881 let limit = if let Some(l) = limit.as_literal_int64() {
883 l
884 } else {
885 let temp_storage = mz_repr::RowArena::new();
886 let key_datums = datum_vec.borrow_with(&grp_row);
887 let datum_limit = limit
890 .eval(&key_datums, &temp_storage)
891 .unwrap_or(mz_repr::Datum::Int64(0));
892 if datum_limit == Datum::Null {
893 i64::MAX
894 } else {
895 datum_limit.unwrap_int64()
896 }
897 };
898
899 let topk = agg_time
900 .entry((grp_row, record_time))
901 .or_insert_with(move || topk_agg::TopKBatch::new(limit));
902 topk.update(monoid, diff.into_inner());
903 }
904 notificator.notify_at(time.retain(0));
905 });
906
907 notificator.for_each(|time, _, _| {
908 if let Some(aggs) = aggregates.remove(time.time()) {
909 let mut session = output.session(&time);
910 for ((grp_row, record_time), topk) in aggs {
911 session.give_iterator(topk.into_iter().map(|(monoid, diff)| {
912 (
913 (grp_row.clone(), monoid.into_row()),
914 record_time.clone(),
915 diff.into(),
916 )
917 }))
918 }
919 }
920 });
921 },
922 )
923 .as_collection()
924}
925
926pub mod topk_agg {
928 use differential_dataflow::consolidation;
929 use smallvec::SmallVec;
930
931 pub struct TopKBatch<T> {
938 updates: SmallVec<[(T, i64); 16]>,
939 clean: usize,
940 limit: i64,
941 }
942
943 impl<T: Ord> TopKBatch<T> {
944 pub fn new(limit: i64) -> Self {
945 Self {
946 updates: SmallVec::new(),
947 clean: 0,
948 limit,
949 }
950 }
951
952 #[inline]
959 pub fn update(&mut self, item: T, value: i64) {
960 self.updates.push((item, value));
961 self.maintain_bounds();
962 }
963
964 #[inline]
970 pub fn compact(&mut self) {
971 if self.clean < self.updates.len() && self.updates.len() > 1 {
972 let len = consolidation::consolidate_slice(&mut self.updates);
973 self.updates.truncate(len);
974
975 let mut limit = self.limit;
977 self.updates.retain(|x| {
978 if limit > 0 {
979 limit -= x.1;
980 true
981 } else {
982 false
983 }
984 });
985 if limit < 0 {
993 if let Some(item) = self.updates.last_mut() {
994 item.1 -= -limit;
997 }
998 }
999 }
1000 self.clean = self.updates.len();
1001 }
1002
1003 fn maintain_bounds(&mut self) {
1006 if self.updates.len() > 32 && self.updates.len() >> 1 >= self.clean {
1008 self.compact()
1009 }
1010 }
1011 }
1012
1013 impl<T: Ord> IntoIterator for TopKBatch<T> {
1014 type Item = (T, i64);
1015 type IntoIter = smallvec::IntoIter<[(T, i64); 16]>;
1016
1017 fn into_iter(mut self) -> Self::IntoIter {
1018 self.compact();
1019 self.updates.into_iter()
1020 }
1021 }
1022}
1023
1024pub mod monoids {
1026 use std::cell::RefCell;
1027 use std::cmp::Ordering;
1028 use std::hash::{Hash, Hasher};
1029 use std::rc::Rc;
1030
1031 use columnation::{Columnation, Region};
1032 use differential_dataflow::difference::{IsZero, Multiply, Semigroup};
1033 use mz_expr::ColumnOrder;
1034 use mz_repr::{DatumVec, Diff, Row};
1035 use serde::{Deserialize, Serialize};
1036
1037 #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Hash, Default)]
1039 pub struct Top1Monoid {
1040 pub row: Row,
1041 pub order_key: Vec<ColumnOrder>,
1042 }
1043
1044 impl Clone for Top1Monoid {
1045 #[inline]
1046 fn clone(&self) -> Self {
1047 Self {
1048 row: self.row.clone(),
1049 order_key: self.order_key.clone(),
1050 }
1051 }
1052
1053 #[inline]
1054 fn clone_from(&mut self, source: &Self) {
1055 self.row.clone_from(&source.row);
1056 self.order_key.clone_from(&source.order_key);
1057 }
1058 }
1059
1060 impl Multiply<Diff> for Top1Monoid {
1061 type Output = Self;
1062
1063 fn multiply(self, factor: &Diff) -> Self {
1064 assert!(factor.is_positive());
1069 self
1070 }
1071 }
1072
1073 impl Ord for Top1Monoid {
1074 fn cmp(&self, other: &Self) -> Ordering {
1075 mz_ore::soft_assert_eq_no_log!(self.order_key, other.order_key);
1076
1077 let left: Vec<_> = self.row.unpack();
1080 let right: Vec<_> = other.row.unpack();
1081 mz_expr::compare_columns(&self.order_key, &left, &right, || left.cmp(&right))
1082 }
1083 }
1084 impl PartialOrd for Top1Monoid {
1085 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1086 Some(self.cmp(other))
1087 }
1088 }
1089
1090 impl Semigroup for Top1Monoid {
1091 fn plus_equals(&mut self, rhs: &Self) {
1092 let cmp = (*self).cmp(rhs);
1093 if cmp == Ordering::Greater {
1095 self.clone_from(rhs);
1096 }
1097 }
1098 }
1099
1100 impl IsZero for Top1Monoid {
1101 fn is_zero(&self) -> bool {
1102 false
1103 }
1104 }
1105
1106 impl Columnation for Top1Monoid {
1107 type InnerRegion = Top1MonoidRegion;
1108 }
1109
1110 #[derive(Default)]
1111 pub struct Top1MonoidRegion {
1112 row_region: <Row as Columnation>::InnerRegion,
1113 order_key_region: <Vec<ColumnOrder> as Columnation>::InnerRegion,
1114 }
1115
1116 impl Region for Top1MonoidRegion {
1117 type Item = Top1Monoid;
1118
1119 unsafe fn copy(&mut self, item: &Self::Item) -> Self::Item {
1120 let row = unsafe { self.row_region.copy(&item.row) };
1121 let order_key = unsafe { self.order_key_region.copy(&item.order_key) };
1122 Self::Item { row, order_key }
1123 }
1124
1125 fn clear(&mut self) {
1126 self.row_region.clear();
1127 self.order_key_region.clear();
1128 }
1129
1130 fn reserve_items<'a, I>(&mut self, items1: I)
1131 where
1132 Self: 'a,
1133 I: Iterator<Item = &'a Self::Item> + Clone,
1134 {
1135 let items2 = items1.clone();
1136 self.row_region
1137 .reserve_items(items1.into_iter().map(|s| &s.row));
1138 self.order_key_region
1139 .reserve_items(items2.into_iter().map(|s| &s.order_key));
1140 }
1141
1142 fn reserve_regions<'a, I>(&mut self, regions1: I)
1143 where
1144 Self: 'a,
1145 I: Iterator<Item = &'a Self> + Clone,
1146 {
1147 let regions2 = regions1.clone();
1148 self.row_region
1149 .reserve_regions(regions1.into_iter().map(|s| &s.row_region));
1150 self.order_key_region
1151 .reserve_regions(regions2.into_iter().map(|s| &s.order_key_region));
1152 }
1153
1154 fn heap_size(&self, mut callback: impl FnMut(usize, usize)) {
1155 self.row_region.heap_size(&mut callback);
1156 self.order_key_region.heap_size(callback);
1157 }
1158 }
1159
1160 #[derive(Debug)]
1162 pub struct Top1MonoidShared {
1163 pub order_key: Vec<ColumnOrder>,
1164 pub left: DatumVec,
1165 pub right: DatumVec,
1166 }
1167
1168 #[derive(Debug, Clone)]
1171 pub struct Top1MonoidLocal {
1172 pub row: Row,
1173 pub shared: Rc<RefCell<Top1MonoidShared>>,
1174 }
1175
1176 impl Top1MonoidLocal {
1177 pub fn into_row(self) -> Row {
1178 self.row
1179 }
1180 }
1181
1182 impl PartialEq for Top1MonoidLocal {
1183 fn eq(&self, other: &Self) -> bool {
1184 self.row.eq(&other.row)
1185 }
1186 }
1187
1188 impl Eq for Top1MonoidLocal {}
1189
1190 impl Hash for Top1MonoidLocal {
1191 fn hash<H: Hasher>(&self, state: &mut H) {
1192 self.row.hash(state);
1193 }
1194 }
1195
1196 impl Ord for Top1MonoidLocal {
1197 fn cmp(&self, other: &Self) -> Ordering {
1198 mz_ore::soft_assert_no_log!(Rc::ptr_eq(&self.shared, &other.shared));
1199 let Top1MonoidShared {
1200 left,
1201 right,
1202 order_key,
1203 } = &mut *self.shared.borrow_mut();
1204
1205 let left = left.borrow_with(&self.row);
1206 let right = right.borrow_with(&other.row);
1207 mz_expr::compare_columns(order_key, &left, &right, || left.cmp(&right))
1208 }
1209 }
1210
1211 impl PartialOrd for Top1MonoidLocal {
1212 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1213 Some(self.cmp(other))
1214 }
1215 }
1216
1217 impl Semigroup for Top1MonoidLocal {
1218 fn plus_equals(&mut self, rhs: &Self) {
1219 let cmp = (*self).cmp(rhs);
1220 if cmp == Ordering::Greater {
1222 self.clone_from(rhs);
1223 }
1224 }
1225 }
1226
1227 impl IsZero for Top1MonoidLocal {
1228 fn is_zero(&self) -> bool {
1229 false
1230 }
1231 }
1232}
1233
1234#[cfg(test)]
1235mod tests {
1236 use differential_dataflow::input::Input;
1237 use mz_repr::{Datum, Timestamp};
1238 use timely::dataflow::operators::Capture;
1239 use timely::dataflow::operators::capture::{Event, Extract};
1240
1241 use super::*;
1242 use crate::render::columnar::{columnar_to_vec, vec_to_columnar};
1243
1244 type KeyedUpdate = ((Row, Row), Timestamp, Diff);
1245 type Captured = std::sync::mpsc::Receiver<Event<Timestamp, Vec<KeyedUpdate>>>;
1246
1247 fn extract_sorted(captured: Captured) -> Vec<KeyedUpdate> {
1248 let mut updates: Vec<_> = captured
1249 .extract()
1250 .into_iter()
1251 .flat_map(|(_, data)| data)
1252 .collect();
1253 updates.sort();
1254 updates
1255 }
1256
1257 fn test_input() -> Vec<(Row, u64, Diff)> {
1261 vec![
1262 (
1263 Row::pack_slice(&[Datum::Int32(1), Datum::String("a")]),
1264 0,
1265 Diff::ONE,
1266 ),
1267 (
1268 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
1269 1,
1270 Diff::ONE,
1271 ),
1272 (
1273 Row::pack_slice(&[Datum::Int32(1), Datum::String("a")]),
1274 2,
1275 Diff::ONE,
1276 ),
1277 (
1278 Row::pack_slice(&[Datum::Int32(3), Datum::Null]),
1279 2,
1280 Diff::ONE,
1281 ),
1282 (
1283 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
1284 2,
1285 -Diff::ONE,
1286 ),
1287 (
1288 Row::pack_slice(&[Datum::Int32(4), Datum::String("d")]),
1289 1,
1290 -Diff::ONE,
1291 ),
1292 ]
1293 }
1294
1295 fn run_columnar(input: Vec<(Row, u64, Diff)>) -> Vec<KeyedUpdate> {
1298 let captured = timely::execute_directly(move |worker| {
1299 worker.dataflow::<Timestamp, _, _>(|scope| {
1300 let (mut handle, collection) = scope.new_collection();
1301 let pairer = Pairer::new(1);
1302 let group_key = [0usize];
1303 let keyed =
1304 map_topk_key(vec_to_columnar(collection), "test", move |datums, row| {
1305 let hash = row.hashed();
1306 let iterator = group_key.iter().map(|i| datums[*i]);
1307 pairer.merge(std::iter::once(Datum::from(hash)), iterator)
1308 });
1309 let captured = keyed.inner.capture();
1310 for (row, time, diff) in input {
1311 handle.update_at(row, Timestamp::from(time), diff);
1312 }
1313 handle.advance_to(Timestamp::from(3_u64));
1314 handle.flush();
1315 captured
1316 })
1317 });
1318 extract_sorted(captured)
1319 }
1320
1321 #[mz_ore::test]
1323 fn map_topk_key_forms_key() {
1324 let updates = run_columnar(test_input());
1325 assert!(!updates.is_empty());
1326 assert!(updates.iter().any(|(_, _, d)| *d < Diff::ZERO));
1329 for ((key, value), _t, _d) in &updates {
1332 let key_datums: Vec<_> = key.iter().collect();
1333 let value_datums: Vec<_> = value.iter().collect();
1334 assert_eq!(key_datums.len(), 2);
1335 assert_eq!(key_datums[1], value_datums[0]);
1336 }
1337 }
1338
1339 #[mz_ore::test]
1340 fn topk_result_to_columnar_drops_key() {
1341 let key = Row::pack_slice(&[Datum::Int64(7)]);
1342 let rows = vec![
1343 (
1344 (key.clone(), Row::pack_slice(&[Datum::Int32(1)])),
1345 0u64,
1346 Diff::ONE,
1347 ),
1348 (
1349 (key.clone(), Row::pack_slice(&[Datum::Int32(2)])),
1350 1u64,
1351 Diff::ONE,
1352 ),
1353 (
1356 (key.clone(), Row::pack_slice(&[Datum::Int32(1)])),
1357 2u64,
1358 -Diff::ONE,
1359 ),
1360 ];
1361 let mut expected: Vec<(Row, Timestamp, Diff)> = rows
1362 .iter()
1363 .map(|((_, v), t, d)| (v.clone(), Timestamp::from(*t), *d))
1364 .collect();
1365 expected.sort();
1366
1367 let captured = timely::execute_directly(move |worker| {
1368 worker.dataflow::<Timestamp, _, _>(|scope| {
1369 let (mut handle, collection) = scope.new_collection();
1370 let edge = topk_result_to_columnar(collection);
1371 let captured = columnar_to_vec(edge).inner.capture();
1372 for (kv, time, diff) in rows {
1373 handle.update_at(kv, Timestamp::from(time), diff);
1374 }
1375 handle.advance_to(Timestamp::from(3u64));
1376 handle.flush();
1377 captured
1378 })
1379 });
1380
1381 let mut got: Vec<(Row, Timestamp, Diff)> = captured
1382 .extract()
1383 .into_iter()
1384 .flat_map(|(_, data)| data)
1385 .collect();
1386 got.sort();
1387 assert_eq!(got, expected);
1388 }
1389}