@@ -36,9 +36,11 @@ use mz_timely_util::columnar::builder::ColumnBuilder;
3636use mz_timely_util:: columnar:: consolidate:: ConsolidatingColumnBuilder ;
3737use mz_timely_util:: columnar:: { Col2ValBatcher , Col2ValPagedBatcher , columnar_exchange} ;
3838use mz_timely_util:: operator:: { CollectionExt , StreamExt } ;
39- use timely:: container:: CapacityContainerBuilder ;
39+ use timely:: ContainerBuilder ;
40+ use timely:: container:: { CapacityContainerBuilder , PushInto } ;
4041use timely:: dataflow:: channels:: pact:: { ExchangeCore , Pipeline } ;
4142use timely:: dataflow:: operators:: OkErr ;
43+ use timely:: dataflow:: operators:: generic:: Operator ;
4244use timely:: dataflow:: { Scope , Stream } ;
4345
4446use crate :: extensions:: arrange:: MzArrangeCore ;
@@ -101,15 +103,21 @@ impl LinearJoinSpec {
101103 }
102104 }
103105
104- /// Render a join operator according to this specification.
105- fn render < ' s , T , Tr1 , Tr2 , L , I > (
106+ /// Render a join operator according to this specification, assembling its
107+ /// output through `CB`.
108+ ///
109+ /// The `DifferentialDataflow` implementation builds its own `Vec` output and
110+ /// cannot be handed a container builder, so that arm re-encodes through `CB`.
111+ /// The `Materialize` implementation writes `CB` directly.
112+ fn render < ' s , T , Tr1 , Tr2 , L , I , CB > (
106113 & self ,
107114 arranged1 : Arranged < ' s , Tr1 > ,
108115 arranged2 : Arranged < ' s , Tr2 > ,
109116 result : L ,
110- ) -> VecCollection < ' s , T , I :: Item , Diff >
117+ ) -> Stream < ' s , T , CB :: Container >
111118 where
112119 T : Lattice + timely:: progress:: Timestamp ,
120+ CB : ContainerBuilder + PushInto < ( I :: Item , T , Diff ) > + ' static ,
113121 Tr1 : TraceReader < Batch : Navigable , Time = T > + Clone + ' static ,
114122 Tr2 : TraceReader < Batch : Navigable , Time = T > + Clone + ' static ,
115123 BatchCursor < Tr1 > : Cursor < Time = T , Diff = Diff > ,
@@ -119,44 +127,30 @@ impl LinearJoinSpec {
119127 {
120128 use LinearJoinImpl :: * ;
121129
122- // `mz_join_core` builds its output through a container builder. The
123- // `Vec` accumulator this method returns needs the capacity builder.
124- type VecCB < D , T > = CapacityContainerBuilder < Vec < ( D , T , Diff ) > > ;
125-
126130 match (
127131 self . implementation ,
128132 self . yielding . after_work ,
129133 self . yielding . after_time ,
130134 ) {
131- ( DifferentialDataflow , _, _) => arranged1. join_core ( arranged2, result) ,
135+ ( DifferentialDataflow , _, _) => {
136+ encode_updates :: < _ , _ , CB > ( arranged1. join_core ( arranged2, result) , "JoinCoreEncode" )
137+ }
132138 ( Materialize , Some ( work_limit) , Some ( time_limit) ) => {
133139 let yield_fn =
134140 move |start : Instant , work| work >= work_limit || start. elapsed ( ) >= time_limit;
135- mz_join_core :: < _ , _ , _ , _ , _ , _ , VecCB < I :: Item , T > > (
136- arranged1, arranged2, result, yield_fn,
137- )
138- . as_collection ( )
141+ mz_join_core :: < _ , _ , _ , _ , _ , _ , CB > ( arranged1, arranged2, result, yield_fn)
139142 }
140143 ( Materialize , Some ( work_limit) , None ) => {
141144 let yield_fn = move |_start, work| work >= work_limit;
142- mz_join_core :: < _ , _ , _ , _ , _ , _ , VecCB < I :: Item , T > > (
143- arranged1, arranged2, result, yield_fn,
144- )
145- . as_collection ( )
145+ mz_join_core :: < _ , _ , _ , _ , _ , _ , CB > ( arranged1, arranged2, result, yield_fn)
146146 }
147147 ( Materialize , None , Some ( time_limit) ) => {
148148 let yield_fn = move |start : Instant , _work| start. elapsed ( ) >= time_limit;
149- mz_join_core :: < _ , _ , _ , _ , _ , _ , VecCB < I :: Item , T > > (
150- arranged1, arranged2, result, yield_fn,
151- )
152- . as_collection ( )
149+ mz_join_core :: < _ , _ , _ , _ , _ , _ , CB > ( arranged1, arranged2, result, yield_fn)
153150 }
154151 ( Materialize , None , None ) => {
155152 let yield_fn = |_start, _work| false ;
156- mz_join_core :: < _ , _ , _ , _ , _ , _ , VecCB < I :: Item , T > > (
157- arranged1, arranged2, result, yield_fn,
158- )
159- . as_collection ( )
153+ mz_join_core :: < _ , _ , _ , _ , _ , _ , CB > ( arranged1, arranged2, result, yield_fn)
160154 }
161155 }
162156 }
@@ -215,9 +209,13 @@ enum JoinedFlavor<'scope, T: RenderTimestamp> {
215209 /// stage. `differential_join` forms its arrangement key off the edge, so a
216210 /// columnar source flows in without a `ColumnarToVec` decode.
217211 Edge ( CollectionEdge < ' scope , T > ) ,
218- /// The intra-operator multi-stage accumulator. `mz_join_core` is
219- /// `Vec`-internal, so the accumulator is a bare `VecCollection`, not a
220- /// collection edge.
212+ /// The intra-operator multi-stage accumulator.
213+ ///
214+ /// A stage whose output is consumed by another stage's arrangement or by a
215+ /// finalization closure writes this. Both of those re-encode what they read,
216+ /// and a `Vec` hands them moved `Row` allocations where a `Column` would
217+ /// copy row bytes, so the accumulator stays `Vec`. Only a stage whose output
218+ /// *is* the node's output writes [`JoinedFlavor::Edge`].
221219 Collection ( VecCollection < ' scope , T , Row , Diff > ) ,
222220 /// A dataflow-local arrangement.
223221 Local ( Arranged < ' scope , RowRowAgent < T , Diff > > ) ,
@@ -326,18 +324,23 @@ where
326324 } ;
327325
328326 // progress through stages, updating partial results and errors.
329- for stage_plan in linear_plan. stage_plans . into_iter ( ) {
327+ //
328+ // The last stage writes the node's output edge directly, but only when
329+ // no finalization closure follows it. With a closure, the closure's
330+ // builder writes the edge and the stage feeds it the `Vec` accumulator.
331+ let stage_count = linear_plan. stage_plans . len ( ) ;
332+ let terminal_stage_writes_edge = linear_plan. final_closure . is_none ( ) ;
333+ for ( index, stage_plan) in linear_plan. stage_plans . into_iter ( ) . enumerate ( ) {
334+ let terminal = index + 1 == stage_count && terminal_stage_writes_edge;
330335 // Different variants of `joined` implement this differently,
331336 // and the logic is centralized there.
332- let stream = self . differential_join (
337+ joined = self . differential_join (
333338 joined,
334339 inputs[ stage_plan. lookup_relation ] . enter_region ( inner) ,
335340 stage_plan,
341+ terminal,
336342 & mut errors,
337343 ) ;
338- // Update joined results and capture any errors. `mz_join_core`
339- // produces a `Vec` collection, the intra-operator accumulator.
340- joined = JoinedFlavor :: Collection ( stream) ;
341344 }
342345
343346 // We have completed the join building, but may have work remaining.
@@ -377,10 +380,14 @@ where
377380 errors. push ( errs) ;
378381 updates
379382 } else {
380- // Identity finalization: the raw output is the result. The source edge
381- // (single-input join) is already columnar and passes through; the `Vec`
382- // accumulator encodes via `vec_to_columnar`, non-consolidating to match
383- // the raw output.
383+ // Identity finalization: the raw output is the result. A
384+ // single-input join passes its source edge through, and with stages
385+ // the last one wrote the edge itself, because
386+ // `terminal_stage_writes_edge` holds exactly here.
387+ //
388+ // The accumulator arm is reachable only through an initial closure
389+ // on a stage-less join, which current lowering never emits. It
390+ // encodes rather than panics, so a lowering change stays correct.
384391 match joined {
385392 JoinedFlavor :: Edge ( edge) => edge,
386393 JoinedFlavor :: Collection ( collection) => vec_to_columnar ( collection) ,
@@ -398,6 +405,9 @@ where
398405
399406 /// Looks up the arrangement for the next input and joins it to the arranged
400407 /// version of the join of previous inputs.
408+ ///
409+ /// `terminal` marks a stage whose output is the node's output, which makes
410+ /// it write the output edge rather than the `Vec` accumulator.
401411 fn differential_join < ' s > (
402412 & self ,
403413 mut joined : JoinedFlavor < ' s , T > ,
@@ -409,8 +419,9 @@ where
409419 closure,
410420 lookup_relation : _,
411421 } : LinearStagePlan ,
422+ terminal : bool ,
412423 errors : & mut Vec < VecCollection < ' s , T , DataflowErrorSer , Diff > > ,
413- ) -> VecCollection < ' s , T , Row , Diff > {
424+ ) -> JoinedFlavor < ' s , T > {
414425 // If we have a streamed input, we must first form an arrangement. The
415426 // source edge keys off the `CollectionEdge` (a columnar source has no
416427 // `ColumnarToVec` hop); the intra-operator accumulator is a bare
@@ -449,7 +460,7 @@ where
449460 ArrangementFlavor :: Local ( oks, errs1) => {
450461 let ( oks, errs2) = self
451462 . differential_join_inner :: < RowRowAgent < _ , _ > , RowRowAgent < _ , _ > > (
452- local, oks, closure,
463+ local, oks, closure, terminal ,
453464 ) ;
454465
455466 errors. push ( errs1. as_collection ( |k, _v| k. clone ( ) ) ) ;
@@ -459,7 +470,7 @@ where
459470 ArrangementFlavor :: Trace ( _gid, oks, errs1) => {
460471 let ( oks, errs2) = self
461472 . differential_join_inner :: < RowRowAgent < _ , _ > , RowRowEnter < _ , _ , _ > > (
462- local, oks, closure,
473+ local, oks, closure, terminal ,
463474 ) ;
464475
465476 errors. push ( errs1. as_collection ( |k, _v| k. clone ( ) ) ) ;
@@ -471,7 +482,7 @@ where
471482 ArrangementFlavor :: Local ( oks, errs1) => {
472483 let ( oks, errs2) = self
473484 . differential_join_inner :: < RowRowEnter < _ , _ , _ > , RowRowAgent < _ , _ > > (
474- trace, oks, closure,
485+ trace, oks, closure, terminal ,
475486 ) ;
476487
477488 errors. push ( errs1. as_collection ( |k, _v| k. clone ( ) ) ) ;
@@ -481,7 +492,7 @@ where
481492 ArrangementFlavor :: Trace ( _gid, oks, errs1) => {
482493 let ( oks, errs2) = self
483494 . differential_join_inner :: < RowRowEnter < _ , _ , _ > , RowRowEnter < _ , _ , _ > > (
484- trace, oks, closure,
495+ trace, oks, closure, terminal ,
485496 ) ;
486497
487498 errors. push ( errs1. as_collection ( |k, _v| k. clone ( ) ) ) ;
@@ -498,13 +509,19 @@ where
498509 ///
499510 /// The return type includes an optional error collection, which may be
500511 /// `None` if we can determine that `closure` cannot error.
512+ /// `terminal` marks a stage whose output is the node's output, which makes
513+ /// the ok side write a [`ColumnBuilder`] instead of the `Vec` accumulator,
514+ /// so the node needs no leaf encode. An error-capable closure writes the
515+ /// accumulator either way, because its output has to be demuxed by
516+ /// `ok_err` before the ok side can be encoded.
501517 fn differential_join_inner < ' s , Tr1 , Tr2 > (
502518 & self ,
503519 prev_keyed : Arranged < ' s , Tr1 > ,
504520 next_input : Arranged < ' s , Tr2 > ,
505521 closure : JoinClosure ,
522+ terminal : bool ,
506523 ) -> (
507- VecCollection < ' s , T , Row , Diff > ,
524+ JoinedFlavor < ' s , T > ,
508525 Option < VecCollection < ' s , T , DataflowErrorSer , Diff > > ,
509526 )
510527 where
@@ -518,25 +535,22 @@ where
518535 // Reuseable allocation for unpacking.
519536 let mut datums = DatumVec :: new ( ) ;
520537
538+ // The `Vec` accumulator's builder. Named because the ok side picks
539+ // between it and a `ColumnBuilder` on `terminal`.
540+ type VecCB < D , T > = CapacityContainerBuilder < Vec < ( D , T , Diff ) > > ;
541+
521542 if closure. could_error ( ) {
522543 let ( oks, err) = self
523544 . linear_join_spec
524- . render ( prev_keyed, next_input, move |key, old, new| {
525- let mut row_builder = SharedRow :: get ( ) ;
526- let temp_storage = RowArena :: new ( ) ;
527-
528- let mut datums_local = datums. borrow ( ) ;
529- key. extend_datums ( & temp_storage, & mut datums_local, None ) ;
530- old. extend_datums ( & temp_storage, & mut datums_local, None ) ;
531- new. extend_datums ( & temp_storage, & mut datums_local, None ) ;
532-
533- closure
534- . apply ( & mut datums_local, & temp_storage, & mut row_builder)
535- . map ( |row| row. cloned ( ) )
536- . map_err ( DataflowErrorSer :: from)
537- . transpose ( )
538- } )
539- . inner
545+ . render :: < T , _ , _ , _ , _ , VecCB < Result < Row , DataflowErrorSer > , T > > (
546+ prev_keyed,
547+ next_input,
548+ move |key, old, new| {
549+ apply_join_closure ( & closure, & mut datums, key, old, new)
550+ . map_err ( DataflowErrorSer :: from)
551+ . transpose ( )
552+ } ,
553+ )
540554 . ok_err ( |( x, t, d) | {
541555 // TODO(mcsherry): consider `ok_err()` for `Collection`.
542556 match x {
@@ -545,30 +559,101 @@ where
545559 }
546560 } ) ;
547561
548- ( oks. as_collection ( ) , Some ( err. as_collection ( ) ) )
549- } else {
562+ let oks = oks. as_collection ( ) ;
563+ let oks = if terminal {
564+ // The demux already materialized the ok side as a `Vec`, so the
565+ // leaf encode stands here.
566+ JoinedFlavor :: Edge ( vec_to_columnar ( oks) )
567+ } else {
568+ JoinedFlavor :: Collection ( oks)
569+ } ;
570+ ( oks, Some ( err. as_collection ( ) ) )
571+ } else if terminal {
550572 let oks = self
551573 . linear_join_spec
552- . render ( prev_keyed , next_input , move |key , old , new| {
553- let mut row_builder = SharedRow :: get ( ) ;
554- let temp_storage = RowArena :: new ( ) ;
555-
556- let mut datums_local = datums . borrow ( ) ;
557- key . extend_datums ( & temp_storage , & mut datums_local , None ) ;
558- old . extend_datums ( & temp_storage , & mut datums_local , None ) ;
559- new . extend_datums ( & temp_storage , & mut datums_local , None ) ;
574+ . render :: < T , _ , _ , _ , _ , ConsolidatingColumnBuilder < Row , T , Diff > > (
575+ prev_keyed ,
576+ next_input ,
577+ move |key , old , new| {
578+ apply_join_closure ( & closure , & mut datums , key , old , new )
579+ . expect ( "Closure claimed to never error" )
580+ } ,
581+ ) ;
560582
561- closure
562- . apply ( & mut datums_local, & temp_storage, & mut row_builder)
563- . expect ( "Closure claimed to never error" )
564- . cloned ( )
565- } ) ;
583+ ( JoinedFlavor :: Edge ( oks. as_collection ( ) ) , None )
584+ } else {
585+ let oks = self
586+ . linear_join_spec
587+ . render :: < T , _ , _ , _ , _ , VecCB < Row , T > > (
588+ prev_keyed,
589+ next_input,
590+ move |key, old, new| {
591+ apply_join_closure ( & closure, & mut datums, key, old, new)
592+ . expect ( "Closure claimed to never error" )
593+ } ,
594+ ) ;
566595
567- ( oks, None )
596+ ( JoinedFlavor :: Collection ( oks. as_collection ( ) ) , None )
568597 }
569598 }
570599}
571600
601+ /// Unpacks one join match into datums and applies `closure` to it.
602+ ///
603+ /// `None` means the closure filtered the match out. The output row is owned
604+ /// because the row `closure` writes borrows the shared row builder, which the
605+ /// caller must not hold past this call.
606+ fn apply_join_closure < K , V1 , V2 > (
607+ closure : & JoinClosure ,
608+ datums : & mut DatumVec ,
609+ key : K ,
610+ old : V1 ,
611+ new : V2 ,
612+ ) -> Result < Option < Row > , mz_expr:: EvalError >
613+ where
614+ K : ExtendDatums ,
615+ V1 : ExtendDatums ,
616+ V2 : ExtendDatums ,
617+ {
618+ let mut row_builder = SharedRow :: get ( ) ;
619+ let temp_storage = RowArena :: new ( ) ;
620+
621+ let mut datums_local = datums. borrow ( ) ;
622+ key. extend_datums ( & temp_storage, & mut datums_local, None ) ;
623+ old. extend_datums ( & temp_storage, & mut datums_local, None ) ;
624+ new. extend_datums ( & temp_storage, & mut datums_local, None ) ;
625+
626+ closure
627+ . apply ( & mut datums_local, & temp_storage, & mut row_builder)
628+ . map ( |row| row. cloned ( ) )
629+ }
630+
631+ /// Re-encodes a `Vec` collection through `CB`.
632+ ///
633+ /// For a join implementation that builds its own `Vec` output and so cannot be
634+ /// handed a container builder.
635+ fn encode_updates < ' s , T , D , CB > (
636+ collection : VecCollection < ' s , T , D , Diff > ,
637+ name : & str ,
638+ ) -> Stream < ' s , T , CB :: Container >
639+ where
640+ T : timely:: progress:: Timestamp ,
641+ D : Data ,
642+ CB : ContainerBuilder + PushInto < ( D , T , Diff ) > + ' static ,
643+ {
644+ collection
645+ . inner
646+ . unary :: < CB , _ , _ , _ > ( Pipeline , name, |_, _| {
647+ move |input, output| {
648+ input. for_each ( |time, data| {
649+ output
650+ . session_with_builder ( & time)
651+ . give_iterator ( data. drain ( ..) ) ;
652+ } ) ;
653+ }
654+ } )
655+ }
656+
572657/// Keys a row-formatted join input stream into columnar `((key, value), t, d)`
573658/// updates, splitting off key-evaluation errors into a separate stream.
574659///
0 commit comments