From 8e86df135aeebc0c4c8dad582107da3ecea82617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alan=20Artigao=20Carre=C3=B1o?= Date: Thu, 20 May 2021 15:01:58 +0200 Subject: [PATCH] Fix fingerprint method for null default values (#733) * Fix default toString for JsonProperties.NULL_VALUE The normalization of the Schema is inconsistent across restarts due to object's JsonProperties.NULL_VALUE lack of override toString. This fixes that and allows deterministic creation for fingerprints. * Assert that fingerprint hash remains the same across executions Added test case to assess that generated fingerprint is always the same between multiple executions, to prevent using an invalid stringified version of Object. Same Schema string must return same fingerprint, at least as long as normalize function is not changed. Forced MD5, just in case in future releases, the default hashing algorithm changes. --- .../avro/AvroSchemaProvider.java | 7 ++- .../avro/AvroSchemaProviderTest.java | 44 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 schema-registry/common/src/test/java/com/hortonworks/registries/schemaregistry/avro/AvroSchemaProviderTest.java diff --git a/schema-registry/common/src/main/java/com/hortonworks/registries/schemaregistry/avro/AvroSchemaProvider.java b/schema-registry/common/src/main/java/com/hortonworks/registries/schemaregistry/avro/AvroSchemaProvider.java index 510c6a6af..1912b481a 100644 --- a/schema-registry/common/src/main/java/com/hortonworks/registries/schemaregistry/avro/AvroSchemaProvider.java +++ b/schema-registry/common/src/main/java/com/hortonworks/registries/schemaregistry/avro/AvroSchemaProvider.java @@ -22,6 +22,7 @@ import com.hortonworks.registries.schemaregistry.SchemaFieldInfo; import com.hortonworks.registries.schemaregistry.errors.InvalidSchemaException; import com.hortonworks.registries.schemaregistry.errors.SchemaNotFoundException; +import org.apache.avro.JsonProperties; import org.apache.avro.Schema; import java.io.IOException; @@ -180,7 +181,11 @@ private static Appendable build(Map env, // handle default value Object defaultValue = field.defaultVal(); if (defaultValue != null) { - appendable.append(defaultValue.toString()); + if (defaultValue == JsonProperties.NULL_VALUE) { + appendable.append("null"); + } else { + appendable.append(defaultValue.toString()); + } } build(env, field.schema(), appendable).append("}"); diff --git a/schema-registry/common/src/test/java/com/hortonworks/registries/schemaregistry/avro/AvroSchemaProviderTest.java b/schema-registry/common/src/test/java/com/hortonworks/registries/schemaregistry/avro/AvroSchemaProviderTest.java new file mode 100644 index 000000000..d85aadaab --- /dev/null +++ b/schema-registry/common/src/test/java/com/hortonworks/registries/schemaregistry/avro/AvroSchemaProviderTest.java @@ -0,0 +1,44 @@ +package com.hortonworks.registries.schemaregistry.avro; + +import com.hortonworks.registries.schemaregistry.SchemaProvider; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; + +import static org.hamcrest.core.IsEqual.equalTo; + +public class AvroSchemaProviderTest { + + private String schemaWithNullDefaults = "{\n" + + " \"type\" : \"record\",\n" + + " \"namespace\" : \"com.hortonworks.registries\",\n" + + " \"name\" : \"trucks\",\n" + + " \"fields\" : [\n" + + " { \"name\" : \"driverId\" , \"type\" : [\"null\", \"int\"], \"default\": null }" + + " ]\n" + + "}\n"; + + @Test + public void testGetFingerprintNullTypeDeterminism() throws Exception { + AvroSchemaProvider avroSchemaProvider = new AvroSchemaProvider(); + avroSchemaProvider.init(Collections.singletonMap(SchemaProvider.HASH_FUNCTION_CONFIG, "MD5")); + + String withNullDefaultsFingerprintHex = bytesToHex(avroSchemaProvider.getFingerprint(schemaWithNullDefaults)); + + Assert.assertThat(withNullDefaultsFingerprintHex, equalTo("e1e17a3aef8c728c131204bbf49046b2")); + } + + private static String bytesToHex(byte[] hash) { + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } + +}