diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableFilter.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableFilter.java index 080fd9d52a111..ff0c67f37b86d 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableFilter.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableFilter.java @@ -77,6 +77,9 @@ public void process(K key, Change change) { V newValue = computeValue(key, change.newValue); V oldValue = sendOldValues ? computeValue(key, change.oldValue) : null; + if (sendOldValues && oldValue == null && newValue == null) + return; // unnecessary to forward here. + context().forward(key, new Change<>(newValue, oldValue)); } diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableFilterTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableFilterTest.java index a3af133d3b0fc..e328baeb53422 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableFilterTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableFilterTest.java @@ -24,6 +24,8 @@ import org.apache.kafka.streams.kstream.Predicate; import org.apache.kafka.test.KStreamTestDriver; import org.apache.kafka.test.MockProcessorSupplier; +import org.apache.kafka.test.MockReducer; +import org.apache.kafka.test.MockKeyValueMapper; import org.apache.kafka.test.TestUtils; import org.junit.After; import org.junit.Before; @@ -254,7 +256,7 @@ public boolean test(String key, Integer value) { driver.process(topic1, "C", 1); proc1.checkAndClearProcessResult("A:(1<-null)", "B:(1<-null)", "C:(1<-null)"); - proc2.checkAndClearProcessResult("A:(null<-null)", "B:(null<-null)", "C:(null<-null)"); + proc2.checkEmptyAndClearProcessResult(); driver.process(topic1, "A", 2); driver.process(topic1, "B", 2); @@ -271,7 +273,40 @@ public boolean test(String key, Integer value) { driver.process(topic1, "B", null); proc1.checkAndClearProcessResult("A:(null<-3)", "B:(null<-2)"); - proc2.checkAndClearProcessResult("A:(null<-null)", "B:(null<-2)"); + proc2.checkAndClearProcessResult("B:(null<-2)"); } + @Test + public void testSkipNullOnMaterialization() throws IOException { + // Do not explicitly set enableSendingOldValues. Let a further downstream stateful operator trigger it instead. + KStreamBuilder builder = new KStreamBuilder(); + + String topic1 = "topic1"; + + KTableImpl table1 = + (KTableImpl) builder.table(stringSerde, stringSerde, topic1); + KTableImpl table2 = (KTableImpl) table1.filter( + new Predicate() { + @Override + public boolean test(String key, String value) { + return value.equalsIgnoreCase("accept"); + } + }).groupBy(MockKeyValueMapper.NoOpKeyValueMapper()) + .reduce(MockReducer.STRING_ADDER, MockReducer.STRING_REMOVER, "mock-result"); + + MockProcessorSupplier proc1 = new MockProcessorSupplier<>(); + MockProcessorSupplier proc2 = new MockProcessorSupplier<>(); + + builder.addProcessor("proc1", proc1, table1.name); + builder.addProcessor("proc2", proc2, table2.name); + + driver = new KStreamTestDriver(builder, stateDir, stringSerde, stringSerde); + + driver.process(topic1, "A", "reject"); + driver.process(topic1, "B", "reject"); + driver.process(topic1, "C", "reject"); + + proc1.checkAndClearProcessResult("A:(reject<-null)", "B:(reject<-null)", "C:(reject<-null)"); + proc2.checkEmptyAndClearProcessResult(); + } } diff --git a/streams/src/test/java/org/apache/kafka/test/MockProcessorSupplier.java b/streams/src/test/java/org/apache/kafka/test/MockProcessorSupplier.java index 9cf0eb264d83d..67d25f5e563e4 100644 --- a/streams/src/test/java/org/apache/kafka/test/MockProcessorSupplier.java +++ b/streams/src/test/java/org/apache/kafka/test/MockProcessorSupplier.java @@ -82,6 +82,12 @@ public void checkAndClearProcessResult(String... expected) { processed.clear(); } + public void checkEmptyAndClearProcessResult() { + + assertEquals("the number of outputs:", 0, processed.size()); + processed.clear(); + } + public void checkAndClearPunctuateResult(long... expected) { assertEquals("the number of outputs:", expected.length, punctuated.size());