From 9f1c1ccaa03d2bdab9b3a5a7e4d4fb07a52b44ba Mon Sep 17 00:00:00 2001 From: Martijn Visser <2989614+MartijnVisser@users.noreply.github.com> Date: Fri, 3 Jul 2026 22:16:24 +0200 Subject: [PATCH 1/4] [FLINK-33045][formats/avro-confluent] Bump Confluent Schema Registry client to 7.9.8 and test images to cp-8.3.0 --- flink-formats/flink-avro-confluent-registry/pom.xml | 2 +- .../src/main/resources/META-INF/NOTICE | 9 +++++---- .../java/org/apache/flink/util/DockerImageVersions.java | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/flink-formats/flink-avro-confluent-registry/pom.xml b/flink-formats/flink-avro-confluent-registry/pom.xml index e8e68a15d6d99a..5fcc9eda3c3120 100644 --- a/flink-formats/flink-avro-confluent-registry/pom.xml +++ b/flink-formats/flink-avro-confluent-registry/pom.xml @@ -32,7 +32,7 @@ under the License. Flink : Formats : Avro confluent registry - 7.5.3 + 7.9.8 diff --git a/flink-formats/flink-sql-avro-confluent-registry/src/main/resources/META-INF/NOTICE b/flink-formats/flink-sql-avro-confluent-registry/src/main/resources/META-INF/NOTICE index b57a307e30c04f..c8ba6f6c2063c3 100644 --- a/flink-formats/flink-sql-avro-confluent-registry/src/main/resources/META-INF/NOTICE +++ b/flink-formats/flink-sql-avro-confluent-registry/src/main/resources/META-INF/NOTICE @@ -9,18 +9,19 @@ This project bundles the following dependencies under the Apache Software Licens - com.fasterxml.jackson.core:jackson-annotations:2.22 - com.fasterxml.jackson.core:jackson-core:2.22.2 - com.fasterxml.jackson.core:jackson-databind:2.22.2 +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.22.2 - com.google.guava:guava:32.0.1-jre - commons-io:commons-io:2.15.1 -- io.confluent:common-utils:7.5.3 -- io.confluent:kafka-schema-registry-client:7.5.3 +- io.confluent:common-utils:7.9.8 +- io.confluent:kafka-schema-registry-client:7.9.8 - org.apache.avro:avro:1.11.5 - org.apache.commons:commons-compress:1.26.0 - org.apache.commons:commons-lang3:3.18.0 -- org.apache.kafka:kafka-clients:7.5.3-ccs +- org.apache.kafka:kafka-clients:7.9.8-ccs - org.xerial.snappy:snappy-java:1.1.10.7 - org.yaml:snakeyaml:2.3 This project bundles the following dependencies under the BSD license. See bundled license files for details. -- com.github.luben:zstd-jni:1.5.5-1 +- com.github.luben:zstd-jni:1.5.6-4 diff --git a/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/util/DockerImageVersions.java b/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/util/DockerImageVersions.java index 655f4e3b4666ab..fa8a82913b135b 100644 --- a/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/util/DockerImageVersions.java +++ b/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/util/DockerImageVersions.java @@ -28,9 +28,9 @@ */ public class DockerImageVersions { - public static final String KAFKA = "confluentinc/cp-kafka:7.5.3"; + public static final String KAFKA = "confluentinc/cp-kafka:8.3.0"; - public static final String SCHEMA_REGISTRY = "confluentinc/cp-schema-registry:7.5.3"; + public static final String SCHEMA_REGISTRY = "confluentinc/cp-schema-registry:8.3.0"; public static final String KINESALITE = "instructure/kinesalite:latest"; From f129d9d061e5410bb52862c3c576e3d6bb45cf94 Mon Sep 17 00:00:00 2001 From: Martijn Visser <2989614+MartijnVisser@users.noreply.github.com> Date: Fri, 3 Jul 2026 22:16:24 +0200 Subject: [PATCH 2/4] [FLINK-33045][formats/avro-confluent] Add auto.register.schemas option to allow disabling schema auto-registration --- .../confluent/AvroConfluentFormatOptions.java | 11 ++ .../confluent/CachedSchemaCoderProvider.java | 3 +- .../ConfluentSchemaRegistryCoder.java | 91 ++++++++++++-- .../confluent/RegistryAvroFormatFactory.java | 8 +- .../debezium/DebeziumAvroFormatFactory.java | 2 + .../ConfluentSchemaRegistryCoderTest.java | 112 ++++++++++++++++++ .../RegistryAvroFormatFactoryTest.java | 88 ++++++++++++++ .../DebeziumAvroFormatFactoryTest.java | 25 ++++ 8 files changed, 325 insertions(+), 15 deletions(-) diff --git a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/AvroConfluentFormatOptions.java b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/AvroConfluentFormatOptions.java index 42e7353d009ba4..8a185d2e24eb0f 100644 --- a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/AvroConfluentFormatOptions.java +++ b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/AvroConfluentFormatOptions.java @@ -65,6 +65,17 @@ public class AvroConfluentFormatOptions { // Commonly used options maintained by Flink for convenience // -------------------------------------------------------------------------------------------- + public static final ConfigOption AUTO_REGISTER_SCHEMAS = + ConfigOptions.key("auto.register.schemas") + .booleanType() + .defaultValue(true) + .withDescription( + "Flag to indicate if the schema should be registered automatically during serialization. " + + "When set to false, an identical schema must have been registered in the Schema Registry " + + "outside of Flink before it can be used; only its id is looked up. " + + "Schema registration only happens when writing data; reading always looks up " + + "the schema by the id embedded in each record. The default value is true."); + public static final ConfigOption SSL_KEYSTORE_LOCATION = ConfigOptions.key("ssl.keystore.location") .stringType() diff --git a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/CachedSchemaCoderProvider.java b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/CachedSchemaCoderProvider.java index 53be09c7475a6c..d8a59846f69c3e 100644 --- a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/CachedSchemaCoderProvider.java +++ b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/CachedSchemaCoderProvider.java @@ -59,7 +59,8 @@ class CachedSchemaCoderProvider implements SchemaCoder.SchemaCoderProvider { public SchemaCoder get() { return new ConfluentSchemaRegistryCoder( this.subject, - new CachedSchemaRegistryClient(url, identityMapCapacity, registryConfigs)); + new CachedSchemaRegistryClient(url, identityMapCapacity, registryConfigs), + registryConfigs); } @Override diff --git a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/ConfluentSchemaRegistryCoder.java b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/ConfluentSchemaRegistryCoder.java index 7be31453148356..3bf2a7a0aec9d0 100644 --- a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/ConfluentSchemaRegistryCoder.java +++ b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/ConfluentSchemaRegistryCoder.java @@ -24,41 +24,87 @@ import io.confluent.kafka.schemaregistry.client.rest.exceptions.RestClientException; import org.apache.avro.Schema; +import javax.annotation.Nullable; + import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.util.Map; import static java.lang.String.format; /** Reads and Writes schema using Confluent Schema Registry protocol. */ public class ConfluentSchemaRegistryCoder implements SchemaCoder { + private static final String AUTO_REGISTER_SCHEMAS_CONFIG = "auto.register.schemas"; + private final SchemaRegistryClient schemaRegistryClient; + private final boolean autoRegisterSchemas; private String subject; private static final int CONFLUENT_MAGIC_BYTE = 0; /** - * Creates {@link SchemaCoder} that uses provided {@link SchemaRegistryClient} to connect to + * Creates {@link SchemaCoder} that uses provided {@link SchemaRegistryClient} to connect to the * schema registry. * - * @param schemaRegistryClient client to connect schema registry * @param subject subject of schema registry to produce + * @param schemaRegistryClient client to connect to the schema registry + * @param registryConfigs map with additional schema registry configs. Supports {@code + * auto.register.schemas} to control whether schemas are registered on write (default) or + * only looked up */ - public ConfluentSchemaRegistryCoder(String subject, SchemaRegistryClient schemaRegistryClient) { + public ConfluentSchemaRegistryCoder( + String subject, + SchemaRegistryClient schemaRegistryClient, + @Nullable Map registryConfigs) { this.schemaRegistryClient = schemaRegistryClient; this.subject = subject; + this.autoRegisterSchemas = parseAutoRegisterSchemas(registryConfigs); } /** - * Creates {@link SchemaCoder} that uses provided {@link SchemaRegistryClient} to connect to + * Creates {@link SchemaCoder} that uses provided {@link SchemaRegistryClient} to connect to the * schema registry. * - * @param schemaRegistryClient client to connect schema registry + * @param subject subject of schema registry to produce + * @param schemaRegistryClient client to connect to the schema registry + */ + public ConfluentSchemaRegistryCoder(String subject, SchemaRegistryClient schemaRegistryClient) { + this(subject, schemaRegistryClient, null); + } + + /** + * Creates {@link SchemaCoder} that uses provided {@link SchemaRegistryClient} to connect to the + * schema registry. + * + * @param schemaRegistryClient client to connect to the schema registry */ public ConfluentSchemaRegistryCoder(SchemaRegistryClient schemaRegistryClient) { - this.schemaRegistryClient = schemaRegistryClient; + this(null, schemaRegistryClient, null); + } + + private static boolean parseAutoRegisterSchemas(@Nullable Map registryConfigs) { + Object value = + registryConfigs == null ? null : registryConfigs.get(AUTO_REGISTER_SCHEMAS_CONFIG); + if (value == null) { + return true; + } + if (value instanceof Boolean) { + return (Boolean) value; + } + String stringValue = value.toString(); + if ("true".equalsIgnoreCase(stringValue)) { + return true; + } + if ("false".equalsIgnoreCase(stringValue)) { + return false; + } + throw new IllegalArgumentException( + format( + "Invalid value '%s' for configuration '%s'. Supported values are 'true' or 'false'.", + value, AUTO_REGISTER_SCHEMAS_CONFIG)); } @Override @@ -81,13 +127,32 @@ public Schema readSchema(InputStream in) throws IOException { @Override public void writeSchema(Schema schema, OutputStream out) throws IOException { - try { - int registeredId = schemaRegistryClient.register(subject, schema); - out.write(CONFLUENT_MAGIC_BYTE); - byte[] schemaIdBytes = ByteBuffer.allocate(4).putInt(registeredId).array(); - out.write(schemaIdBytes); - } catch (RestClientException e) { - throw new IOException("Could not register schema in registry", e); + int registeredId; + if (autoRegisterSchemas) { + try { + registeredId = schemaRegistryClient.register(subject, schema); + } catch (RestClientException e) { + throw new IOException( + format( + "Could not register schema %s under subject %s in registry", + schema.getFullName(), subject), + e); + } + } else { + try { + registeredId = schemaRegistryClient.getId(subject, schema); + } catch (RestClientException | IOException e) { + throw new IOException( + format( + "Could not retrieve id for schema %s under subject %s from registry. " + + "Since '%s' is 'false', an identical schema must be registered " + + "under this subject before it can be used", + schema.getFullName(), subject, AUTO_REGISTER_SCHEMAS_CONFIG), + e); + } } + out.write(CONFLUENT_MAGIC_BYTE); + byte[] schemaIdBytes = ByteBuffer.allocate(4).putInt(registeredId).array(); + out.write(schemaIdBytes); } } diff --git a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/RegistryAvroFormatFactory.java b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/RegistryAvroFormatFactory.java index e43d5a76e06817..806ec45ff36990 100644 --- a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/RegistryAvroFormatFactory.java +++ b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/RegistryAvroFormatFactory.java @@ -60,6 +60,7 @@ import java.util.stream.Stream; import static java.lang.String.format; +import static org.apache.flink.formats.avro.registry.confluent.AvroConfluentFormatOptions.AUTO_REGISTER_SCHEMAS; import static org.apache.flink.formats.avro.registry.confluent.AvroConfluentFormatOptions.BASIC_AUTH_CREDENTIALS_SOURCE; import static org.apache.flink.formats.avro.registry.confluent.AvroConfluentFormatOptions.BASIC_AUTH_USER_INFO; import static org.apache.flink.formats.avro.registry.confluent.AvroConfluentFormatOptions.BEARER_AUTH_CREDENTIALS_SOURCE; @@ -186,6 +187,7 @@ public Set> optionalOptions() { options.add(BASIC_AUTH_USER_INFO); options.add(BEARER_AUTH_CREDENTIALS_SOURCE); options.add(BEARER_AUTH_TOKEN); + options.add(AUTO_REGISTER_SCHEMAS); return options; } @@ -203,7 +205,8 @@ public Set> forwardOptions() { BASIC_AUTH_CREDENTIALS_SOURCE, BASIC_AUTH_USER_INFO, BEARER_AUTH_CREDENTIALS_SOURCE, - BEARER_AUTH_TOKEN) + BEARER_AUTH_TOKEN, + AUTO_REGISTER_SCHEMAS) .collect(Collectors.toSet()); } @@ -237,6 +240,9 @@ public Set> forwardOptions() { formatOptions .getOptional(BEARER_AUTH_TOKEN) .ifPresent(v -> properties.put("bearer.auth.token", v)); + formatOptions + .getOptional(AUTO_REGISTER_SCHEMAS) + .ifPresent(v -> properties.put("auto.register.schemas", Boolean.toString(v))); if (properties.isEmpty()) { return null; diff --git a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroFormatFactory.java b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroFormatFactory.java index 5925f23f763250..aab877a2471e95 100644 --- a/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroFormatFactory.java +++ b/flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroFormatFactory.java @@ -51,6 +51,7 @@ import java.util.Set; import static java.lang.String.format; +import static org.apache.flink.formats.avro.registry.confluent.AvroConfluentFormatOptions.AUTO_REGISTER_SCHEMAS; import static org.apache.flink.formats.avro.registry.confluent.AvroConfluentFormatOptions.BASIC_AUTH_CREDENTIALS_SOURCE; import static org.apache.flink.formats.avro.registry.confluent.AvroConfluentFormatOptions.BASIC_AUTH_USER_INFO; import static org.apache.flink.formats.avro.registry.confluent.AvroConfluentFormatOptions.BEARER_AUTH_CREDENTIALS_SOURCE; @@ -178,6 +179,7 @@ public Set> optionalOptions() { options.add(BASIC_AUTH_USER_INFO); options.add(BEARER_AUTH_CREDENTIALS_SOURCE); options.add(BEARER_AUTH_TOKEN); + options.add(AUTO_REGISTER_SCHEMAS); return options; } diff --git a/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/ConfluentSchemaRegistryCoderTest.java b/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/ConfluentSchemaRegistryCoderTest.java index 2c93348b002a5a..0fc08a63611903 100644 --- a/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/ConfluentSchemaRegistryCoderTest.java +++ b/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/ConfluentSchemaRegistryCoderTest.java @@ -19,15 +19,20 @@ package org.apache.flink.formats.avro.registry.confluent; import io.confluent.kafka.schemaregistry.client.MockSchemaRegistryClient; +import io.confluent.kafka.schemaregistry.client.rest.exceptions.RestClientException; import org.apache.avro.Schema; import org.apache.avro.SchemaBuilder; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -56,6 +61,106 @@ void testSpecificRecordWithConfluentSchemaRegistry() throws Exception { assertThat(byteInStream).isEmpty(); } + @Test + void testWriteSchemaRegistersSchemaByDefault() throws Exception { + MockSchemaRegistryClient client = new MockSchemaRegistryClient(); + Schema schema = + SchemaBuilder.record("testRecord").fields().optionalString("testField").endRecord(); + + ConfluentSchemaRegistryCoder coder = + new ConfluentSchemaRegistryCoder("testSubject", client); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + coder.writeSchema(schema, out); + + assertThat(client.getAllSubjects()).contains("testSubject"); + assertWrittenId(out, client.getId("testSubject", schema)); + } + + @ParameterizedTest + @ValueSource(strings = {"false", "FALSE"}) + void testWriteSchemaWithAutoRegisterDisabledLooksUpExistingId(String configValue) + throws Exception { + MockSchemaRegistryClient client = new MockSchemaRegistryClient(); + Schema schema = + SchemaBuilder.record("testRecord").fields().optionalString("testField").endRecord(); + int schemaId = client.register("testSubject", schema); + + ConfluentSchemaRegistryCoder coder = + new ConfluentSchemaRegistryCoder( + "testSubject", client, singletonMap("auto.register.schemas", configValue)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + coder.writeSchema(schema, out); + + assertWrittenId(out, schemaId); + } + + @Test + void testWriteSchemaWithAutoRegisterDisabledDoesNotRegisterUnknownSchema() throws Exception { + MockSchemaRegistryClient client = new MockSchemaRegistryClient(); + Schema schema = + SchemaBuilder.record("testRecord").fields().optionalString("testField").endRecord(); + + ConfluentSchemaRegistryCoder coder = + new ConfluentSchemaRegistryCoder( + "unknownSubject", client, singletonMap("auto.register.schemas", "false")); + + assertThatThrownBy(() -> coder.writeSchema(schema, new ByteArrayOutputStream())) + .isInstanceOf(IOException.class) + .hasMessageContaining("unknownSubject"); + // the failed lookup must not have registered anything under the subject + assertThatThrownBy(() -> client.getAllVersions("unknownSubject")) + .isInstanceOf(RestClientException.class) + .hasMessageContaining("Subject Not Found"); + } + + @Test + void testWriteSchemaWithAutoRegisterDisabledViaBooleanConfig() throws Exception { + // DataStream API users can pass Boolean values instead of Strings in the registry configs + MockSchemaRegistryClient client = new MockSchemaRegistryClient(); + Schema schema = + SchemaBuilder.record("testRecord").fields().optionalString("testField").endRecord(); + int schemaId = client.register("testSubject", schema); + + ConfluentSchemaRegistryCoder coder = + new ConfluentSchemaRegistryCoder( + "testSubject", + client, + singletonMap("auto.register.schemas", Boolean.FALSE)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + coder.writeSchema(schema, out); + + assertWrittenId(out, schemaId); + } + + @Test + void testWriteSchemaWithAutoRegisterEnabledViaBooleanConfig() throws Exception { + MockSchemaRegistryClient client = new MockSchemaRegistryClient(); + Schema schema = + SchemaBuilder.record("testRecord").fields().optionalString("testField").endRecord(); + + ConfluentSchemaRegistryCoder coder = + new ConfluentSchemaRegistryCoder( + "testSubject", client, singletonMap("auto.register.schemas", Boolean.TRUE)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + coder.writeSchema(schema, out); + + assertThat(client.getAllSubjects()).contains("testSubject"); + } + + @Test + void testInvalidAutoRegisterSchemasValueIsRejected() { + MockSchemaRegistryClient client = new MockSchemaRegistryClient(); + + assertThatThrownBy( + () -> + new ConfluentSchemaRegistryCoder( + "testSubject", + client, + singletonMap("auto.register.schemas", "enabled"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("auto.register.schemas"); + } + @Test void testMagicByteVerification() throws Exception { MockSchemaRegistryClient client = new MockSchemaRegistryClient(); @@ -74,4 +179,11 @@ void testMagicByteVerification() throws Exception { .isInstanceOf(IOException.class); } } + + private static void assertWrittenId(ByteArrayOutputStream out, int expectedId) + throws IOException { + DataInputStream in = new DataInputStream(new ByteArrayInputStream(out.toByteArray())); + assertThat(in.readByte()).isEqualTo((byte) 0); + assertThat(in.readInt()).isEqualTo(expectedId); + } } diff --git a/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/RegistryAvroFormatFactoryTest.java b/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/RegistryAvroFormatFactoryTest.java index 6ace72b199ed87..08290c18b0ccf9 100644 --- a/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/RegistryAvroFormatFactoryTest.java +++ b/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/RegistryAvroFormatFactoryTest.java @@ -39,6 +39,8 @@ import org.apache.avro.Schema.Parser; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.util.HashMap; import java.util.Map; @@ -229,6 +231,92 @@ public void testSerializationSchemaWithInvalidOptionalSchema() { null, SCHEMA.toPhysicalRowDataType())); } + @ParameterizedTest + @ValueSource(strings = {"true", "false"}) + void testSeDeSchemaWithAutoRegisterSchemas(String autoRegisterSchemas) { + final Map expectedProperties = new HashMap<>(); + expectedProperties.put("auto.register.schemas", autoRegisterSchemas); + + final Map options = + getModifiedOptions( + opts -> + opts.put( + "avro-confluent.auto.register.schemas", + autoRegisterSchemas)); + + final AvroRowDataDeserializationSchema expectedDeser = + new AvroRowDataDeserializationSchema( + ConfluentRegistryAvroDeserializationSchema.forGeneric( + AvroSchemaConverter.convertToSchema(ROW_TYPE), + REGISTRY_URL, + expectedProperties), + AvroToRowDataConverters.createRowConverter(ROW_TYPE), + InternalTypeInfo.of(ROW_TYPE)); + + final DynamicTableSource actualSource = createTableSource(SCHEMA, options); + assertThat(actualSource).isInstanceOf(TestDynamicTableFactory.DynamicTableSourceMock.class); + TestDynamicTableFactory.DynamicTableSourceMock scanSourceMock = + (TestDynamicTableFactory.DynamicTableSourceMock) actualSource; + + DeserializationSchema actualDeser = + scanSourceMock.valueFormat.createRuntimeDecoder( + ScanRuntimeProviderContext.INSTANCE, SCHEMA.toPhysicalRowDataType()); + assertThat(actualDeser).isEqualTo(expectedDeser); + + final AvroRowDataSerializationSchema expectedSer = + new AvroRowDataSerializationSchema( + ROW_TYPE, + ConfluentRegistryAvroSerializationSchema.forGeneric( + SUBJECT, + AvroSchemaConverter.convertToSchema(ROW_TYPE), + REGISTRY_URL, + expectedProperties), + RowDataToAvroConverters.createConverter(ROW_TYPE)); + + final DynamicTableSink actualSink = createTableSink(SCHEMA, options); + assertThat(actualSink).isInstanceOf(TestDynamicTableFactory.DynamicTableSinkMock.class); + TestDynamicTableFactory.DynamicTableSinkMock sinkMock = + (TestDynamicTableFactory.DynamicTableSinkMock) actualSink; + + SerializationSchema actualSer = + sinkMock.valueFormat.createRuntimeEncoder(null, SCHEMA.toPhysicalRowDataType()); + assertThat(actualSer).isEqualTo(expectedSer); + } + + @Test + void testAutoRegisterSchemasViaPropertiesMap() { + // auto.register.schemas can also be provided through the generic properties map, + // without the dedicated table option + final Map expectedProperties = new HashMap<>(); + expectedProperties.put("auto.register.schemas", "false"); + + final Map options = + getModifiedOptions( + opts -> + opts.put( + "avro-confluent.properties.auto.register.schemas", + "false")); + + final AvroRowDataSerializationSchema expectedSer = + new AvroRowDataSerializationSchema( + ROW_TYPE, + ConfluentRegistryAvroSerializationSchema.forGeneric( + SUBJECT, + AvroSchemaConverter.convertToSchema(ROW_TYPE), + REGISTRY_URL, + expectedProperties), + RowDataToAvroConverters.createConverter(ROW_TYPE)); + + final DynamicTableSink actualSink = createTableSink(SCHEMA, options); + assertThat(actualSink).isInstanceOf(TestDynamicTableFactory.DynamicTableSinkMock.class); + TestDynamicTableFactory.DynamicTableSinkMock sinkMock = + (TestDynamicTableFactory.DynamicTableSinkMock) actualSink; + + SerializationSchema actualSer = + sinkMock.valueFormat.createRuntimeEncoder(null, SCHEMA.toPhysicalRowDataType()); + assertThat(actualSer).isEqualTo(expectedSer); + } + // ------------------------------------------------------------------------ // Utilities // ------------------------------------------------------------------------ diff --git a/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroFormatFactoryTest.java b/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroFormatFactoryTest.java index e7a263ab42123b..52f1d942d00e1f 100644 --- a/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroFormatFactoryTest.java +++ b/flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroFormatFactoryTest.java @@ -168,6 +168,31 @@ public void testSeDeSchemaWithSchemaOption() { assertThat(actualSer).isEqualTo(expectedSer); } + @Test + void testSeDeSchemaWithAutoRegisterSchemas() { + final Map options = getAllOptions(); + options.put("debezium-avro-confluent.auto.register.schemas", "false"); + + final Map registryConfigs = getRegistryConfigs(); + registryConfigs.put("auto.register.schemas", "false"); + + DebeziumAvroDeserializationSchema expectedDeser = + new DebeziumAvroDeserializationSchema( + ROW_TYPE, + InternalTypeInfo.of(ROW_TYPE), + REGISTRY_URL, + null, + registryConfigs); + DeserializationSchema actualDeser = createDeserializationSchema(options); + assertThat(actualDeser).isEqualTo(expectedDeser); + + DebeziumAvroSerializationSchema expectedSer = + new DebeziumAvroSerializationSchema( + ROW_TYPE, REGISTRY_URL, SUBJECT, null, registryConfigs); + SerializationSchema actualSer = createSerializationSchema(options); + assertThat(actualSer).isEqualTo(expectedSer); + } + @Test public void testSeDeSchemaWithInvalidSchemaOption() { final Map options = getAllOptions(); From 4c65cd3bda0f068df755122514f839354a48227b Mon Sep 17 00:00:00 2001 From: Martijn Visser <2989614+MartijnVisser@users.noreply.github.com> Date: Fri, 3 Jul 2026 22:16:24 +0200 Subject: [PATCH 3/4] [FLINK-33045][e2e] Replace disabled bash-based schema registry test with AvroConfluentITCase --- .../flink-confluent-schema-registry/pom.xml | 157 +++++-- .../avro/{user.avsc => input-record.avsc} | 21 +- .../main/resources/avro/output-record.avsc | 28 ++ .../registry/test/AvroConfluentITCase.java | 439 ++++++++++++++++++ .../schema/registry/test/SchemaLoader.java | 46 ++ .../avro_confluent_missing_schema_e2e.sql | 37 ++ .../avro_confluent_roundtrip_e2e.sql | 61 +++ .../src/test/resources/log4j2-test.properties | 34 ++ flink-end-to-end-tests/run-nightly-tests.sh | 4 - .../test_confluent_schema_registry.sh | 117 ----- 10 files changed, 764 insertions(+), 180 deletions(-) rename flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/{user.avsc => input-record.avsc} (66%) create mode 100644 flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/output-record.avsc create mode 100644 flink-end-to-end-tests/flink-confluent-schema-registry/src/test/java/org/apache/flink/schema/registry/test/AvroConfluentITCase.java create mode 100644 flink-end-to-end-tests/flink-confluent-schema-registry/src/test/java/org/apache/flink/schema/registry/test/SchemaLoader.java create mode 100644 flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/avro_confluent_missing_schema_e2e.sql create mode 100644 flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/avro_confluent_roundtrip_e2e.sql create mode 100644 flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/log4j2-test.properties delete mode 100755 flink-end-to-end-tests/test-scripts/test_confluent_schema_registry.sh diff --git a/flink-end-to-end-tests/flink-confluent-schema-registry/pom.xml b/flink-end-to-end-tests/flink-confluent-schema-registry/pom.xml index e115b997f40f28..98f215063f20a7 100644 --- a/flink-end-to-end-tests/flink-confluent-schema-registry/pom.xml +++ b/flink-end-to-end-tests/flink-confluent-schema-registry/pom.xml @@ -43,101 +43,160 @@ under the License. org.apache.flink - flink-streaming-java + flink-table-common ${project.version} provided - + org.apache.flink - flink-connector-kafka - 3.0.0-1.17 + flink-table-api-java-bridge + ${project.version} + provided - + org.apache.flink - flink-shaded-guava - 30.1.1-jre-16.1 + flink-end-to-end-tests-common + ${project.version} + + org.apache.flink - flink-avro + flink-runtime-web ${project.version} + test + + + + + + org.apache.flink + flink-sql-connector-kafka + 5.0.0-2.2 + provided + org.apache.flink - flink-avro-confluent-registry + flink-sql-avro-confluent-registry ${project.version} + provided + + + + org.apache.kafka + kafka-clients + 4.0.0 + test + + + org.testcontainers + testcontainers-kafka + test + + + + io.confluent + kafka-avro-serializer + 7.9.8 + test + + + + org.apache.avro + avro + 1.11.5 + test + + + + commons-codec + commons-codec + test + + + + + + org.apache.kafka + kafka-clients + 4.0.0 + + + + + org.apache.maven.plugins maven-shade-plugin - TestAvroConsumerConfluent + + e2e-dependencies package shade - TestAvroConsumerConfluent - - - org.apache.flink.schema.registry.test.TestAvroConsumerConfluent - - - - - - - - org.apache.avro - avro-maven-plugin - ${avro.version} - - - generate-sources - - schema - - - ${project.basedir}/src/main/resources/avro/ - ${project.basedir}/target/generated-sources/ - PRIVATE - - **/*.avsc - + SqlToolbox + + org.apache.maven.plugins - maven-enforcer-plugin + maven-dependency-plugin - dependency-convergence + copy + package - enforce + copy - true + ${project.build.directory}/sql-jars + + + + org.apache.flink + flink-sql-connector-kafka + 5.0.0-2.2 + jar + + + org.apache.flink + flink-test-utils + ${project.version} + jar + + + org.apache.flink + flink-sql-avro-confluent-registry + ${project.version} + jar + + - - org.apache.maven.plugins - maven-checkstyle-plugin - - **/example/avro/* - - diff --git a/flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/user.avsc b/flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/input-record.avsc similarity index 66% rename from flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/user.avsc rename to flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/input-record.avsc index aca9b83f9c2ced..bb275a71b01ce4 100644 --- a/flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/user.avsc +++ b/flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/input-record.avsc @@ -15,13 +15,14 @@ * limitations under the License. */ - {"namespace": "example.avro", - "type": "record", - "name": "User", - "fields": [ - {"name": "name", "type": "string", "default": ""}, - {"name": "favoriteNumber", "type": "string", "default": ""}, - {"name": "favoriteColor", "type": "string", "default": ""}, - {"name": "eventType","type": {"name": "EventType","type": "enum", "symbols": ["meeting"] }} - ] -} +{ + "namespace": "org.apache.flink.avro.generated", + "type": "record", + "name": "record", + "fields": [ + {"name": "name", "type": ["null", "string"], "default": null}, + {"name": "favoriteNumber", "type": ["null", "string"], "default": null}, + {"name": "favoriteColor", "type": ["null", "string"], "default": null}, + {"name": "eventType", "type": ["null", "string"], "default": null} + ] +} diff --git a/flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/output-record.avsc b/flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/output-record.avsc new file mode 100644 index 00000000000000..bb275a71b01ce4 --- /dev/null +++ b/flink-end-to-end-tests/flink-confluent-schema-registry/src/main/resources/avro/output-record.avsc @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "namespace": "org.apache.flink.avro.generated", + "type": "record", + "name": "record", + "fields": [ + {"name": "name", "type": ["null", "string"], "default": null}, + {"name": "favoriteNumber", "type": ["null", "string"], "default": null}, + {"name": "favoriteColor", "type": ["null", "string"], "default": null}, + {"name": "eventType", "type": ["null", "string"], "default": null} + ] +} diff --git a/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/java/org/apache/flink/schema/registry/test/AvroConfluentITCase.java b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/java/org/apache/flink/schema/registry/test/AvroConfluentITCase.java new file mode 100644 index 00000000000000..72bb396873a5f4 --- /dev/null +++ b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/java/org/apache/flink/schema/registry/test/AvroConfluentITCase.java @@ -0,0 +1,439 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.schema.registry.test; + +import org.apache.flink.api.common.JobStatus; +import org.apache.flink.connector.testframe.container.FlinkContainers; +import org.apache.flink.connector.testframe.container.FlinkContainersSettings; +import org.apache.flink.connector.testframe.container.TestcontainersSettings; +import org.apache.flink.core.testutils.CommonTestUtils; +import org.apache.flink.runtime.client.JobStatusMessage; +import org.apache.flink.test.resources.ResourceTestUtils; +import org.apache.flink.test.util.SQLJobSubmission; +import org.apache.flink.util.DockerImageVersions; +import org.apache.flink.util.jackson.JacksonMapperFactory; + +import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.avro.generic.GenericRecord; +import org.apache.kafka.clients.admin.Admin; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.kafka.ConfluentKafkaContainer; +import org.testcontainers.utility.DockerImageName; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.TimeoutException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** E2E Test for Avro-Confluent integration. */ +@Testcontainers +public class AvroConfluentITCase { + + private static final Logger LOG = LoggerFactory.getLogger(AvroConfluentITCase.class); + + private static final String INTER_CONTAINER_KAFKA_ALIAS = "kafka"; + private static final String INTER_CONTAINER_KAFKA_BOOTSTRAP = + INTER_CONTAINER_KAFKA_ALIAS + ":19092"; + private static final String INTER_CONTAINER_SCHEMA_REGISTRY_ALIAS = "schema-registry"; + private static final String INTER_CONTAINER_SCHEMA_REGISTRY_URL = + "http://" + INTER_CONTAINER_SCHEMA_REGISTRY_ALIAS + ":8081"; + + private static final String TOPIC = "test-avro-input"; + private static final String RESULT_TOPIC = "test-avro-output"; + private static final String MANUAL_TOPIC = "test-avro-input-manual"; + private static final String MANUAL_RESULT_TOPIC = "test-avro-output-manual"; + private static final String MISSING_SCHEMA_TOPIC = "test-avro-output-missing-schema"; + + private static final String INPUT_SCHEMA = SchemaLoader.loadSchema("avro/input-record.avsc"); + private static final String OUTPUT_SCHEMA = SchemaLoader.loadSchema("avro/output-record.avsc"); + + private static final Path sqlToolBoxJar = ResourceTestUtils.getResource(".*/SqlToolbox\\.jar"); + + private final Path sqlConnectorKafkaJar = ResourceTestUtils.getResource(".*kafka.*\\.jar"); + private final Path sqlConnectorUpsertTestJar = + ResourceTestUtils.getResource(".*flink-test-utils.*\\.jar"); + private final Path sqlAvroConfluentJar = + ResourceTestUtils.getResource(".*avro-confluent.*\\.jar"); + + private static final Network NETWORK = Network.newNetwork(); + + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); + private static final ObjectMapper OBJECT_MAPPER = JacksonMapperFactory.createObjectMapper(); + + @Container + public static final ConfluentKafkaContainer KAFKA = + new ConfluentKafkaContainer(DockerImageName.parse(DockerImageVersions.KAFKA)) + .withNetwork(NETWORK) + .withNetworkAliases(INTER_CONTAINER_KAFKA_ALIAS) + .withListener(INTER_CONTAINER_KAFKA_BOOTSTRAP); + + @Container + private static final GenericContainer SCHEMA_REGISTRY = + new GenericContainer<>(DockerImageName.parse(DockerImageVersions.SCHEMA_REGISTRY)) + .withNetwork(NETWORK) + .withNetworkAliases(INTER_CONTAINER_SCHEMA_REGISTRY_ALIAS) + .withExposedPorts(8081) + .withEnv("SCHEMA_REGISTRY_HOST_NAME", INTER_CONTAINER_SCHEMA_REGISTRY_ALIAS) + .withEnv( + "SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS", + INTER_CONTAINER_KAFKA_BOOTSTRAP) + .dependsOn(KAFKA); + + @RegisterExtension + private static final FlinkContainers FLINK = + FlinkContainers.builder() + .withFlinkContainersSettings( + FlinkContainersSettings.builder().numTaskManagers(1).build()) + .withTestcontainersSettings( + TestcontainersSettings.builder() + .network(NETWORK) + .logger(LOG) + .dependsOn(KAFKA) + .build()) + .build(); + + @BeforeEach + public void before() throws Exception { + // Create topics using external bootstrap servers since we're outside the Docker network + Properties props = new Properties(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers()); + try (Admin admin = Admin.create(props)) { + List topics = + Arrays.asList( + new NewTopic(TOPIC, 1, (short) 1), + new NewTopic(RESULT_TOPIC, 1, (short) 1), + new NewTopic(MANUAL_TOPIC, 1, (short) 1), + new NewTopic(MANUAL_RESULT_TOPIC, 1, (short) 1), + new NewTopic(MISSING_SCHEMA_TOPIC, 1, (short) 1)); + admin.createTopics(topics); + + // Poll for topic creation with timeout + try { + CommonTestUtils.waitUntilIgnoringExceptions( + () -> { + try { + Set existingTopics = admin.listTopics().names().get(); + return topics.stream() + .allMatch(topic -> existingTopics.contains(topic.name())); + } catch (Exception e) { + LOG.warn("Exception while checking topic creation", e); + return false; + } + }, + Duration.ofSeconds(30), + Duration.ofMillis(100), + "Topics were not created in time"); + } catch (TimeoutException | InterruptedException e) { + throw new RuntimeException("Failed to wait for topic creation", e); + } + + LOG.info("Topics created successfully"); + } + } + + @Test + public void testAvroConfluentIntegrationWithAutoRegister() throws Exception { + List sqlLines = + initializeSqlLines( + "avro_confluent_roundtrip_e2e.sql", + generateRoundtripReplaceVars(TOPIC, RESULT_TOPIC, true)); + + executeSql(sqlLines); + + verifyNumberOfResultRecords(RESULT_TOPIC, 3); + } + + @Test + public void testAvroConfluentIntegrationWithManualRegister() throws Exception { + // Manually register schemas before creating tables + registerSchema(MANUAL_TOPIC + "-value", INPUT_SCHEMA); + registerSchema(MANUAL_RESULT_TOPIC + "-value", OUTPUT_SCHEMA); + + List sqlLines = + initializeSqlLines( + "avro_confluent_roundtrip_e2e.sql", + generateRoundtripReplaceVars(MANUAL_TOPIC, MANUAL_RESULT_TOPIC, false)); + + executeSql(sqlLines); + + verifyNumberOfResultRecords(MANUAL_RESULT_TOPIC, 3); + + // The job must not have registered any new schema version on top of the + // manually registered ones + assertThat(getSubjectVersions(MANUAL_TOPIC + "-value")).containsExactly(1); + assertThat(getSubjectVersions(MANUAL_RESULT_TOPIC + "-value")).containsExactly(1); + } + + @Test + public void testWritingFailsWhenAutoRegisterDisabledAndSchemaMissing() throws Exception { + // No schema is registered for MISSING_SCHEMA_TOPIC, so writing with + // auto.register.schemas=false must fail instead of registering the schema + Map vars = new HashMap<>(); + vars.put("$OUTPUT_TOPIC", MISSING_SCHEMA_TOPIC); + vars.put("$BOOTSTRAP_SERVERS", INTER_CONTAINER_KAFKA_BOOTSTRAP); + vars.put("$SCHEMA_REGISTRY_URL", INTER_CONTAINER_SCHEMA_REGISTRY_URL); + List sqlLines = initializeSqlLines("avro_confluent_missing_schema_e2e.sql", vars); + + // The SQL client reads the script from stdin and always exits successfully, even if + // a statement fails; the failure must be observed through the job status instead. + // 'table.dml-sync' guarantees the job has reached a terminal state afterwards. + executeSql(sqlLines); + + JobStatusMessage insertJob = + FLINK.getRestClusterClient().listJobs().get().stream() + .filter(job -> job.getJobName().contains("avro_output_missing_schema")) + .findAny() + .orElseThrow( + () -> + new AssertionError( + "Could not find the job writing to the missing-schema table")); + assertThat(insertJob.getJobState()).isEqualTo(JobStatus.FAILED); + + // The failed job must not have registered the schema either + assertThat(subjectExists(MISSING_SCHEMA_TOPIC + "-value")).isFalse(); + } + + private static Map generateRoundtripReplaceVars( + String inputTopic, String outputTopic, boolean autoRegisterSchemas) { + Map vars = new HashMap<>(); + vars.put("$INPUT_TOPIC", inputTopic); + vars.put("$OUTPUT_TOPIC", outputTopic); + vars.put("$BOOTSTRAP_SERVERS", INTER_CONTAINER_KAFKA_BOOTSTRAP); + vars.put("$SCHEMA_REGISTRY_URL", INTER_CONTAINER_SCHEMA_REGISTRY_URL); + vars.put("$AUTO_REGISTER_SCHEMAS", String.valueOf(autoRegisterSchemas)); + return vars; + } + + private static List initializeSqlLines(String sqlPath, Map vars) + throws IOException { + URL url = AvroConfluentITCase.class.getClassLoader().getResource(sqlPath); + if (url == null) { + throw new FileNotFoundException(sqlPath); + } + List lines = Files.readAllLines(new File(url.getFile()).toPath()); + List result = new ArrayList<>(); + for (String line : lines) { + for (Map.Entry var : vars.entrySet()) { + line = line.replace(var.getKey(), var.getValue()); + } + result.add(line); + } + return result; + } + + private static String getSchemaRegistryUrl() { + return "http://" + SCHEMA_REGISTRY.getHost() + ":" + SCHEMA_REGISTRY.getMappedPort(8081); + } + + private String registerSchema(String subject, String schema) { + try { + // Use Jackson to properly serialize the request body + Map requestBodyMap = new HashMap<>(); + requestBodyMap.put("schema", schema); + String requestBody = OBJECT_MAPPER.writeValueAsString(requestBodyMap); + + LOG.info("Registering schema for subject {} with schema: {}", subject, schema); + + HttpRequest request = + HttpRequest.newBuilder() + .uri( + URI.create( + getSchemaRegistryUrl() + + "/subjects/" + + subject + + "/versions")) + .header("Content-Type", "application/vnd.schemaregistry.v1+json") + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + HttpResponse response = + HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + throw new RuntimeException( + "Failed to register schema for subject " + + subject + + ": " + + response.statusCode() + + " - " + + response.body()); + } + + LOG.info("Successfully registered schema for subject {}: {}", subject, response.body()); + return response.body(); + } catch (Exception e) { + throw new RuntimeException("Failed to register schema for subject " + subject, e); + } + } + + private List getSubjectVersions(String subject) { + try { + HttpRequest request = + HttpRequest.newBuilder() + .uri( + URI.create( + getSchemaRegistryUrl() + + "/subjects/" + + subject + + "/versions")) + .GET() + .build(); + HttpResponse response = + HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new RuntimeException( + "Failed to get versions for subject " + + subject + + ": " + + response.statusCode() + + " - " + + response.body()); + } + return Arrays.asList(OBJECT_MAPPER.readValue(response.body(), Integer[].class)); + } catch (Exception e) { + throw new RuntimeException("Failed to get versions for subject " + subject, e); + } + } + + private boolean subjectExists(String subject) { + try { + HttpRequest request = + HttpRequest.newBuilder() + .uri( + URI.create( + getSchemaRegistryUrl() + + "/subjects/" + + subject + + "/versions")) + .GET() + .build(); + HttpResponse response = + HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + return response.statusCode() == 200; + } catch (Exception e) { + throw new RuntimeException("Failed to check subject " + subject, e); + } + } + + private void verifyNumberOfResultRecords(String topicName, int expectedCount) throws Exception { + // Use external bootstrap servers since we're outside the Docker network + Properties props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers()); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "test-verification-group-" + topicName); + props.put( + ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put( + ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "io.confluent.kafka.serializers.KafkaAvroDeserializer"); + props.put("schema.registry.url", getSchemaRegistryUrl()); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + + try (KafkaConsumer consumer = new KafkaConsumer<>(props)) { + consumer.subscribe(Collections.singletonList(topicName)); + List records = new ArrayList<>(); + + // Poll for records until we have enough or timeout + try { + CommonTestUtils.waitUntilIgnoringExceptions( + () -> { + try { + ConsumerRecords consumerRecords = + consumer.poll(Duration.ofMillis(100)); + for (ConsumerRecord record : + consumerRecords) { + records.add(record.value()); + } + return records.size() >= expectedCount; + } catch (Exception e) { + LOG.warn("Exception while polling records", e); + return false; + } + }, + Duration.ofSeconds(30), + Duration.ofMillis(100), + "Expected " + + expectedCount + + " records but only found " + + records.size() + + " in topic " + + topicName); + } catch (TimeoutException | InterruptedException e) { + throw new RuntimeException("Failed to wait for records in topic " + topicName, e); + } + + // Verify the exact number of records + assertEquals( + expectedCount, + records.size(), + "Expected exactly " + expectedCount + " records but found " + records.size()); + + // Log the records for debugging + for (GenericRecord record : records) { + LOG.info("Found record in {}: {}", topicName, record); + } + } + } + + private void executeSql(List sqlLines) throws Exception { + LOG.info("Submitting SQL statements: {}", String.join("\n", sqlLines)); + FLINK.submitSQLJob( + new SQLJobSubmission.SQLJobSubmissionBuilder(sqlLines) + .addJars( + sqlConnectorUpsertTestJar, + sqlConnectorKafkaJar, + sqlAvroConfluentJar, + sqlToolBoxJar) + .build()); + } +} diff --git a/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/java/org/apache/flink/schema/registry/test/SchemaLoader.java b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/java/org/apache/flink/schema/registry/test/SchemaLoader.java new file mode 100644 index 00000000000000..d4a0023aa54317 --- /dev/null +++ b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/java/org/apache/flink/schema/registry/test/SchemaLoader.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.schema.registry.test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** Utility class for loading Avro schema files from resources. */ +public class SchemaLoader { + + /** + * Load an Avro schema from a resource file. + * + * @param resourcePath Path to the .avsc file in resources + * @return Schema content as string + * @throws RuntimeException if the file cannot be loaded + */ + public static String loadSchema(String resourcePath) { + try (InputStream inputStream = + SchemaLoader.class.getClassLoader().getResourceAsStream(resourcePath)) { + if (inputStream == null) { + throw new RuntimeException("Schema file not found: " + resourcePath); + } + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Failed to load schema from: " + resourcePath, e); + } + } +} diff --git a/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/avro_confluent_missing_schema_e2e.sql b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/avro_confluent_missing_schema_e2e.sql new file mode 100644 index 00000000000000..8c1678f2e64cf4 --- /dev/null +++ b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/avro_confluent_missing_schema_e2e.sql @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +SET 'table.dml-sync' = 'true'; + +CREATE TABLE avro_output_missing_schema ( + name STRING, + favoriteNumber STRING, + favoriteColor STRING, + eventType STRING +) WITH ( + 'connector' = 'kafka', + 'topic' = '$OUTPUT_TOPIC', + 'properties.bootstrap.servers' = '$BOOTSTRAP_SERVERS', + 'scan.startup.mode' = 'earliest-offset', + 'scan.bounded.mode' = 'latest-offset', + 'format' = 'avro-confluent', + 'avro-confluent.url' = '$SCHEMA_REGISTRY_URL', + 'avro-confluent.auto.register.schemas' = 'false' +); + +INSERT INTO avro_output_missing_schema VALUES ('Grace', '1', 'black', 'INSERT'); diff --git a/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/avro_confluent_roundtrip_e2e.sql b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/avro_confluent_roundtrip_e2e.sql new file mode 100644 index 00000000000000..de936e893aa6d9 --- /dev/null +++ b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/avro_confluent_roundtrip_e2e.sql @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +SET 'table.dml-sync' = 'true'; + +CREATE TABLE avro_input ( + name STRING, + favoriteNumber STRING, + favoriteColor STRING, + eventType STRING +) WITH ( + 'connector' = 'kafka', + 'topic' = '$INPUT_TOPIC', + 'properties.bootstrap.servers' = '$BOOTSTRAP_SERVERS', + 'properties.group.id' = 'test-group-$INPUT_TOPIC', + 'scan.startup.mode' = 'earliest-offset', + 'scan.bounded.mode' = 'latest-offset', + 'format' = 'avro-confluent', + 'avro-confluent.url' = '$SCHEMA_REGISTRY_URL', + 'avro-confluent.auto.register.schemas' = '$AUTO_REGISTER_SCHEMAS' +); + +CREATE TABLE avro_output ( + name STRING, + favoriteNumber STRING, + favoriteColor STRING, + eventType STRING +) WITH ( + 'connector' = 'kafka', + 'topic' = '$OUTPUT_TOPIC', + 'properties.bootstrap.servers' = '$BOOTSTRAP_SERVERS', + 'properties.group.id' = 'test-group-$OUTPUT_TOPIC', + 'scan.startup.mode' = 'earliest-offset', + 'scan.bounded.mode' = 'latest-offset', + 'format' = 'avro-confluent', + 'avro-confluent.url' = '$SCHEMA_REGISTRY_URL', + 'avro-confluent.auto.register.schemas' = '$AUTO_REGISTER_SCHEMAS' +); + +INSERT INTO avro_input VALUES + ('Alice', '42', 'blue', 'INSERT'), + ('Bob', '7', 'red', 'INSERT'), + ('Charlie', '73', 'green', 'INSERT'); + +INSERT INTO avro_output +SELECT * FROM avro_input; diff --git a/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/log4j2-test.properties b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/log4j2-test.properties new file mode 100644 index 00000000000000..358fd81ef99786 --- /dev/null +++ b/flink-end-to-end-tests/flink-confluent-schema-registry/src/test/resources/log4j2-test.properties @@ -0,0 +1,34 @@ +################################################################################ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +# Set root logger level to OFF to not flood build logs +# set manually to INFO for debugging purposes +rootLogger.level = OFF +rootLogger.appenderRef.test.ref = TestLogger + +appender.testlogger.name = TestLogger +appender.testlogger.type = CONSOLE +appender.testlogger.target = SYSTEM_ERR +appender.testlogger.layout.type = PatternLayout +appender.testlogger.layout.pattern = %-4r [%t] %-5p %c %x - %m%n + +# It is recommended to uncomment these lines when enabling the logger. The below package used +# by testcontainers is quite verbose +#logger.yarn.name = org.testcontainers.shaded.com.github.dockerjava.core +#logger.yarn.level = WARN +#logger.yarn.appenderRef.console.ref = TestLogger diff --git a/flink-end-to-end-tests/run-nightly-tests.sh b/flink-end-to-end-tests/run-nightly-tests.sh index 3b84d479836fbf..b2cb9bd0e82ab5 100755 --- a/flink-end-to-end-tests/run-nightly-tests.sh +++ b/flink-end-to-end-tests/run-nightly-tests.sh @@ -194,10 +194,6 @@ function run_group_3 { # Miscellaneous ################################################################################ - # Disable this test as 2.0 broke the compatibility of kafka sink writer. We should consider migrate this test to flink-connector-kafka repo. - # See FLINK-36268. - # run_test "Avro Confluent Schema Registry nightly end-to-end test" "$END_TO_END_DIR/test-scripts/test_confluent_schema_registry.sh" - run_test "State TTL Heap backend end-to-end test" "$END_TO_END_DIR/test-scripts/test_stream_state_ttl.sh hashmap" "skip_check_exceptions" run_test "State TTL RocksDb backend end-to-end test" "$END_TO_END_DIR/test-scripts/test_stream_state_ttl.sh rocks" "skip_check_exceptions" diff --git a/flink-end-to-end-tests/test-scripts/test_confluent_schema_registry.sh b/flink-end-to-end-tests/test-scripts/test_confluent_schema_registry.sh deleted file mode 100755 index d653ef46bc578a..00000000000000 --- a/flink-end-to-end-tests/test-scripts/test_confluent_schema_registry.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env bash -################################################################################ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -################################################################################ - -set -Eeuo pipefail - -KAFKA_VERSION="3.2.3" -CONFLUENT_VERSION="7.5.3" -CONFLUENT_MAJOR_VERSION="7.5" -# Check the Confluent Platform <> Apache Kafka compatibility matrix when updating KAFKA_VERSION -KAFKA_SQL_VERSION="universal" - -source "$(dirname "$0")"/common.sh -source "$(dirname "$0")"/kafka_sql_common.sh \ - $KAFKA_VERSION \ - $CONFLUENT_VERSION \ - $CONFLUENT_MAJOR_VERSION \ - $KAFKA_SQL_VERSION - -function verify_output { - local expected=$(printf $1) - local result=$(echo $2 | sed 's/ //g') - - if [[ "$result" != "$expected" ]]; then - echo "Output from Flink program does not match expected output." - echo -e "EXPECTED FOR KEY: --$expected--" - echo -e "ACTUAL: --$result--" - exit 1 - fi -} - -function test_setup { - start_kafka_cluster - start_confluent_schema_registry -} - -function test_cleanup { - stop_confluent_schema_registry - stop_kafka_cluster -} - -on_exit test_cleanup - -function schema_registry_test { - setup_kafka_dist - setup_confluent_dist - - retry_times_with_backoff_and_cleanup 3 5 test_setup test_cleanup - - TEST_PROGRAM_JAR=${END_TO_END_DIR}/flink-confluent-schema-registry/target/TestAvroConsumerConfluent.jar - - INPUT_MESSAGE_1='{"name":"Alyssa","favoriteNumber":"250","favoriteColor":"green","eventType":"meeting"}' - INPUT_MESSAGE_2='{"name":"Charlie","favoriteNumber":"10","favoriteColor":"blue","eventType":"meeting"}' - INPUT_MESSAGE_3='{"name":"Ben","favoriteNumber":"7","favoriteColor":"red","eventType":"meeting"}' - USER_SCHEMA='{"namespace":"example.avro","type":"record","name":"User","fields":[{"name":"name","type":"string","default":""},{"name":"favoriteNumber","type":"string","default":""},{"name":"favoriteColor","type":"string","default":""},{"name":"eventType","type":{"name":"EventType","type":"enum","symbols":["meeting"]}}]}' - - curl -X POST \ - ${SCHEMA_REGISTRY_URL}/subjects/users-value/versions \ - -H 'cache-control: no-cache' \ - -H 'content-type: application/vnd.schemaregistry.v1+json' \ - -d '{"schema": "{\"namespace\": \"example.avro\",\"type\": \"record\",\"name\": \"User\",\"fields\": [{\"name\": \"name\", \"type\": \"string\", \"default\": \"\"},{\"name\": \"favoriteNumber\", \"type\": \"string\", \"default\": \"\"},{\"name\": \"favoriteColor\", \"type\": \"string\", \"default\": \"\"},{\"name\": \"eventType\",\"type\": {\"name\": \"EventType\",\"type\": \"enum\", \"symbols\": [\"meeting\"] }}]}"}' - - echo "Sending messages to Kafka topic [test-avro-input] ..." - - send_messages_to_kafka_avro $INPUT_MESSAGE_1 test-avro-input $USER_SCHEMA - send_messages_to_kafka_avro $INPUT_MESSAGE_2 test-avro-input $USER_SCHEMA - send_messages_to_kafka_avro $INPUT_MESSAGE_3 test-avro-input $USER_SCHEMA - - start_cluster - - create_kafka_topic 1 1 test-string-out - create_kafka_topic 1 1 test-avro-out - - # Read Avro message from [test-avro-input], check the schema and send message to [test-string-ou] - $FLINK_DIR/bin/flink run -d $TEST_PROGRAM_JAR \ - --input-topic test-avro-input --output-string-topic test-string-out --output-avro-topic test-avro-out --output-subject test-output-subject \ - --bootstrap.servers localhost:9092 --group.id myconsumer --auto.offset.reset earliest \ - --schema-registry-url ${SCHEMA_REGISTRY_URL} - - echo "Reading messages from Kafka topic [test-string-out] ..." - - KEY_1_STRING_MSGS=$(read_messages_from_kafka 3 test-string-out Alyssa_consumer | grep Alyssa) - KEY_2_STRING_MSGS=$(read_messages_from_kafka 3 test-string-out Charlie_consumer | grep Charlie) - KEY_3_STRING_MSGS=$(read_messages_from_kafka 3 test-string-out Ben_consumer | grep Ben) - - ## Verifying STRING output with actual message - verify_output $INPUT_MESSAGE_1 "$KEY_1_STRING_MSGS" - verify_output $INPUT_MESSAGE_2 "$KEY_2_STRING_MSGS" - verify_output $INPUT_MESSAGE_3 "$KEY_3_STRING_MSGS" - - KEY_1_AVRO_MSGS=$(read_messages_from_kafka_avro 3 test-avro-out $USER_SCHEMA Alyssa_consumer_1 | grep Alyssa) - KEY_2_AVRO_MSGS=$(read_messages_from_kafka_avro 3 test-avro-out $USER_SCHEMA Charlie_consumer_1 | grep Charlie) - KEY_3_AVRO_MSGS=$(read_messages_from_kafka_avro 3 test-avro-out $USER_SCHEMA Ben_consumer_1 | grep Ben) - - ## Verifying AVRO output with actual message - verify_output $INPUT_MESSAGE_1 "$KEY_1_AVRO_MSGS" - verify_output $INPUT_MESSAGE_2 "$KEY_2_AVRO_MSGS" - verify_output $INPUT_MESSAGE_3 "$KEY_3_AVRO_MSGS" -} - -run_test_with_timeout 900 schema_registry_test - From df1d162190270a28ce803c651a5a05855e76310c Mon Sep 17 00:00:00 2001 From: Martijn Visser <2989614+MartijnVisser@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:15:42 +0200 Subject: [PATCH 4/4] [FLINK-33045][docs] Document schema auto-registration behavior for avro-confluent formats --- .../docs/connectors/table/formats/avro-confluent.md | 7 +++++++ .../docs/connectors/table/formats/debezium.md | 7 +++++++ .../docs/connectors/table/formats/avro-confluent.md | 10 ++++++++++ docs/content/docs/connectors/table/formats/debezium.md | 7 +++++++ 4 files changed, 31 insertions(+) diff --git a/docs/content.zh/docs/connectors/table/formats/avro-confluent.md b/docs/content.zh/docs/connectors/table/formats/avro-confluent.md index 02b71fab66d261..6caa2fdd9ea9ed 100644 --- a/docs/content.zh/docs/connectors/table/formats/avro-confluent.md +++ b/docs/content.zh/docs/connectors/table/formats/avro-confluent.md @@ -196,6 +196,13 @@ Format 参数 String Specify what format to use, here should be 'avro-confluent'. + +
avro-confluent.auto.register.schemas
+ optional + true + Boolean + Whether to automatically register the schema in the Confluent Schema Registry during serialization. When set to false, an identical schema must have been registered under the subject outside of Flink before it can be used; only its id is looked up and the job fails if it cannot be found. Schema registration only happens when writing data; reading always looks up the schema by the id embedded in each record. +
avro-confluent.basic-auth.credentials-source
optional diff --git a/docs/content.zh/docs/connectors/table/formats/debezium.md b/docs/content.zh/docs/connectors/table/formats/debezium.md index a0b2f8d7b7a295..0991ceba1e3be2 100644 --- a/docs/content.zh/docs/connectors/table/formats/debezium.md +++ b/docs/content.zh/docs/connectors/table/formats/debezium.md @@ -292,6 +292,13 @@ Flink 提供了 `debezium-avro-confluent` 和 `debezium-json` 两种 format 来 String Specify what format to use, here should be 'debezium-avro-confluent'. + +
debezium-avro-confluent.auto.register.schemas
+ optional + true + Boolean + Whether to automatically register the schema in the Confluent Schema Registry during serialization. When set to false, an identical schema must have been registered under the subject outside of Flink before it can be used; only its id is looked up and the job fails if it cannot be found. Schema registration only happens when writing data; reading always looks up the schema by the id embedded in each record. +
debezium-avro-confluent.basic-auth.credentials-source
optional diff --git a/docs/content/docs/connectors/table/formats/avro-confluent.md b/docs/content/docs/connectors/table/formats/avro-confluent.md index c2fb1bc4717867..e8f4968801bd7e 100644 --- a/docs/content/docs/connectors/table/formats/avro-confluent.md +++ b/docs/content/docs/connectors/table/formats/avro-confluent.md @@ -35,6 +35,8 @@ When reading (deserializing) a record with this format the Avro writer schema is When writing (serializing) a record with this format the Avro schema is inferred from the table schema and used to retrieve a schema id to be encoded with the data. The lookup is performed with in the configured Confluent Schema Registry under the [subject](https://docs.confluent.io/current/schema-registry/index.html#schemas-subjects-and-topics) given in `avro-confluent.subject`. +By default, the schema is automatically registered under the subject if it is not present yet (`'avro-confluent.auto.register.schemas' = 'true'`). Schema registration only happens when writing (serializing) data; reading always fetches the writer schema by the id encoded in each record. Automatic registration can be disabled (`'avro-confluent.auto.register.schemas' = 'false'`), for example when schemas are managed outside of Flink and write access to the Schema Registry is restricted. In that case a schema identical to the one used by the format must already be registered under the subject, otherwise the job fails with a "Schema not found" error. Since Flink infers the Avro schema from the table schema (using the record name `org.apache.flink.avro.generated.record`), it is recommended to provide the expected schema explicitly via `avro-confluent.schema` when schemas are managed externally. Note that the Schema Registry client caches failed lookups; registering a missing schema only takes effect after the cache entry expires or the job is restarted. + The Avro Schema Registry format can only be used in conjunction with the [Apache Kafka SQL connector]({{< ref "docs/connectors/table/kafka" >}}) or the [Upsert Kafka SQL Connector]({{< ref "docs/connectors/table/upsert-kafka" >}}). Dependencies @@ -191,6 +193,14 @@ Format Options String Specify what format to use, here should be 'avro-confluent'. + +
avro-confluent.auto.register.schemas
+ optional + yes + true + Boolean + Whether to automatically register the schema in the Confluent Schema Registry during serialization. When set to false, an identical schema must have been registered under the subject outside of Flink before it can be used; only its id is looked up and the job fails if it cannot be found. Schema registration only happens when writing data; reading always looks up the schema by the id embedded in each record. +
avro-confluent.basic-auth.credentials-source
optional diff --git a/docs/content/docs/connectors/table/formats/debezium.md b/docs/content/docs/connectors/table/formats/debezium.md index 54f36448887e21..8623a265d3c5ed 100644 --- a/docs/content/docs/connectors/table/formats/debezium.md +++ b/docs/content/docs/connectors/table/formats/debezium.md @@ -282,6 +282,13 @@ Use format `debezium-avro-confluent` to interpret Debezium Avro messages and for String Specify what format to use, here should be 'debezium-avro-confluent'. + +
debezium-avro-confluent.auto.register.schemas
+ optional + true + Boolean + Whether to automatically register the schema in the Confluent Schema Registry during serialization. When set to false, an identical schema must have been registered under the subject outside of Flink before it can be used; only its id is looked up and the job fails if it cannot be found. Schema registration only happens when writing data; reading always looks up the schema by the id embedded in each record. +
debezium-avro-confluent.basic-auth.credentials-source
optional