@@ -19,6 +19,7 @@ import (
1919 "bytes"
2020 "fmt"
2121 "io"
22+ "log/slog"
2223 "reflect"
2324 "sort"
2425 "strings"
@@ -72,6 +73,7 @@ func (*runner) PrepareUrns() []string {
7273 urns .TransformRedistributeArbitrarily ,
7374 urns .TransformRedistributeByKey ,
7475 urns .TransformFlatten ,
76+ urns .TransformTestStream ,
7577 }
7678}
7779
@@ -82,6 +84,8 @@ func (h *runner) PrepareTransform(tid string, t *pipepb.PTransform, comps *pipep
8284 return h .handleFlatten (tid , t , comps )
8385 case urns .TransformReshuffle , urns .TransformRedistributeArbitrarily , urns .TransformRedistributeByKey :
8486 return h .handleReshuffle (tid , t , comps )
87+ case urns .TransformTestStream :
88+ return h .handleTestStream (tid , t , comps )
8589 default :
8690 panic ("unknown urn to Prepare: " + t .GetSpec ().GetUrn ())
8791 }
@@ -216,6 +220,111 @@ func (h *runner) handleReshuffle(tid string, t *pipepb.PTransform, comps *pipepb
216220 }
217221}
218222
223+ func (h * runner ) handleTestStream (tid string , t * pipepb.PTransform , comps * pipepb.Components ) prepareResult {
224+ var pyld pipepb.TestStreamPayload
225+ if err := proto .Unmarshal (t .GetSpec ().GetPayload (), & pyld ); err != nil {
226+ panic ("Failed to decode TestStreamPayload: " + err .Error ())
227+ }
228+ coders := map [string ]* pipepb.Coder {}
229+ // Ensure awareness of the coder used for the teststream.
230+ cID , err := lpUnknownCoders (pyld .GetCoderId (), coders , comps .GetCoders ())
231+ if err != nil {
232+ panic (err )
233+ }
234+
235+ // If the TestStream coder needs to be LP'ed or if it is a coder that has different
236+ // behaviors between nested context and outer context (in Java SDK), then we must
237+ // LP this coder and the TestStream data elements.
238+ forceLP := (cID != pyld .GetCoderId () && coders [pyld .GetCoderId ()].GetSpec ().GetUrn () != "beam:go:coder:custom:v1" ) ||
239+ coders [cID ].GetSpec ().GetUrn () == urns .CoderStringUTF8 ||
240+ coders [cID ].GetSpec ().GetUrn () == urns .CoderBytes ||
241+ coders [cID ].GetSpec ().GetUrn () == urns .CoderKV
242+
243+ if ! forceLP {
244+ return prepareResult {SubbedComps : & pipepb.Components {
245+ Transforms : map [string ]* pipepb.PTransform {tid : t },
246+ }}
247+ }
248+
249+ // The coder needed length prefixing. For simplicity, add a length prefix to each
250+ // encoded element, since we will be sending a length prefixed coder to consume
251+ // this anyway. This is simpler than trying to find all the re-written coders after the fact.
252+ // This also adds a LP-coder for the original coder in comps.
253+ cID , err = forceLpCoder (pyld .GetCoderId (), coders , comps .GetCoders ())
254+ if err != nil {
255+ panic (err )
256+ }
257+ slog .Debug ("teststream: add coder" , "coderId" , cID )
258+
259+ mustLP := func (v []byte ) []byte {
260+ var buf bytes.Buffer
261+ if err := coder .EncodeVarInt ((int64 )(len (v )), & buf ); err != nil {
262+ panic (err )
263+ }
264+ if _ , err := buf .Write (v ); err != nil {
265+ panic (err )
266+ }
267+ return buf .Bytes ()
268+ }
269+
270+ // We need to loop over the events.
271+ // For element events, we need to apply the mayLP function to the encoded element.
272+ // Then we construct a new payload with the modified events.
273+ var newEvents []* pipepb.TestStreamPayload_Event
274+ for _ , event := range pyld .GetEvents () {
275+ switch event .GetEvent ().(type ) {
276+ case * pipepb.TestStreamPayload_Event_ElementEvent :
277+ elms := event .GetElementEvent ().GetElements ()
278+ var newElms []* pipepb.TestStreamPayload_TimestampedElement
279+ for _ , elm := range elms {
280+ newElm := proto .Clone (elm ).(* pipepb.TestStreamPayload_TimestampedElement )
281+ newElm .EncodedElement = mustLP (elm .GetEncodedElement ())
282+ slog .Debug ("handleTestStream: rewrite bytes" ,
283+ "before:" , string (elm .GetEncodedElement ()),
284+ "after:" , string (newElm .GetEncodedElement ()))
285+ newElms = append (newElms , newElm )
286+ }
287+ newEvents = append (newEvents , & pipepb.TestStreamPayload_Event {
288+ Event : & pipepb.TestStreamPayload_Event_ElementEvent {
289+ ElementEvent : & pipepb.TestStreamPayload_Event_AddElements {
290+ Elements : newElms ,
291+ },
292+ },
293+ })
294+ default :
295+ newEvents = append (newEvents , event )
296+ }
297+ }
298+ newPyld := & pipepb.TestStreamPayload {
299+ CoderId : cID ,
300+ Events : newEvents ,
301+ Endpoint : pyld .GetEndpoint (),
302+ }
303+ b , err := proto .Marshal (newPyld )
304+ if err != nil {
305+ panic (fmt .Sprintf ("couldn't marshal new test stream payload: %v" , err ))
306+ }
307+
308+ ts := proto .Clone (t ).(* pipepb.PTransform )
309+ ts .GetSpec ().Payload = b
310+
311+ pcolSubs := map [string ]* pipepb.PCollection {}
312+ for _ , gi := range ts .GetOutputs () {
313+ pcol := comps .GetPcollections ()[gi ]
314+ newPcol := proto .Clone (pcol ).(* pipepb.PCollection )
315+ newPcol .CoderId = cID
316+ slog .Debug ("handleTestStream: rewrite coder for output pcoll" , "colId" , gi , "oldId" , pcol .CoderId , "newId" , newPcol .CoderId )
317+ pcolSubs [gi ] = newPcol
318+ }
319+
320+ tSubs := map [string ]* pipepb.PTransform {tid : ts }
321+ return prepareResult {SubbedComps : & pipepb.Components {
322+ Transforms : tSubs ,
323+ Pcollections : pcolSubs ,
324+ Coders : coders ,
325+ }}
326+ }
327+
219328var _ transformExecuter = (* runner )(nil )
220329
221330func (* runner ) ExecuteUrns () []string {
0 commit comments