From 727f4aa8f45edbf5699b2563f36a465f5815dd2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Borowiecki?= Date: Thu, 27 Sep 2018 21:10:44 +0100 Subject: [PATCH] KAFKA-6605 fix NPE in Flatten when optional Struct is null --- .../kafka/connect/transforms/Flatten.java | 29 +++++++++----- .../kafka/connect/transforms/FlattenTest.java | 40 +++++++++++++++++++ 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/connect/transforms/src/main/java/org/apache/kafka/connect/transforms/Flatten.java b/connect/transforms/src/main/java/org/apache/kafka/connect/transforms/Flatten.java index c5e4000975324..d7d21445d73d8 100644 --- a/connect/transforms/src/main/java/org/apache/kafka/connect/transforms/Flatten.java +++ b/connect/transforms/src/main/java/org/apache/kafka/connect/transforms/Flatten.java @@ -35,7 +35,7 @@ import java.util.Map; import static org.apache.kafka.connect.transforms.util.Requirements.requireMap; -import static org.apache.kafka.connect.transforms.util.Requirements.requireStruct; +import static org.apache.kafka.connect.transforms.util.Requirements.requireStructOrNull; public abstract class Flatten> implements Transformation { @@ -136,20 +136,24 @@ private void applySchemaless(Map originalRecord, String fieldNam } private R applyWithSchema(R record) { - final Struct value = requireStruct(operatingValue(record), PURPOSE); + final Struct value = requireStructOrNull(operatingValue(record), PURPOSE); - Schema updatedSchema = schemaUpdateCache.get(value.schema()); + Schema schema = operatingSchema(record); + Schema updatedSchema = schemaUpdateCache.get(schema); if (updatedSchema == null) { - final SchemaBuilder builder = SchemaUtil.copySchemaBasics(value.schema(), SchemaBuilder.struct()); - Struct defaultValue = (Struct) value.schema().defaultValue(); - buildUpdatedSchema(value.schema(), "", builder, value.schema().isOptional(), defaultValue); + final SchemaBuilder builder = SchemaUtil.copySchemaBasics(schema, SchemaBuilder.struct()); + Struct defaultValue = (Struct) schema.defaultValue(); + buildUpdatedSchema(schema, "", builder, schema.isOptional(), defaultValue); updatedSchema = builder.build(); - schemaUpdateCache.put(value.schema(), updatedSchema); + schemaUpdateCache.put(schema, updatedSchema); + } + if (value == null) { + return newRecord(record, updatedSchema, null); + } else { + final Struct updatedValue = new Struct(updatedSchema); + buildWithSchema(value, "", updatedValue); + return newRecord(record, updatedSchema, updatedValue); } - - final Struct updatedValue = new Struct(updatedSchema); - buildWithSchema(value, "", updatedValue); - return newRecord(record, updatedSchema, updatedValue); } /** @@ -216,6 +220,9 @@ private Schema convertFieldSchema(Schema orig, boolean optional, Object defaultF } private void buildWithSchema(Struct record, String fieldNamePrefix, Struct newRecord) { + if (record == null) { + return; + } for (Field field : record.schema().fields()) { final String fieldName = fieldName(fieldNamePrefix, field.name()); switch (field.schema().type()) { diff --git a/connect/transforms/src/test/java/org/apache/kafka/connect/transforms/FlattenTest.java b/connect/transforms/src/test/java/org/apache/kafka/connect/transforms/FlattenTest.java index d709054dbe043..430bba6f64022 100644 --- a/connect/transforms/src/test/java/org/apache/kafka/connect/transforms/FlattenTest.java +++ b/connect/transforms/src/test/java/org/apache/kafka/connect/transforms/FlattenTest.java @@ -181,6 +181,46 @@ public void testOptionalFieldStruct() { assertNull(transformedStruct.get("B.opt_int32")); } + @Test + public void testOptionalStruct() { + xformValue.configure(Collections.emptyMap()); + + SchemaBuilder builder = SchemaBuilder.struct().optional(); + builder.field("opt_int32", Schema.OPTIONAL_INT32_SCHEMA); + Schema schema = builder.build(); + + SourceRecord transformed = xformValue.apply(new SourceRecord(null, null, + "topic", 0, + schema, null)); + + assertEquals(Schema.Type.STRUCT, transformed.valueSchema().type()); + assertNull(transformed.value()); + } + + @Test + public void testOptionalNestedStruct() { + xformValue.configure(Collections.emptyMap()); + + SchemaBuilder builder = SchemaBuilder.struct().optional(); + builder.field("opt_int32", Schema.OPTIONAL_INT32_SCHEMA); + Schema supportedTypesSchema = builder.build(); + + builder = SchemaBuilder.struct(); + builder.field("B", supportedTypesSchema); + Schema oneLevelNestedSchema = builder.build(); + + Struct oneLevelNestedStruct = new Struct(oneLevelNestedSchema); + oneLevelNestedStruct.put("B", null); + + SourceRecord transformed = xformValue.apply(new SourceRecord(null, null, + "topic", 0, + oneLevelNestedSchema, oneLevelNestedStruct)); + + assertEquals(Schema.Type.STRUCT, transformed.valueSchema().type()); + Struct transformedStruct = (Struct) transformed.value(); + assertNull(transformedStruct.get("B.opt_int32")); + } + @Test public void testOptionalFieldMap() { xformValue.configure(Collections.emptyMap());