1use columnar::{Borrow, Columnar, Container, Index, Len, Push};
24use differential_dataflow::dynamic::pointstamp::{PointStamp, PointStampSummary};
25use differential_dataflow::{AsCollection, Collection, VecCollection};
26use mz_repr::{DatumVec, DatumVecBorrow, Diff, Row};
27use mz_timely_util::columnar::Column;
28use mz_timely_util::columnar::builder::ColumnBuilder;
29use mz_timely_util::columnar::chunk::{AccountedChunkChainBatcher, ComputeSpill};
30use mz_timely_util::columnar::columnar_consolidate_exchange;
31use mz_timely_util::operator::consolidate_pact;
32use timely::ContainerBuilder;
33use timely::container::{CapacityContainerBuilder, NoopBuilder};
34use timely::dataflow::channels::pact::{ExchangeCore, Pipeline};
35use timely::dataflow::operators::CapabilitySet;
36use timely::dataflow::operators::generic::builder_rc::OperatorBuilder;
37use timely::dataflow::operators::generic::{Operator, OutputBuilder};
38use timely::dataflow::{Scope, Stream, StreamVec};
39use timely::order::Product;
40use timely::progress::Antichain;
41
42use crate::render::RenderTimestamp;
43use crate::render::context::{ECB, Session};
44use crate::render::errors::DataflowErrorSer;
45
46pub type ColumnarCollection<'scope, T, D, R> = Collection<'scope, T, Column<(D, T, R)>>;
52
53pub type ColCollection<'scope, T> = ColumnarCollection<'scope, T, Row, Diff>;
56
57pub fn concat_many<'scope, T, I>(scope: Scope<'scope, T>, edges: I) -> ColCollection<'scope, T>
59where
60 T: RenderTimestamp,
61 I: IntoIterator<Item = ColCollection<'scope, T>>,
62{
63 let cols: Vec<_> = edges.into_iter().collect();
64 differential_dataflow::collection::concatenate(scope, cols)
65}
66
67pub fn flat_map_datums<'scope, T, DCB, L>(
77 edge: ColCollection<'scope, T>,
78 name: &str,
79 max_demand: usize,
80 mut logic: L,
81) -> (
82 Stream<'scope, T, DCB::Container>,
83 StreamVec<'scope, T, (DataflowErrorSer, T, Diff)>,
84)
85where
86 T: RenderTimestamp,
87 DCB: ContainerBuilder,
88 L: for<'a> FnMut(
89 &'a mut DatumVecBorrow<'_>,
90 T,
91 Diff,
92 &mut Session<T, DCB>,
93 &mut Session<T, ECB<T>>,
94 ) -> usize
95 + 'static,
96{
97 let scope = edge.inner.scope();
98 let mut builder = OperatorBuilder::new(name.to_string(), scope);
99 let (ok_output, ok_stream) = builder.new_output();
100 let mut ok_output = OutputBuilder::<_, DCB>::from(ok_output);
101 let (err_output, err_stream) = builder.new_output();
102 let mut err_output = OutputBuilder::<_, ECB<T>>::from(err_output);
103 let mut input = builder.new_input(edge.inner, Pipeline);
104 builder.build(move |_capabilities| {
105 let mut datums = DatumVec::new();
106 move |_frontiers| {
107 let mut ok_output = ok_output.activate();
108 let mut err_output = err_output.activate();
109 input.for_each(|time, data| {
110 let ok_cap = time.retain_stamp(0);
113 let err_cap = time.retain_stamp(1);
114 let mut ok_session = ok_output.session_with_builder(&ok_cap);
115 let mut err_session = err_output.session_with_builder(&err_cap);
116 for (v, t, d) in data.borrow().into_index_iter() {
119 logic(
120 &mut datums.borrow_with_limit(v, max_demand),
121 Columnar::into_owned(t),
122 Columnar::into_owned(d),
123 &mut ok_session,
124 &mut err_session,
125 );
126 }
127 });
128 }
129 });
130 (ok_stream, err_stream)
131}
132
133fn negate_column<T>(column: Column<(Row, T, Diff)>) -> Column<(Row, T, Diff)>
149where
150 T: RenderTimestamp,
151{
152 fn negated_diffs<'a, D>(diffs: &'a D) -> <Diff as Columnar>::Container
154 where
155 D: Len + Index<Ref = Diff> + 'a,
156 {
157 let mut negated = <Diff as Columnar>::Container::default();
158 for index in 0..diffs.len() {
159 negated.push(-diffs.get(index));
160 }
161 negated
162 }
163
164 match column {
165 Column::Typed((rows, times, diffs)) => {
166 let negated = negated_diffs(&diffs.borrow());
167 Column::Typed((rows, times, negated))
168 }
169 column => {
170 let view = column.borrow();
171 let len = view.len();
172 let mut negated = <(Row, T, Diff) as Columnar>::Container::default();
173 let (rows, times, diffs) = &mut negated;
174 rows.extend_from_self(view.0, 0..len);
175 times.extend_from_self(view.1, 0..len);
176 *diffs = negated_diffs(&view.2);
177 Column::Typed(negated)
178 }
179 }
180}
181
182pub type RecTimestamp = Product<mz_repr::Timestamp, PointStamp<u64>>;
184
185fn truncate_times(
191 column: Column<(Row, RecTimestamp, Diff)>,
192 level: usize,
193) -> Column<(Row, RecTimestamp, Diff)> {
194 fn truncated<'a, C>(times: C, level: usize) -> <RecTimestamp as Columnar>::Container
196 where
197 C: Len + Index<Ref = columnar::Ref<'a, RecTimestamp>> + 'a,
198 {
199 let mut truncated = <RecTimestamp as Columnar>::Container::default();
200 let mut time = RecTimestamp::default();
201 for reference in times.into_index_iter() {
202 time.copy_from(reference);
203 let mut coordinates = std::mem::take(&mut time.inner).into_inner();
204 coordinates.truncate(level - 1);
205 time.inner = PointStamp::new(coordinates);
206 truncated.push(&time);
207 }
208 truncated
209 }
210
211 match column {
212 Column::Typed((rows, times, diffs)) => {
213 let times = truncated(times.borrow(), level);
214 Column::Typed((rows, times, diffs))
215 }
216 column => {
217 let view = column.borrow();
218 let len = view.len();
219 let mut out = <(Row, RecTimestamp, Diff) as Columnar>::Container::default();
220 let (rows, times, diffs) = &mut out;
221 rows.extend_from_self(view.0, 0..len);
222 *times = truncated(view.1, level);
223 diffs.extend_from_self(view.2, 0..len);
224 Column::Typed(out)
225 }
226 }
227}
228
229pub fn columnar_leave_dynamic<'scope>(
235 collection: ColumnarCollection<'scope, RecTimestamp, Row, Diff>,
236 level: usize,
237) -> ColumnarCollection<'scope, RecTimestamp, Row, Diff> {
238 let scope = collection.inner.scope();
239 let mut builder = OperatorBuilder::new("ColumnarLeaveDynamic".to_string(), scope);
240 let (output, stream) = builder.new_output();
241 let mut output =
242 OutputBuilder::<_, NoopBuilder<Column<(Row, RecTimestamp, Diff)>>>::from(output);
243 let summary = Product {
246 outer: Default::default(),
247 inner: PointStampSummary {
248 retain: Some(level - 1),
249 actions: Vec::new(),
250 },
251 };
252 let mut input = builder.new_input_connection(
253 collection.inner,
254 Pipeline,
255 [(0, Antichain::from_elem(summary))],
256 );
257
258 builder.build(move |_capability| {
259 move |_frontier| {
260 let mut output = output.activate();
261 input.for_each(|cap, data| {
262 let caps: CapabilitySet<_> = cap
266 .stamp()
267 .iter()
268 .map(|time| {
269 let mut time = time.clone();
270 let mut coordinates = std::mem::take(&mut time.inner).into_inner();
271 coordinates.truncate(level - 1);
272 time.inner = PointStamp::new(coordinates);
273 cap.delayed(&time, 0)
274 })
275 .collect();
276 let mut truncated = truncate_times(std::mem::take(data), level);
277 output
278 .session_with_builder(&caps)
279 .give_container(&mut truncated);
280 });
281 }
282 });
283
284 stream.as_collection()
285}
286
287pub fn columnar_negate<'scope, T>(
289 collection: ColumnarCollection<'scope, T, Row, Diff>,
290) -> ColumnarCollection<'scope, T, Row, Diff>
291where
292 T: RenderTimestamp,
293{
294 collection
295 .inner
296 .unary::<NoopBuilder<Column<(Row, T, Diff)>>, _, _, _>(
297 Pipeline,
298 "ColumnarNegate",
299 |_cap, _info| {
300 move |input, output| {
301 input.for_each(|time, data| {
302 let mut negated = negate_column(std::mem::take(data));
303 output
304 .session_with_builder(&time)
305 .give_container(&mut negated);
306 });
307 }
308 },
309 )
310 .as_collection()
311}
312
313pub fn columnar_consolidate<'scope, T>(
327 collection: ColumnarCollection<'scope, T, Row, Diff>,
328 name: &str,
329) -> ColumnarCollection<'scope, T, Row, Diff>
330where
331 T: RenderTimestamp,
332{
333 let exchange = ExchangeCore::<ColumnBuilder<_>, _>::new_core(
337 columnar_consolidate_exchange::<Row, T, Diff>,
338 );
339 let consolidated =
340 consolidate_pact::<AccountedChunkChainBatcher<Row, T, Diff, ComputeSpill>, _, _>(
341 collection.inner,
342 exchange,
343 name,
344 AccountedChunkChainBatcher::new,
345 );
346
347 consolidated
354 .unary::<NoopBuilder<Column<(Row, T, Diff)>>, _, _, _>(
355 Pipeline,
356 &format!("Flatten {name}"),
357 |_cap, _info| {
358 move |input, output| {
359 input.for_each(|time, data| {
360 let mut session = output.session_with_builder(&time);
361 for chunk in data.drain(..).flatten() {
362 let mut column = Column::from(chunk.into_body());
363 session.give_container(&mut column);
364 }
365 });
366 }
367 },
368 )
369 .as_collection()
370}
371
372pub fn vec_to_columnar<'scope, T>(
377 collection: VecCollection<'scope, T, Row, Diff>,
378) -> ColumnarCollection<'scope, T, Row, Diff>
379where
380 T: RenderTimestamp,
381{
382 collection
383 .inner
384 .unary::<ColumnBuilder<(Row, T, Diff)>, _, _, _>(
385 Pipeline,
386 "VecToColumnar",
387 |_cap, _info| {
388 move |input, output| {
389 input.for_each(|time, data| {
390 let mut session = output.session_with_builder(&time);
391 for (v, t, d) in data.drain(..) {
392 session.give((&v, &t, &d));
393 }
394 });
395 }
396 },
397 )
398 .as_collection()
399}
400
401pub fn columnar_to_vec<'scope, T>(
407 collection: ColumnarCollection<'scope, T, Row, Diff>,
408) -> VecCollection<'scope, T, Row, Diff>
409where
410 T: RenderTimestamp,
411{
412 collection
413 .inner
414 .unary::<CapacityContainerBuilder<Vec<(Row, T, Diff)>>, _, _, _>(
415 Pipeline,
416 "ColumnarToVec",
417 |_cap, _info| {
418 move |input, output| {
419 input.for_each(|time, data| {
420 let mut session = output.session(&time);
421 for (v, t, d) in data.borrow().into_index_iter() {
422 session.give((
423 Columnar::into_owned(v),
424 Columnar::into_owned(t),
425 Columnar::into_owned(d),
426 ));
427 }
428 });
429 }
430 },
431 )
432 .as_collection()
433}
434
435#[cfg(test)]
436mod tests {
437 use differential_dataflow::input::Input;
438 use mz_ore::cast::CastFrom;
439 use mz_repr::{Datum, Timestamp};
440 use timely::dataflow::operators::Capture;
441 use timely::dataflow::operators::capture::{Event, Extract};
442
443 use super::*;
444
445 type RowBuilder = CapacityContainerBuilder<Vec<(Row, Timestamp, Diff)>>;
446 type CapturedRows = std::sync::mpsc::Receiver<Event<Timestamp, Vec<(Row, Timestamp, Diff)>>>;
447
448 fn extract_sorted(captured: CapturedRows) -> Vec<(Row, Timestamp, Diff)> {
449 let mut updates: Vec<_> = captured
450 .extract()
451 .into_iter()
452 .flat_map(|(_, data)| data)
453 .collect();
454 updates.sort();
455 updates
456 }
457
458 fn test_rows() -> Vec<Row> {
459 vec![
460 Row::pack_slice(&[Datum::Int32(42), Datum::String("hello")]),
461 Row::pack_slice(&[Datum::Int64(100), Datum::Null]),
462 Row::pack_slice(&[Datum::True, Datum::False, Datum::Null]),
463 Row::default(),
464 ]
465 }
466
467 #[mz_ore::test]
468 fn round_trip_through_columnar() {
469 let rows = test_rows();
470 let expected: Vec<_> = {
471 let mut updates: Vec<_> = rows
472 .iter()
473 .enumerate()
474 .map(|(i, r)| (r.clone(), Timestamp::from(u64::cast_from(i / 2)), Diff::ONE))
475 .collect();
476 updates.sort();
477 updates
478 };
479 let captured = timely::execute_directly(move |worker| {
480 worker.dataflow::<Timestamp, _, _>(|scope| {
481 let (mut input, collection) = scope.new_collection();
482 let captured = columnar_to_vec(vec_to_columnar(collection)).inner.capture();
483 for (i, row) in rows.into_iter().enumerate() {
484 input.advance_to(Timestamp::from(u64::cast_from(i / 2)));
485 input.update(row, Diff::ONE);
486 }
487 input.advance_to(Timestamp::from(2_u64));
488 input.flush();
489 captured
490 })
491 });
492 assert_eq!(extract_sorted(captured), expected);
493 }
494
495 #[mz_ore::test]
496 fn columnar_negate_flips_diffs() {
497 let rows = test_rows();
498 let expected: Vec<_> = {
499 let mut updates: Vec<_> = rows
500 .iter()
501 .map(|r| (r.clone(), Timestamp::from(0_u64), -Diff::ONE))
502 .collect();
503 updates.sort();
504 updates
505 };
506 let captured = timely::execute_directly(move |worker| {
507 worker.dataflow::<Timestamp, _, _>(|scope| {
508 let (mut input, collection) = scope.new_collection();
509 let edge = columnar_negate(vec_to_columnar(collection));
510 let captured = columnar_to_vec(edge).inner.capture();
511 for row in rows {
512 input.update(row, Diff::ONE);
513 }
514 input.advance_to(Timestamp::from(1_u64));
515 input.flush();
516 captured
517 })
518 });
519 assert_eq!(extract_sorted(captured), expected);
520 }
521
522 #[mz_ore::test]
523 fn concat_many_concatenates_columnar() {
524 let rows = test_rows();
525 let expected: Vec<_> = {
526 let mut updates: Vec<_> = rows
527 .iter()
528 .map(|r| (r.clone(), Timestamp::from(0_u64), Diff::ONE))
529 .collect();
530 updates.push((rows[0].clone(), Timestamp::from(0_u64), Diff::ONE));
532 updates.sort();
533 updates
534 };
535 let captured = timely::execute_directly(move |worker| {
536 worker.dataflow::<Timestamp, _, _>(|scope| {
537 let (mut input1, collection1) = scope.new_collection();
538 let (mut input2, collection2) = scope.new_collection();
539 let edge = concat_many(
540 scope,
541 [vec_to_columnar(collection1), vec_to_columnar(collection2)],
542 );
543 let captured = columnar_to_vec(edge).inner.capture();
544 let (first, rest) = rows.split_first().unwrap();
545 input1.update(first.clone(), Diff::ONE);
546 input2.update(first.clone(), Diff::ONE);
547 for row in rest {
548 input1.update(row.clone(), Diff::ONE);
549 }
550 for input in [&mut input1, &mut input2] {
551 input.advance_to(Timestamp::from(1_u64));
552 input.flush();
553 }
554 captured
555 })
556 });
557 assert_eq!(extract_sorted(captured), expected);
558 }
559
560 #[mz_ore::test]
561 fn flat_map_datums_arms_agree() {
562 let rows = test_rows();
564 let captured = timely::execute_directly(move |worker| {
565 worker.dataflow::<Timestamp, _, _>(|scope| {
566 let (mut input, collection) = scope.new_collection();
567 let (oks, _errs) = flat_map_datums::<_, RowBuilder, _>(
568 vec_to_columnar(collection),
569 "test",
570 1,
571 |datums, t, d, ok_session, _err_session| {
572 ok_session.give((Row::pack(datums.iter()), t, d));
573 1
574 },
575 );
576 let captured = oks.capture();
577 for row in rows {
578 input.update(row, Diff::ONE);
579 }
580 input.advance_to(Timestamp::from(1_u64));
581 input.flush();
582 captured
583 })
584 });
585 let updates = extract_sorted(captured);
586 assert!(!updates.is_empty());
587 assert!(updates.iter().all(|(r, _, _)| r.iter().count() <= 1));
589 }
590
591 #[mz_ore::test]
592 fn columnar_consolidate_accumulates_and_cancels() {
593 let row1 = Row::pack_slice(&[Datum::Int32(1)]);
594 let row2 = Row::pack_slice(&[Datum::Int32(2)]);
595 let row3 = Row::pack_slice(&[Datum::Int32(3)]);
596 let expected = vec![
599 (row1.clone(), Timestamp::from(0_u64), Diff::from(2)),
600 (row1.clone(), Timestamp::from(1_u64), Diff::ONE),
601 ];
602
603 let captured = timely::execute_directly(move |worker| {
604 worker.dataflow::<Timestamp, _, _>(|scope| {
605 let (mut input, collection) = scope.new_collection();
606 let edge = columnar_consolidate(vec_to_columnar(collection), "Test");
607 let captured = columnar_to_vec(edge).inner.capture();
608 input.advance_to(Timestamp::from(0_u64));
610 input.update(row1.clone(), Diff::ONE);
611 input.update(row1.clone(), Diff::ONE);
612 input.update(row2.clone(), Diff::ONE);
613 input.update(row2, -Diff::ONE);
614 input.advance_to(Timestamp::from(1_u64));
616 input.update(row1, Diff::ONE);
617 input.update(row3.clone(), Diff::ONE);
618 input.update(row3, -Diff::ONE);
619 input.advance_to(Timestamp::from(2_u64));
620 input.flush();
621 captured
622 })
623 });
624 assert_eq!(extract_sorted(captured), expected);
625 }
626
627 #[mz_ore::test]
630 #[cfg_attr(miri, ignore)] fn columnar_consolidate_spills_and_round_trips() {
632 use mz_ore::pool::Pool;
633 use mz_timely_util::columnar::chunk::set_spill_override;
634
635 let rows: Vec<Row> = (0..40_000i64)
639 .map(|i| Row::pack_slice(&[Datum::Int64(i), Datum::String("a repeated string value")]))
640 .collect();
641 let expected = rows.len();
642
643 let pool = Pool::new().expect("pool creation");
644 set_spill_override(Some(pool.clone()));
647 let captured = timely::execute_directly(move |worker| {
648 worker.dataflow::<Timestamp, _, _>(|scope| {
649 let (mut input, collection) = scope.new_collection();
650 let edge = columnar_consolidate(vec_to_columnar(collection), "Test");
651 let captured = columnar_to_vec(edge).inner.capture();
652 input.advance_to(Timestamp::from(0_u64));
653 for row in rows {
654 input.update(row, Diff::ONE);
655 }
656 input.advance_to(Timestamp::from(1_u64));
657 input.flush();
658 captured
659 })
660 });
661 set_spill_override(None);
662
663 assert!(
664 pool.stats().inserts > 0,
665 "the payload should have reached the pool"
666 );
667 let updates = extract_sorted(captured);
668 assert_eq!(updates.len(), expected);
669 assert!(
670 updates
671 .iter()
672 .all(|(_, time, diff)| *time == Timestamp::from(0_u64) && *diff == Diff::ONE),
673 "every row survives at its own time and multiplicity"
674 );
675 }
676}