@@ -18,6 +18,7 @@ use mz_expr::{Eval, MfpPlan};
1818use mz_repr:: { DatumVec , RowArena , SharedRow } ;
1919use mz_repr:: { Diff , Row , RowRef , Timestamp } ;
2020use mz_timely_util:: columnar:: Column ;
21+ use mz_timely_util:: columnar:: consolidate:: ConsolidatingColumnBuilder ;
2122use mz_timely_util:: operator:: StreamExt ;
2223use timely:: Container ;
2324use timely:: container:: DrainContainer ;
@@ -76,15 +77,19 @@ impl<'scope, T: crate::render::RenderTimestamp> Context<'scope, T> {
7677 } ;
7778
7879 use differential_dataflow:: AsCollection ;
79- let ok_collection = oks. as_collection ( ) ;
80+ let ok_collection = CollectionEdge :: Columnar ( oks. as_collection ( ) ) ;
8081 let new_err_collection = errs. as_collection ( ) ;
8182 let err_collection = err_collection. concat ( new_err_collection) ;
82- CollectionBundle :: from_collections ( ok_collection, err_collection)
83+ CollectionBundle :: from_edge ( ok_collection, err_collection)
8384 }
8485}
8586
8687/// Output ok-session container builder for [`flat_map_stage`].
87- type FlatMapOk < T > = ConsolidatingContainerBuilder < Vec < ( Row , T , Diff ) > > ;
88+ ///
89+ /// Consolidating like the err builder, but emits `Column<(Row, T, Diff)>` so
90+ /// the FlatMap output travels as the columnar edge. Output rows are freshly
91+ /// built by the mfp, so the owned give into staging is a move, not a new alloc.
92+ type FlatMapOk < T > = ConsolidatingColumnBuilder < Row , T , Diff > ;
8893/// Output err-session container builder for [`flat_map_stage`].
8994type FlatMapErr < T > = ConsolidatingContainerBuilder < Vec < ( DataflowErrorSer , T , Diff ) > > ;
9095
@@ -137,7 +142,7 @@ fn flat_map_stage<'scope, T, C>(
137142 until : Antichain < Timestamp > ,
138143 budget : usize ,
139144) -> (
140- Stream < ' scope , T , Vec < ( Row , T , Diff ) > > ,
145+ Stream < ' scope , T , Column < ( Row , T , Diff ) > > ,
141146 Stream < ' scope , T , Vec < ( DataflowErrorSer , T , Diff ) > > ,
142147)
143148where
@@ -329,7 +334,7 @@ mod tests {
329334 use differential_dataflow:: input:: Input ;
330335 use mz_expr:: MapFilterProject ;
331336 use mz_repr:: { Datum , ReprScalarType } ;
332- use timely:: dataflow:: operators:: Inspect ;
337+ use timely:: dataflow:: operators:: InspectCore ;
333338 use timely:: dataflow:: operators:: capture:: { Capture , Extract } ;
334339
335340 use super :: * ;
@@ -370,7 +375,13 @@ mod tests {
370375 let scope = stream. scope ( ) ;
371376 let ( oks, _errs) =
372377 flat_map_stage ( stream, scope, exprs, func, mfp, Antichain :: new ( ) , budget) ;
373- oks. inspect ( move |_| * sink. borrow_mut ( ) += 1 ) ;
378+ // The columnar output ships whole `Column`s, so count records
379+ // through the container rather than per raw element.
380+ oks. inspect_container ( move |event| {
381+ if let Ok ( ( _time, data) ) = event {
382+ * sink. borrow_mut ( ) += data. borrow ( ) . into_index_iter ( ) . count ( ) ;
383+ }
384+ } ) ;
374385 input
375386 } ) ;
376387
@@ -479,21 +490,105 @@ mod tests {
479490 } )
480491 } ) ;
481492
482- let extract_sorted = |captured : std:: sync:: mpsc:: Receiver < _ > | {
483- let mut updates: Vec < ( Row , Timestamp , Diff ) > = captured
484- . extract ( )
485- . into_iter ( )
486- . flat_map ( |( _, data) | data)
487- . collect ( ) ;
488- updates. sort ( ) ;
489- updates
490- } ;
491- let vec_updates = extract_sorted ( vec_captured) ;
493+ let vec_updates = extract_sorted_columns ( vec_captured) ;
492494 assert ! ( !vec_updates. is_empty( ) ) ;
493495 assert ! (
494496 vec_updates. iter( ) . any( |( _, _, d) | * d < Diff :: ZERO ) ,
495497 "the retraction must survive as a negative diff"
496498 ) ;
497- assert_eq ! ( vec_updates, extract_sorted( col_captured) ) ;
499+ assert_eq ! ( vec_updates, extract_sorted_columns( col_captured) ) ;
500+ }
501+
502+ /// Decodes a capture of the columnar FlatMap output into sorted owned
503+ /// `(row, time, diff)` updates.
504+ fn extract_sorted_columns (
505+ captured : std:: sync:: mpsc:: Receiver <
506+ timely:: dataflow:: operators:: capture:: Event < Timestamp , Column < ( Row , Timestamp , Diff ) > > ,
507+ > ,
508+ ) -> Vec < ( Row , Timestamp , Diff ) > {
509+ let mut updates: Vec < ( Row , Timestamp , Diff ) > = captured
510+ . extract ( )
511+ . into_iter ( )
512+ . flat_map ( |( _, col) | {
513+ col. borrow ( )
514+ . into_index_iter ( )
515+ . map ( |( v, t, d) | {
516+ (
517+ Columnar :: into_owned ( v) ,
518+ Columnar :: into_owned ( t) ,
519+ Columnar :: into_owned ( d) ,
520+ )
521+ } )
522+ . collect :: < Vec < _ > > ( )
523+ } )
524+ . collect ( ) ;
525+ updates. sort ( ) ;
526+ updates
527+ }
528+
529+ #[ mz_ore:: test]
530+ fn flat_map_output_consolidates_within_batch ( ) {
531+ // Two distinct input rows whose table-function expansions overlap once
532+ // the mfp projects away the differing `stop` column. The overlapping
533+ // output rows land at the same time in one batch, so the consolidating
534+ // columnar output builder must fold them into summed diffs.
535+ let captured = timely:: execute_directly ( move |worker| {
536+ worker. dataflow :: < Timestamp , _ , _ > ( |scope| {
537+ let ( mut input, collection) = scope. new_collection ( ) ;
538+ let exprs = vec ! [
539+ LirScalarExpr :: column( 0 ) ,
540+ LirScalarExpr :: column( 1 ) ,
541+ LirScalarExpr :: literal_ok( Datum :: Int64 ( 1 ) , ReprScalarType :: Int64 ) ,
542+ ] ;
543+ let func = TableFunc :: GenerateSeriesInt64 ;
544+ // Project to only the generated value (column 2), collapsing the
545+ // two input rows' distinct (start, stop) prefixes.
546+ let mfp = MapFilterProject :: < LirScalarExpr > :: new ( 3 )
547+ . project ( vec ! [ 2 ] )
548+ . into_plan ( )
549+ . expect ( "project mfp" ) ;
550+ let stream = collection. inner ;
551+ let scope = stream. scope ( ) ;
552+ let ( oks, _errs) = flat_map_stage (
553+ stream,
554+ scope,
555+ exprs,
556+ func,
557+ mfp,
558+ Antichain :: new ( ) ,
559+ usize:: MAX ,
560+ ) ;
561+ let captured = oks. capture ( ) ;
562+ // Both rows at t=0: generate_series(1, 2) -> {1, 2},
563+ // generate_series(1, 3) -> {1, 2, 3}. Generated 1 and 2 appear on
564+ // both, so they must fold to a diff of two.
565+ input. advance_to ( Timestamp :: from ( 0_u64 ) ) ;
566+ input. update ( input_row ( 2 ) , Diff :: ONE ) ;
567+ input. update ( input_row ( 3 ) , Diff :: ONE ) ;
568+ input. advance_to ( Timestamp :: from ( 1_u64 ) ) ;
569+ input. flush ( ) ;
570+ captured
571+ } )
572+ } ) ;
573+
574+ let updates = extract_sorted_columns ( captured) ;
575+ let expected = vec ! [
576+ (
577+ Row :: pack_slice( & [ Datum :: Int64 ( 1 ) ] ) ,
578+ Timestamp :: from( 0_u64 ) ,
579+ Diff :: from( 2 ) ,
580+ ) ,
581+ (
582+ Row :: pack_slice( & [ Datum :: Int64 ( 2 ) ] ) ,
583+ Timestamp :: from( 0_u64 ) ,
584+ Diff :: from( 2 ) ,
585+ ) ,
586+ (
587+ Row :: pack_slice( & [ Datum :: Int64 ( 3 ) ] ) ,
588+ Timestamp :: from( 0_u64 ) ,
589+ Diff :: ONE ,
590+ ) ,
591+ ] ;
592+ assert_eq ! ( updates, expected) ;
498593 }
499594}
0 commit comments