public void FilterTest() { // Creates a basic filter for even-timestamped events, punctuations var inputSubject = new Subject <StreamEvent <int> >(); var qc = new QueryContainer(); var input = qc.RegisterInput(inputSubject, this.disorderPolicy, this.flushPolicy, this.punctuationPolicy, this.completedPolicy); IStreamable <Empty, int> query = input; // Add a no-op operator that isn't simply a batch-in-batch-out operator query = query.ClipEventDuration(IntervalLength); query = query.Where(this.FilterExpression); query = query.ClipEventDuration(IntervalLength); var filtered = qc.RegisterOutput(query).ForEachAsync(o => OnEgress(o)); var process = qc.Restore(); for (int i = 0; i < IngressEventCount; i++) { OnIngress(inputSubject, StreamEvent.CreateInterval(i, i + IntervalLength, i)); if (i > 0 && i % PunctuationGenerationPeriod == 0) { OnIngress(inputSubject, StreamEvent.CreatePunctuation <int>(i)); } // Make sure we don't have any pending events we expected to be egressed at this point Assert.IsTrue(this.expectedOutput.Count == 0); } OnCompleted(inputSubject); }
public void DisjointUnionNegativeWatermarkRepro() { const int leftKey = 1; const int rightKey = 2; var left = new Subject <PartitionedStreamEvent <int, int> >(); var right = new Subject <PartitionedStreamEvent <int, int> >(); var qc = new QueryContainer(); var leftInput = qc.RegisterInput(left); var rightInput = qc.RegisterInput(right); var actualOutput = new List <PartitionedStreamEvent <int, int> >(); var inputs = new IStreamable <PartitionKey <int>, int>[] { leftInput, rightInput }; var union = new MultiUnionStreamable <PartitionKey <int>, int>(inputs, guaranteedDisjoint: true); var egress = qc.RegisterOutput(union).ForEachAsync(o => actualOutput.Add(o)); var process = qc.Restore(); left.OnNext(PartitionedStreamEvent.CreatePoint(leftKey, 100, 1)); right.OnNext(PartitionedStreamEvent.CreatePoint(rightKey, 100, 1)); process.Flush(); left.OnCompleted(); right.OnCompleted(); var expected = new PartitionedStreamEvent <int, int>[] { PartitionedStreamEvent.CreatePoint(leftKey, 100, 1), PartitionedStreamEvent.CreatePoint(rightKey, 100, 1), PartitionedStreamEvent.CreateLowWatermark <int, int>(StreamEvent.InfinitySyncTime), }; Assert.IsTrue(expected.SequenceEqual(actualOutput)); }
public void SurrogateTest() { var qc = new QueryContainer(new MySurrogate()); var output1 = new List <StreamEvent <IMyInterface> >(); var input = new Subject <StreamEvent <IMyInterface> >(); var ingress = qc.RegisterInput(input); var egress = qc.RegisterOutput(ingress).ForEachAsync(o => output1.Add(o)); var process = qc.Restore(); input.OnNext(StreamEvent.CreatePoint(1, (IMyInterface) new MyType(1))); var stream = new MemoryStream(); process.Checkpoint(stream); stream.Position = 0; input.OnCompleted(); var input2 = new Subject <StreamEvent <IMyInterface> >(); var output2 = new List <StreamEvent <IMyInterface> >(); var qc2 = new QueryContainer(new MySurrogate()); var ingress2 = qc2.RegisterInput(input2); var egress2 = qc2.RegisterOutput(ingress2).ForEachAsync(o => output2.Add(o)); var process2 = qc2.Restore(stream); input2.OnCompleted(); Assert.AreEqual(2, output2.Count); Assert.AreEqual(1, output2[0].Payload.GetValue()); Assert.AreEqual(StreamEvent.InfinitySyncTime, output2[1].SyncTime); }
public void PartitionedStitch() { var subject = new Subject <PartitionedStreamEvent <string, string> >(); var qc = new QueryContainer(); var input = qc.RegisterInput(subject); var output = new List <PartitionedStreamEvent <string, string> >(); var egress = qc.RegisterOutput(input.Stitch()).ForEachAsync(o => output.Add(o)); var process = qc.Restore(); var payload = new[] { "c1payload", "c2payload" }; // c1 - [1,7),[7,10),[11,12) => [1,10),[11,12) subject.OnNext(PartitionedStreamEvent.CreateStart("c1", 1, payload: payload[0])); subject.OnNext(PartitionedStreamEvent.CreateEnd("c1", 7, 1, payload: payload[0])); subject.OnNext(PartitionedStreamEvent.CreateStart("c1", 7, payload: payload[0])); subject.OnNext(PartitionedStreamEvent.CreateEnd("c1", 10, 7, payload: payload[0])); subject.OnNext(PartitionedStreamEvent.CreateStart("c1", 11, payload: payload[0])); subject.OnNext(PartitionedStreamEvent.CreateEnd("c1", 12, 11, payload: payload[0])); // c2 - [2,3),[5,10),[10,12) => [2,3),[5,12) subject.OnNext(PartitionedStreamEvent.CreateStart("c2", 2, payload: payload[1])); subject.OnNext(PartitionedStreamEvent.CreateEnd("c2", 3, 2, payload: payload[1])); subject.OnNext(PartitionedStreamEvent.CreateStart("c2", 5, payload: payload[1])); subject.OnNext(PartitionedStreamEvent.CreateEnd("c2", 10, 5, payload: payload[1])); subject.OnNext(PartitionedStreamEvent.CreateStart("c2", 10, payload: payload[1])); subject.OnNext(PartitionedStreamEvent.CreateEnd("c2", 12, 10, payload: payload[1])); subject.OnCompleted(); process.Flush(); var expected = new[] { new List <PartitionedStreamEvent <string, string> > { PartitionedStreamEvent.CreateStart("c1", 1, payload: payload[0]), PartitionedStreamEvent.CreateEnd("c1", 10, 1, payload: payload[0]), PartitionedStreamEvent.CreateStart("c1", 11, payload: payload[0]), PartitionedStreamEvent.CreateEnd("c1", 12, 11, payload: payload[0]), }, new List <PartitionedStreamEvent <string, string> > { PartitionedStreamEvent.CreateStart("c2", 2, payload: payload[1]), PartitionedStreamEvent.CreateEnd("c2", 3, 2, payload: payload[1]), PartitionedStreamEvent.CreateStart("c2", 5, payload: payload[1]), PartitionedStreamEvent.CreateEnd("c2", 12, 5, payload: payload[1]), }, }; var outputData = new[] { output.Where(o => o.IsData && o.PartitionKey == "c1").ToList(), output.Where(o => o.IsData && o.PartitionKey == "c2").ToList(), }; Assert.IsTrue(expected[0].SequenceEqual(outputData[0])); Assert.IsTrue(expected[1].SequenceEqual(outputData[1])); }
public void PointAtEndTest2() { // nothing interesting happens here var inputList = new[] { StreamEvent.CreateInterval(1, 5, "A"), StreamEvent.CreateInterval(2, 10, "A"), StreamEvent.CreateInterval(3, 8, "A"), StreamEvent.CreateInterval(4, 6, "A"), StreamEvent.CreateInterval(8, 9, "A"), }; var compareTo = new[] { StreamEvent.CreatePoint(5, "A"), StreamEvent.CreatePoint(6, "A"), StreamEvent.CreatePoint(8, "A"), StreamEvent.CreatePoint(9, "A"), StreamEvent.CreatePoint(10, "A"), END }; var inputObservable = inputList.ToList().ToObservable(); var container = new QueryContainer(); var input = container.RegisterInput(inputObservable); var outputStream = input.PointAtEnd(); var output = container.RegisterOutput(outputStream); var result = new List <StreamEvent <string> >(); output.Subscribe(t => result.Add(t)); container.Restore(null); Assert.IsTrue(result.SequenceEqual(compareTo)); }
private QueryContainer CreateQuery() { var query = new QueryContainer(); Config.ForceRowBasedExecution = true; // incoming events are received via a conduit _tweetConduit = new Subject <StreamEvent <Tweet> >(); var streamInput = query.RegisterInput(_tweetConduit, OnCompletedPolicy.None(), DisorderPolicy.Drop(TimeSpan.FromSeconds(3).Ticks), PeriodicPunctuationPolicy.Time((ulong)TimeSpan.FromMilliseconds(1).Ticks)); // outgoing events are pushed to the dashboard var myOutput = query.RegisterOutput(TwitterAnalytics(streamInput), ReshapingPolicy.None()); myOutput.Subscribe(tweetEvent => Publish(tweetEvent)); return(query); }
public void ExtendTest1() { // nothing interesting happens here var inputList = new[] { StreamEvent.CreateStart(1, "A"), StreamEvent.CreateEnd(2, 1, "A"), StreamEvent.CreateStart(2, "A"), StreamEvent.CreateEnd(3, 2, "A"), StreamEvent.CreateStart(3, "A"), StreamEvent.CreateEnd(4, 3, "A"), StreamEvent.CreateStart(4, "A"), StreamEvent.CreateEnd(5, 4, "A") }; var compareTo = new[] { StreamEvent.CreateStart(1, "A"), StreamEvent.CreateStart(2, "A"), StreamEvent.CreateStart(3, "A"), StreamEvent.CreateStart(4, "A"), StreamEvent.CreateEnd(5, 1, "A"), StreamEvent.CreateEnd(6, 2, "A"), StreamEvent.CreateEnd(7, 3, "A"), StreamEvent.CreateEnd(8, 4, "A"), END }; var inputObservable = inputList.ToList().ToObservable(); var container = new QueryContainer(); var input = container.RegisterInput(inputObservable); input.SetProperty().IsIntervalFree(true); var outputStream = input.ExtendLifetime(3); var output = container.RegisterOutput(outputStream); var result = new List <StreamEvent <string> >(); output.Subscribe(t => result.Add(t)); container.Restore(null); Assert.IsTrue(result.SequenceEqual(compareTo)); }
private void SetupQuery( DisorderPolicy disorderPolicy, PartitionedFlushPolicy flushPolicy = PartitionedFlushPolicy.FlushOnLowWatermark, PeriodicPunctuationPolicy punctuationPolicy = null, // Default: PeriodicPunctuationPolicy.None() PeriodicLowWatermarkPolicy lowWatermarkPolicy = null, // Default: PeriodicLowWatermarkPolicy.None() OnCompletedPolicy completedPolicy = OnCompletedPolicy.EndOfStream) { var qc = new QueryContainer(); this.input = new Subject <PartitionedStreamEvent <int, int> >(); var ingress = qc.RegisterInput(this.input, disorderPolicy, flushPolicy, punctuationPolicy, lowWatermarkPolicy, completedPolicy); this.output = new List <PartitionedStreamEvent <int, int> >(); this.egress = qc.RegisterOutput(ingress).ForEachAsync(o => this.output.Add(o)); this.process = qc.Restore(); this.validateByPartition = disorderPolicy.reorderLatency > 0; }
private void SetupQuery <TInput>( Func <IPartitionedIngressStreamable <int, TInput>, IStreamable <PartitionKey <int>, double> > createQuery, out Subject <PartitionedStreamEvent <int, TInput> > input) { const uint generationPeriod = 10; const uint lowWatermarkTimestampLag = 20; var qc = new QueryContainer(); input = new Subject <PartitionedStreamEvent <int, TInput> >(); var ingress = qc.RegisterInput(input, null, PartitionedFlushPolicy.None, null, PeriodicLowWatermarkPolicy.Time(generationPeriod, lowWatermarkTimestampLag)); var query = createQuery(ingress); this.output = new List <PartitionedStreamEvent <int, double> >(); this.egress = qc.RegisterOutput(query).ForEachAsync(o => this.output.Add(o)); this.process = qc.Restore(); }
public void DisjointUnionPunctuations() { var left = new Subject <StreamEvent <int> >(); var right = new Subject <StreamEvent <int> >(); var qc = new QueryContainer(); var leftInput = qc.RegisterInput(left); var rightInput = qc.RegisterInput(right); var actualOutput = new List <StreamEvent <int> >(); var union = new MultiUnionStreamable <Empty, int>(new IStreamable <Empty, int>[] { leftInput, rightInput }, guaranteedDisjoint: true); var egress = qc.RegisterOutput(union).ForEachAsync(o => actualOutput.Add(o)); var process = qc.Restore(); left.OnNext(StreamEvent.CreatePoint(100, 1)); left.OnNext(StreamEvent.CreatePunctuation <int>(101)); right.OnNext(StreamEvent.CreatePoint(100, 1)); right.OnNext(StreamEvent.CreatePunctuation <int>(110)); process.Flush(); left.OnNext(StreamEvent.CreatePoint(101, 1)); right.OnNext(StreamEvent.CreatePoint(110, 1)); process.Flush(); left.OnCompleted(); right.OnCompleted(); var expected = new StreamEvent <int>[] { StreamEvent.CreatePoint(100, 1), StreamEvent.CreatePoint(100, 1), StreamEvent.CreatePunctuation <int>(101), StreamEvent.CreatePoint(101, 1), StreamEvent.CreatePoint(110, 1), StreamEvent.CreatePunctuation <int>(110), StreamEvent.CreatePunctuation <int>(StreamEvent.InfinitySyncTime), }; Assert.IsTrue(expected.SequenceEqual(actualOutput)); }
private static void JoinPointsTest(bool fixedInterval = false) { var left = new Subject <StreamEvent <string> >(); var right = new Subject <StreamEvent <string> >(); var qc = new QueryContainer(); IStreamable <Empty, string> leftInput = qc.RegisterInput(left); IStreamable <Empty, string> rightInput = qc.RegisterInput(right); if (fixedInterval) { leftInput = leftInput.AlterEventDuration(1); rightInput = rightInput.AlterEventDuration(1); } var query = leftInput.Join( rightInput, l => (l != null ? l[0].ToString() : null), r => (r != null ? r[0].ToString() : null), (l, r) => $"{l},{r}"); var output = new List <StreamEvent <string> >(); qc.RegisterOutput(query).ForEachAsync(o => output.Add(o)); var process = qc.Restore(); // Should match and egress immediately left.OnNext(StreamEvent.CreatePoint(100, "A1")); right.OnNext(StreamEvent.CreatePoint(100, "A2")); process.Flush(); var expected = new StreamEvent <string>[] { StreamEvent.CreatePoint(100, "A1,A2"), }; Assert.IsTrue(expected.SequenceEqual(output)); output.Clear(); left.OnCompleted(); right.OnCompleted(); }
private void ProcessInput( out List <OutOfOrderPartitionedStreamEvent <int, int> > diagnosticEvents, out List <PartitionedStreamEvent <int, int> > dataEvents) { var qc = new QueryContainer(); var ingress = qc.RegisterInput(this.input.ToObservable(), this.disorderPolicy, PartitionedFlushPolicy.None, PeriodicPunctuationPolicy.None(), this.lowWatermarkPolicy); var outOfOrderEvents = new List <OutOfOrderPartitionedStreamEvent <int, int> >(); ingress.GetDroppedAdjustedEventsDiagnostic().Subscribe(o => outOfOrderEvents.Add(o)); var output = new List <PartitionedStreamEvent <int, int> >(); var egress = qc.RegisterOutput(ingress).ForEachAsync(o => output.Add(o)); var process = qc.Restore(); process.Flush(); egress.Wait(); diagnosticEvents = outOfOrderEvents; dataEvents = output; }
private static void PassThrough() { // var inputStream = GetTollReadings(); // Display(inputStream); // var subscription = TollReadingEvents.ToAtemporalStreamable(); // Display(subscription); { var queryContainer = new QueryContainer(); var inputStream = queryContainer.RegisterInput( TollReadingEvents, DisorderPolicy.Drop(), FlushPolicy.FlushOnPunctuation, PeriodicPunctuationPolicy.Time(1)); var query = inputStream.HoppingWindowLifetime(TimeSpan.FromSeconds(10).Ticks, TimeSpan.FromSeconds(1).Ticks) .Count() .AlterEventDuration(1); var async = queryContainer.RegisterOutput(query); Display("1) ", 1, async); queryProcess1 = queryContainer.Restore(); } }
public void SelectManyWithFlush() { var data = new PartitionedStreamEvent <int, double>[] { PartitionedStreamEvent.CreatePoint(0, 0, 0.0), PartitionedStreamEvent.CreatePoint(0, 1, 1.0), PartitionedStreamEvent.CreatePoint(0, 2, 2.0), PartitionedStreamEvent.CreatePoint(1, 1, 1.0), PartitionedStreamEvent.CreatePoint(1, 2, 2.0), PartitionedStreamEvent.CreatePoint(1, 3, 3.0), PartitionedStreamEvent.CreatePoint(2, 2, 2.0), PartitionedStreamEvent.CreatePoint(2, 3, 3.0), PartitionedStreamEvent.CreatePoint(2, 4, 4.0), }; var j = new Subject <PartitionedStreamEvent <int, double> >(); var res = new List <Notification <PartitionedStreamEvent <int, double> > >(); var qc = new QueryContainer(); var input = qc.RegisterInput(j); var output = qc.RegisterOutput(input.SelectMany(e => new[] { e, 100.0 * e })); using (j) using (output.Subscribe(res.NotificationListObserver())) { Process p = qc.Restore(null); foreach (var x in data) { j.OnNext(x); } p.Flush(); Assert.IsTrue(res.Count > data.Length, "Flush should push all events out."); j.OnCompleted(); } }
private static IObservable <StreamEvent <Toll> > GetTollObservable(QueryContainer queryContainer) { var inputStream = queryContainer.RegisterInput(TollReadingEvents, DisorderPolicy.Drop(), FlushPolicy.FlushOnPunctuation, PeriodicPunctuationPolicy.Time(1)); var partitionedSlidingWindow1 = inputStream.AlterEventDuration((start, end) => end - start + TimeSpan.FromSeconds(2).Ticks) .HoppingWindowLifetime(TimeSpan.FromSeconds(2).Ticks, TimeSpan.FromSeconds(2).Ticks) .GroupApply( r => r.TollId, r => r.Multicast( perTollBoth => perTollBoth.Sum(e => e.Toll) .Join(perTollBoth.Count(), (sum, count) => new { Sum = sum, Count = count })), (key, result) => new Toll { TollId = key.Key, TollAmount = result.Sum, VehicleCount = result.Count }); var retval = queryContainer.RegisterOutput(partitionedSlidingWindow1); return(retval); }
public static void Test() { // This version of checkpointing works only with row-oriented mode. This restriction will eventually be relaxed. Config.ForceRowBasedExecution = true; // Subjects to feed pre- and post-checkpoint data to the query var preCheckpointSubject = new Subject <StreamEvent <StructTuple <int, int> > >(); var postCheckpointSubject = new Subject <StreamEvent <StructTuple <int, int> > >(); // Outputs of queries with and without checkpointing var outputListWithCheckpoint = new List <StructTuple <int, ulong> >(); var outputListWithoutCheckpoint = new List <StructTuple <int, ulong> >(); // Containers are an abstraction to hold queries and are the unit of checkpointing var container1 = new QueryContainer(); var container2 = new QueryContainer(); var container3 = new QueryContainer(); // Query state is written to and read from a .NET stream Stream state = new MemoryStream(); // Input data: first half of the dataset before a checkpoint is taken var preCheckpointData = Enumerable.Range(0, 10000).ToList() .ToObservable() .Select(e => StreamEvent.CreateStart(e, new StructTuple <int, int> { Item1 = e % 10, Item2 = e })); // Input data: second half of the dataset after a checkpoint is taken var postCheckpointData = Enumerable.Range(10000, 10000).ToList() .ToObservable() .Select(e => StreamEvent.CreateStart(e, new StructTuple <int, int> { Item1 = e % 10, Item2 = e })); // For comparison, we run the same query directly on the full dataset var fullData = Enumerable.Range(0, 20000).ToList() .ToObservable() .Select(e => StreamEvent.CreateStart(e, new StructTuple <int, int> { Item1 = e % 10, Item2 = e })); // Query 1: Run with first half of the dataset, then take a checkpoint var input1 = container1.RegisterInput(preCheckpointSubject); var query1 = input1.GroupApply(e => e.Item1, str => str.Sum(e => (ulong)e.Item2), (g, c) => new StructTuple <int, ulong> { Item1 = g.Key, Item2 = c }); var output1 = container1.RegisterOutput(query1); var outputAsync1 = output1.Where(e => e.IsData).Select(e => e.Payload).ForEachAsync(o => outputListWithCheckpoint.Add(o)); var pipe1 = container1.Restore(null); preCheckpointData.ForEachAsync(e => preCheckpointSubject.OnNext(e)).Wait(); preCheckpointSubject.OnNext(StreamEvent.CreatePunctuation <StructTuple <int, int> >(9999)); pipe1.Checkpoint(state); // Seek to the beginning of the stream that represents checkpointed state state.Seek(0, SeekOrigin.Begin); // Query 2: Restore the state from the saved checkpoint, and feed the second half of the dataset var input2 = container2.RegisterInput(postCheckpointSubject); var query2 = input2.GroupApply(e => e.Item1, str => str.Sum(e => (ulong)e.Item2), (g, c) => new StructTuple <int, ulong> { Item1 = g.Key, Item2 = c }); var output2 = container2.RegisterOutput(query2); var outputAsync2 = output2.Where(e => e.IsData).Select(e => e.Payload).ForEachAsync(o => outputListWithCheckpoint.Add(o)); var pipe2 = container2.Restore(state); postCheckpointData.ForEachAsync(e => postCheckpointSubject.OnNext(e)).Wait(); postCheckpointSubject.OnCompleted(); outputAsync2.Wait(); // Sort the payloads in the query result outputListWithCheckpoint.Sort((a, b) => a.Item1.CompareTo(b.Item1) == 0 ? a.Item2.CompareTo(b.Item2) : a.Item1.CompareTo(b.Item1)); // Query 3: For comparison, run the query directly on the entire dataset without any checkpoint/restore var input3 = container3.RegisterInput(fullData /*, OnCompletedPolicy.EndOfStream()*/); var query3 = input3.GroupApply(e => e.Item1, str => str.Sum(e => (ulong)e.Item2), (g, c) => new StructTuple <int, ulong> { Item1 = g.Key, Item2 = c }); var output3 = container3.RegisterOutput(query3); var outputAsync3 = output3.Where(e => e.IsData).Select(e => e.Payload).ForEachAsync(o => outputListWithoutCheckpoint.Add(o)); container3.Restore(null); // The parameter of "null" to restore causes it to run from scratch outputAsync3.Wait(); // Sort the payloads in the query result outputListWithoutCheckpoint.Sort((a, b) => a.Item1.CompareTo(b.Item1) == 0 ? a.Item2.CompareTo(b.Item2) : a.Item1.CompareTo(b.Item1)); // Perform a comparison of the checkpoint/restore query result and the result of the original query run directly on the entire dataset if (outputListWithCheckpoint.SequenceEqual(outputListWithoutCheckpoint)) { Console.WriteLine("SUCCESS: Output of query with checkpoint/restore matched output of uninterrupted query"); } else { Console.WriteLine("ERROR: Output of query with checkpoint/restore did not match the output of uninterrupted query"); } Console.ReadLine(); }
public void LOJ1Row() { var container = new QueryContainer(null); var left = new StreamEvent <MyData>[] { StreamEvent.CreatePoint(10, new MyData { field1 = 1, field2 = "A" }), StreamEvent.CreatePoint(10, new MyData { field1 = 1, field2 = "B" }), StreamEvent.CreatePoint(10, new MyData { field1 = 2, field2 = "D" }), StreamEvent.CreatePoint(10, new MyData { field1 = 2, field2 = "E" }) }; var right = new StreamEvent <MyData2>[] { StreamEvent.CreatePoint(10, new MyData2 { field3 = 1, field4 = "W" }), StreamEvent.CreatePoint(10, new MyData2 { field3 = 1, field4 = "X" }), StreamEvent.CreatePoint(10, new MyData2 { field3 = 2, field4 = "Y" }) }; var output = new List <StreamEvent <MyData3> >(); var expected = new StreamEvent <MyData3>[] { StreamEvent.CreatePoint(10, new MyData3 { field1 = 2, field2 = "E", field3 = -1, field4 = "null" }), StreamEvent.CreatePoint(10, new MyData3 { field1 = 1, field2 = "B", field3 = 1, field4 = "W" }), StreamEvent.CreatePoint(10, new MyData3 { field1 = 1, field2 = "A", field3 = 1, field4 = "W" }), StreamEvent.CreatePoint(10, new MyData3 { field1 = 1, field2 = "B", field3 = 1, field4 = "X" }), StreamEvent.CreatePoint(10, new MyData3 { field1 = 1, field2 = "A", field3 = 1, field4 = "X" }), StreamEvent.CreatePoint(10, new MyData3 { field1 = 2, field2 = "D", field3 = 2, field4 = "Y" }), }; var leftStream = container.RegisterInput(left.ToObservable()); var rightStream = container.RegisterInput(right.ToObservable()); int tmp1 = -1; string tmp2 = "null"; var query = leftStream.LeftOuterJoin(rightStream, e => e.field1, e => e.field3, (l, r) => l.field2 != "E", (l) => new MyData3 { field1 = l.field1, field2 = l.field2, field3 = tmp1, field4 = tmp2 }, (l, r) => new MyData3 { field1 = l.field1, field2 = l.field2, field3 = r.field3, field4 = r.field4 }); var result = container.RegisterOutput(query, ReshapingPolicy.CoalesceEndEdges).Where(e => e.IsData); var resultAsync = result.ForEachAsync(o => output.Add(o)); container.Restore(null); // start the query Assert.IsTrue(output.ToArray().SequenceEqual(expected)); }