weiqingy commented on code in PR #28498:
URL: https://github.com/apache/flink/pull/28498#discussion_r3555144540


##########
flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroDeserializationSchema.java:
##########
@@ -112,11 +191,37 @@ public DebeziumAvroDeserializationSchema(
             AvroRowDataDeserializationSchema avroDeserializer) {
         this.producedTypeInfo = producedTypeInfo;
         this.avroDeserializer = avroDeserializer;
+        this.hasMetadata = false;
+        this.metadataConverters = new MetadataConverter[0];
+        this.schemaRegistryUrl = null;
+        this.registryConfigs = null;
+    }
+
+    @VisibleForTesting
+    DebeziumAvroDeserializationSchema(
+            TypeInformation<RowData> producedTypeInfo,
+            AvroRowDataDeserializationSchema avroDeserializer,
+            boolean hasMetadata,
+            MetadataConverter[] metadataConverters,
+            @Nullable RegistryAvroDeserializationSchema<GenericRecord> 
genericDeserializer) {
+        this.producedTypeInfo = producedTypeInfo;
+        this.avroDeserializer = avroDeserializer;
+        this.hasMetadata = hasMetadata;
+        this.metadataConverters = metadataConverters;
+        this.genericDeserializer = genericDeserializer;
+        this.schemaRegistryUrl = null;
+        this.registryConfigs = null;
     }
 
     @Override
     public void open(InitializationContext context) throws Exception {
         avroDeserializer.open(context);
+
+        if (hasMetadata && this.genericDeserializer == null) {
+            this.genericDeserializer =
+                    ConfluentRegistryAvroDeserializationSchema.forGeneric(
+                            null, schemaRegistryUrl, registryConfigs);

Review Comment:
   The reader schema passed here is `null`. `forGeneric(null, …)` lands in 
`AvroDeserializationSchema` with `schemaString == null`, so on the first record 
`checkAvroInitialized()` runs `new Schema.Parser().parse(null)` 
(`AvroDeserializationSchema.java:200-201`) — which NPEs (`Cannot invoke 
"String.length()" because "content" is null`, reproduced against the repo's 
Avro 1.11.5), and the outer `catch (Throwable)` at `:279-282` rewraps it as 
`Can't deserialize Debezium Avro message.`. This branch runs whenever a 
metadata column is declared (`hasMetadata == true`, wired from 
`createRuntimeDecoder` at `DebeziumAvroDecodingFormat.java:96-102`), so every 
metadata-enabled table would die on its first record. The test doesn't see it 
because it injects a `genericDeserializer` built with a real reader schema 
instead of going through `open()`. Would passing a concrete reader schema here 
— the envelope schema, or one built from the registry — be the intended wiring? 
(The missing `.open(co
 ntext)` on `genericDeserializer` is harmless on its own; lazy init covers it. 
It's the null schema that's fatal.)



##########
flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroDeserializationSchema.java:
##########
@@ -169,6 +282,45 @@ public void deserialize(byte[] message, Collector<RowData> 
out) throws IOExcepti
         }
     }
 
+    private void emitRow(
+            GenericRowData rootRow, GenericRowData physicalRow, 
Collector<RowData> out) {
+        // shortcut in case no output projection is required
+        if (!hasMetadata) {
+            out.collect(physicalRow);
+            return;
+        }
+
+        // Inject MapData at source position (4) in rootRow
+        final int sourcePosition = 4; // Stable Debezium envelope position

Review Comment:
   The root row is `[before(0), after(1), op(2)]` plus the distinct required 
Avro fields of only the *requested* metadata, in request order 
(`createDebeziumAvroRowType`, `:353-370`). So `source` sits at index 4 only 
when `ingestion-timestamp` (contributing `ts_ms`) is also requested and 
precedes any `source.*`. The metadata converters already read at the real index 
via `debeziumAvroRowType.getFieldNames().indexOf(...)` (`:174-177`); only this 
injection is hardcoded, so the two disagree for most selections.
   
   Concretely, request `source.database` alone: root is `[before, after, op, 
source]`, `source` at index 3, arity 4 — the loop's `i == 4` guard never fires, 
so the injection is skipped, position 3 keeps the empty typed `GenericRowData`, 
and `readProperty` → `row.getMap(3)` casts a `GenericRowData` to `MapData` 
(`GenericRowData.java:201-203`) → `ClassCastException`. Declaring `source.*` 
before `ingestion-timestamp` is worse: the map is injected over the `ts_ms` 
slot. The metadata test passes only because it requests all six in enum order, 
the one layout where `4` holds.
   
   One option, since the value is already computed for `rootPosition`: derive 
it the same way — `debeziumAvroRowType.getFieldNames().indexOf("source")` — and 
inject only when a `source` field is present. Happy to be redirected if there's 
a reason to keep the literal.



##########
flink-formats/flink-avro-confluent-registry/src/main/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroSerializationSchema.java:
##########
@@ -81,15 +81,15 @@ public DebeziumAvroSerializationSchema(
         validateSchemaString(schemaString, debeziumAvroRowType);
         Schema schema =
                 schemaString == null
-                        ? 
AvroSchemaConverter.convertToSchema(debeziumAvroRowType)
+                        ? 
AvroSchemaConverter.convertToSchema(debeziumAvroRowType, false)

Review Comment:
   This (and the matching `createConverter(..., false)` at `:92`) switches the 
serialization schema from the 1-arg default `legacyTimestampMapping = true` to 
`false`. The serialization row type is `ROW(before, after, op)` over the user's 
physical columns, so for any physical `TIMESTAMP(3)` sink column this changes 
the emitted Avro logical type from `timestamp-millis` to 
`local-timestamp-millis` (`AvroSchemaConverter.java:462-509`) — a registry/wire 
change for existing `debezium-avro-confluent` sinks that have a timestamp 
column, in a PR scoped to adding read metadata. `TIMESTAMP_LTZ` is a separate 
story: it used to throw, so enabling it here is a real improvement, not a 
break. Was the write path meant to change at all, or can it stay on the 1-arg 
default? If the change is intended, it likely wants a compatibility note.



##########
flink-formats/flink-avro-confluent-registry/src/test/java/org/apache/flink/formats/avro/registry/confluent/debezium/DebeziumAvroSerDeSchemaTest.java:
##########
@@ -164,6 +263,50 @@ public List<String> testDeserialization(String dataPath) 
throws Exception {
         return 
collector.list.stream().map(Object::toString).collect(Collectors.toList());
     }
 
+    private void testDeserializationWithMetadata(String dataPath, 
Consumer<RowData> testConsumer)
+            throws Exception {
+        final List<ReadableMetadata> requestedMetadata = 
Arrays.asList(ReadableMetadata.values());
+
+        final DataType producedDataType =
+                DataTypeUtils.appendRowFields(
+                        fromLogicalToDataType(rowType),
+                        requestedMetadata.stream()
+                                .map(m -> DataTypes.FIELD(m.key, m.dataType))
+                                .collect(Collectors.toList()));
+
+        RowType rowTypeDe =
+                DebeziumAvroDeserializationSchema.createDebeziumAvroRowType(
+                        fromLogicalToDataType(rowType), requestedMetadata);
+
+        client.register(SUBJECT, DEBEZIUM_SCHEMA_COMPATIBLE_TEST, 1, 81);
+
+        ConfluentSchemaRegistryCoder registryCoder =
+                new ConfluentSchemaRegistryCoder(SUBJECT, client);
+
+        RegistryAvroDeserializationSchema<GenericRecord> genericDeserializer =

Review Comment:
   Injecting a pre-built, pre-opened `genericDeserializer` here (through the 
new 5-arg `@VisibleForTesting` constructor) means the test never runs the 
production `open()`, which is where the generic deserializer gets built with a 
null reader schema. And `requestedMetadata` above is 
`Arrays.asList(ReadableMetadata.values())` (`:268`) — all six in enum order — 
which happens to be the one layout where the hardcoded `sourcePosition = 4` in 
`emitRow` is correct. Between them, the two production bugs I've flagged on 
`DebeziumAvroDeserializationSchema` both sit in the gap between this test and 
`createRuntimeDecoder`, which is why they slip through green. A case that 
drives the real `open()` path, plus one that requests `source.database` alone 
without `ingestion-timestamp`, would each catch one of them. Worth adding both?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to