This is an automated email from the ASF dual-hosted git repository.
yuzelin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 05c17b150 [flink] Introduce record parser for debezium with schema
(#2730)
05c17b150 is described below
commit 05c17b150ca8c2efea1d39c3d0ea0cdd026e3e82
Author: monster <[email protected]>
AuthorDate: Tue Jan 23 11:46:38 2024 +0800
[flink] Introduce record parser for debezium with schema (#2730)
---
.../paimon/flink/action/cdc/format/DataFormat.java | 4 +-
.../flink/action/cdc/format/RecordParser.java | 4 +-
.../action/cdc/format/canal/CanalRecordParser.java | 2 +-
.../cdc/format/debezium/CdcDebeziumSchema.java | 91 +++++++++++++++++++
.../cdc/format/debezium/DebeziumRecordParser.java | 2 +-
.../DebeziumSchemaIncludeRecordParser.java | 100 +++++++++++++++++++++
.../kafka/KafkaDebeziumSyncTableActionITCase.java | 7 ++
.../cdc/kafka/KafkaSyncTableActionITCase.java | 39 ++++++++
.../table/schema/include/debezium-data-1.txt | 19 ++++
9 files changed, 263 insertions(+), 5 deletions(-)
diff --git
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/DataFormat.java
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/DataFormat.java
index 28dc3e457..5344190d4 100644
---
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/DataFormat.java
+++
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/DataFormat.java
@@ -22,6 +22,7 @@ import org.apache.paimon.flink.action.cdc.ComputedColumn;
import org.apache.paimon.flink.action.cdc.TypeMapping;
import org.apache.paimon.flink.action.cdc.format.canal.CanalRecordParser;
import org.apache.paimon.flink.action.cdc.format.debezium.DebeziumRecordParser;
+import
org.apache.paimon.flink.action.cdc.format.debezium.DebeziumSchemaIncludeRecordParser;
import org.apache.paimon.flink.action.cdc.format.maxwell.MaxwellRecordParser;
import org.apache.paimon.flink.action.cdc.format.ogg.OggRecordParser;
@@ -38,7 +39,8 @@ public enum DataFormat {
CANAL_JSON(CanalRecordParser::new),
OGG_JSON(OggRecordParser::new),
MAXWELL_JSON(MaxwellRecordParser::new),
- DEBEZIUM_JSON(DebeziumRecordParser::new);
+ DEBEZIUM_JSON(DebeziumRecordParser::new),
+ DEBEZIUM_JSON_SCHEMA_INCLUDE(DebeziumSchemaIncludeRecordParser::new);
// Add more data formats here if needed
private final RecordParserFactory parser;
diff --git
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/RecordParser.java
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/RecordParser.java
index 66d313135..91bc946c8 100644
---
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/RecordParser.java
+++
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/RecordParser.java
@@ -119,7 +119,7 @@ public abstract class RecordParser implements
FlatMapFunction<String, RichCdcMul
}
// use STRING type in default when we cannot get origin data types (most
cases)
- protected LinkedHashMap<String, DataType> fillDefaultStringTypes(JsonNode
record) {
+ protected LinkedHashMap<String, DataType> fillDefaultTypes(JsonNode
record) {
LinkedHashMap<String, DataType> fieldTypes = new LinkedHashMap<>();
record.fieldNames().forEachRemaining(name -> fieldTypes.put(name,
DataTypes.STRING()));
return fieldTypes;
@@ -138,7 +138,7 @@ public abstract class RecordParser implements
FlatMapFunction<String, RichCdcMul
protected Map<String, String> extractRowData(
JsonNode record, LinkedHashMap<String, DataType> paimonFieldTypes)
{
- paimonFieldTypes.putAll(fillDefaultStringTypes(record));
+ paimonFieldTypes.putAll(fillDefaultTypes(record));
Map<String, Object> recordMap =
JsonSerdeUtil.convertValue(record, new
TypeReference<Map<String, Object>>() {});
Map<String, String> rowData =
diff --git
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/canal/CanalRecordParser.java
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/canal/CanalRecordParser.java
index 2877b0ed3..c7cd06c18 100644
---
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/canal/CanalRecordParser.java
+++
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/canal/CanalRecordParser.java
@@ -177,7 +177,7 @@ public class CanalRecordParser extends RecordParser {
rowData.put(fieldName, newValue);
}
} else {
- paimonFieldTypes.putAll(fillDefaultStringTypes(record));
+ paimonFieldTypes.putAll(fillDefaultTypes(record));
for (Map.Entry<String, Object> entry : recordMap.entrySet()) {
rowData.put(entry.getKey(), Objects.toString(entry.getValue(),
null));
}
diff --git
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/CdcDebeziumSchema.java
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/CdcDebeziumSchema.java
new file mode 100644
index 000000000..64f3a1267
--- /dev/null
+++
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/CdcDebeziumSchema.java
@@ -0,0 +1,91 @@
+/*
+ * 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.paimon.flink.action.cdc.format.debezium;
+
+import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator;
+import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGetter;
+import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/** Cdc Debezium Schema Entity. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CdcDebeziumSchema {
+
+ private static final String FIELD_FIELDS = "fields";
+ private static final String FIELD_NAME = "name";
+ private static final String FIELD_TYPE = "type";
+ private static final String FIELD_FIELD = "field";
+ private static final String FIELD_OPTIONAL = "optional";
+
+ @JsonProperty(FIELD_FIELD)
+ private final String field;
+
+ @JsonProperty(FIELD_TYPE)
+ private final String type;
+
+ @JsonProperty(FIELD_OPTIONAL)
+ private final Boolean optional;
+
+ @JsonProperty(FIELD_FIELDS)
+ private final List<CdcDebeziumSchema> fields;
+
+ @JsonProperty(FIELD_NAME)
+ private final String name;
+
+ @JsonCreator
+ public CdcDebeziumSchema(
+ @JsonProperty(FIELD_FIELD) String field,
+ @JsonProperty(FIELD_TYPE) String type,
+ @JsonProperty(FIELD_OPTIONAL) Boolean optional,
+ @JsonProperty(FIELD_FIELDS) List<CdcDebeziumSchema> fields,
+ @JsonProperty(FIELD_NAME) String name) {
+ this.field = field;
+ this.type = type;
+ this.optional = optional;
+ this.fields = fields;
+ this.name = name;
+ }
+
+ @JsonGetter(FIELD_FIELD)
+ public String field() {
+ return field;
+ }
+
+ @JsonGetter(FIELD_TYPE)
+ public String type() {
+ return type;
+ }
+
+ @JsonGetter(FIELD_OPTIONAL)
+ public Boolean optional() {
+ return optional;
+ }
+
+ @JsonGetter(FIELD_FIELDS)
+ public List<CdcDebeziumSchema> fields() {
+ return fields;
+ }
+
+ @JsonGetter(FIELD_NAME)
+ public String name() {
+ return name;
+ }
+}
diff --git
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/DebeziumRecordParser.java
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/DebeziumRecordParser.java
index 1301bb532..a736e5b49 100644
---
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/DebeziumRecordParser.java
+++
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/DebeziumRecordParser.java
@@ -54,7 +54,7 @@ import static org.apache.paimon.utils.JsonSerdeUtil.isNull;
public class DebeziumRecordParser extends RecordParser {
private static final String FIELD_SCHEMA = "schema";
- private static final String FIELD_PAYLOAD = "payload";
+ protected static final String FIELD_PAYLOAD = "payload";
private static final String FIELD_BEFORE = "before";
private static final String FIELD_AFTER = "after";
private static final String FIELD_SOURCE = "source";
diff --git
a/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/DebeziumSchemaIncludeRecordParser.java
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/DebeziumSchemaIncludeRecordParser.java
new file mode 100644
index 000000000..8c9b3402f
--- /dev/null
+++
b/paimon-flink/paimon-flink-cdc/src/main/java/org/apache/paimon/flink/action/cdc/format/debezium/DebeziumSchemaIncludeRecordParser.java
@@ -0,0 +1,100 @@
+/*
+ * 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.paimon.flink.action.cdc.format.debezium;
+
+import org.apache.paimon.flink.action.cdc.ComputedColumn;
+import org.apache.paimon.flink.action.cdc.TypeMapping;
+import org.apache.paimon.flink.action.cdc.format.RecordParser;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.utils.JsonSerdeUtil;
+
+import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.JsonNode;
+
+import java.io.UncheckedIOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import static io.debezium.pipeline.signal.SchemaChanges.FIELD_SCHEMA;
+
+/**
+ * The {@code DebeziumSchemaIncludeRecordParser} class extends the {@link
RecordParser} to parse
+ * Debezium's change data capture (CDC) records, specifically focusing on
parsing records that
+ * include the complete Debezium schema. This inclusion of the schema allows
for a more detailed and
+ * accurate interpretation of the CDC data.
+ */
+public class DebeziumSchemaIncludeRecordParser extends DebeziumRecordParser {
+
+ private JsonNode schema;
+
+ public DebeziumSchemaIncludeRecordParser(
+ boolean caseSensitive, TypeMapping typeMapping,
List<ComputedColumn> computedColumns) {
+ super(caseSensitive, typeMapping, computedColumns);
+ }
+
+ @Override
+ protected LinkedHashMap<String, DataType> fillDefaultTypes(JsonNode
record) {
+ CdcDebeziumSchema cdcDebeziumSchema;
+ try {
+ cdcDebeziumSchema = JsonSerdeUtil.fromJson(schema.toString(),
CdcDebeziumSchema.class);
+ } catch (UncheckedIOException e) {
+ throw new RuntimeException(e);
+ }
+ LinkedHashMap<String, DataType> fieldTypes = new LinkedHashMap<>();
+ CdcDebeziumSchema field = cdcDebeziumSchema.fields().get(1);
+ for (CdcDebeziumSchema f : field.fields()) {
+ DataType dataType = toDataType(f.type());
+ fieldTypes.put(f.field(), dataType);
+ }
+ return fieldTypes;
+ }
+
+ public static DataType toDataType(String type) {
+ switch (type) {
+ case "int32":
+ return DataTypes.INT();
+ case "int64":
+ return DataTypes.BIGINT();
+ case "string":
+ return DataTypes.STRING();
+ case "float32":
+ case "float64":
+ return DataTypes.FLOAT();
+ case "double":
+ return DataTypes.DOUBLE();
+ case "bytes":
+ return DataTypes.BYTES();
+ default:
+ throw new UnsupportedOperationException(
+ String.format("Don't support type '%s' yet.", type));
+ }
+ }
+
+ @Override
+ protected void setRoot(String record) {
+ JsonNode node = JsonSerdeUtil.fromJson(record, JsonNode.class);
+ root = node.get(FIELD_PAYLOAD);
+ schema = node.get(FIELD_SCHEMA);
+ }
+
+ @Override
+ protected String format() {
+ return "debezium_json_schema_include";
+ }
+}
diff --git
a/paimon-flink/paimon-flink-cdc/src/test/java/org/apache/paimon/flink/action/cdc/kafka/KafkaDebeziumSyncTableActionITCase.java
b/paimon-flink/paimon-flink-cdc/src/test/java/org/apache/paimon/flink/action/cdc/kafka/KafkaDebeziumSyncTableActionITCase.java
index bb9061761..e5e030a60 100644
---
a/paimon-flink/paimon-flink-cdc/src/test/java/org/apache/paimon/flink/action/cdc/kafka/KafkaDebeziumSyncTableActionITCase.java
+++
b/paimon-flink/paimon-flink-cdc/src/test/java/org/apache/paimon/flink/action/cdc/kafka/KafkaDebeziumSyncTableActionITCase.java
@@ -25,6 +25,7 @@ import org.junit.jupiter.api.Timeout;
public class KafkaDebeziumSyncTableActionITCase extends
KafkaSyncTableActionITCase {
private static final String DEBEZIUM = "debezium";
+ private static final String DEBEZIUM_JSON_SCHEMA_INCLUDE =
"debezium_json_schema_include";
@Test
@Timeout(60)
@@ -91,4 +92,10 @@ public class KafkaDebeziumSyncTableActionITCase extends
KafkaSyncTableActionITCa
public void testKafkaBuildSchemaWithDelete() throws Exception {
testKafkaBuildSchemaWithDelete(DEBEZIUM);
}
+
+ @Test
+ @Timeout(60)
+ public void testSchemaIncludeRecord1() throws Exception {
+ testSchemaIncludeRecord(DEBEZIUM_JSON_SCHEMA_INCLUDE);
+ }
}
diff --git
a/paimon-flink/paimon-flink-cdc/src/test/java/org/apache/paimon/flink/action/cdc/kafka/KafkaSyncTableActionITCase.java
b/paimon-flink/paimon-flink-cdc/src/test/java/org/apache/paimon/flink/action/cdc/kafka/KafkaSyncTableActionITCase.java
index f025e9c96..aabb5d53c 100644
---
a/paimon-flink/paimon-flink-cdc/src/test/java/org/apache/paimon/flink/action/cdc/kafka/KafkaSyncTableActionITCase.java
+++
b/paimon-flink/paimon-flink-cdc/src/test/java/org/apache/paimon/flink/action/cdc/kafka/KafkaSyncTableActionITCase.java
@@ -663,4 +663,43 @@ public class KafkaSyncTableActionITCase extends
KafkaActionITCaseBase {
Thread.sleep(1000);
}
}
+
+ public void testSchemaIncludeRecord(String format) throws Exception {
+ String topic = "schema_include";
+ createTestTopic(topic, 1, 1);
+
+ List<String> lines =
readLines("kafka/debezium/table/schema/include/debezium-data-1.txt");
+ try {
+ writeRecordsToKafka(topic, lines);
+ } catch (Exception e) {
+ throw new Exception("Failed to write debezium data to Kafka.", e);
+ }
+
+ Map<String, String> kafkaConfig = getBasicKafkaConfig();
+ kafkaConfig.put(VALUE_FORMAT.key(), format);
+ kafkaConfig.put(TOPIC.key(), topic);
+ KafkaSyncTableAction action =
+ syncTableActionBuilder(kafkaConfig)
+ .withPrimaryKeys("id")
+ .withTableConfig(getBasicTableConfig())
+ .build();
+ runActionWithDefaultEnv(action);
+
+ FileStoreTable table = getFileStoreTable(tableName);
+
+ RowType rowType =
+ RowType.of(
+ new DataType[] {
+ DataTypes.INT().notNull(),
+ DataTypes.STRING(),
+ DataTypes.STRING(),
+ DataTypes.DOUBLE()
+ },
+ new String[] {"id", "name", "description", "weight"});
+ List<String> primaryKeys = Collections.singletonList("id");
+ List<String> expected =
+ Collections.singletonList(
+ "+I[101, scooter, Small 2-wheel scooter,
3.140000104904175]");
+ waitForResult(expected, table, rowType, primaryKeys);
+ }
}
diff --git
a/paimon-flink/paimon-flink-cdc/src/test/resources/kafka/debezium/table/schema/include/debezium-data-1.txt
b/paimon-flink/paimon-flink-cdc/src/test/resources/kafka/debezium/table/schema/include/debezium-data-1.txt
new file mode 100644
index 000000000..417ba5567
--- /dev/null
+++
b/paimon-flink/paimon-flink-cdc/src/test/resources/kafka/debezium/table/schema/include/debezium-data-1.txt
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+{"schema":{"type":"struct","fields":[{"type":"struct","fields":[{"type":"int32","optional":false,"field":"id"},{"type":"string","optional":false,"field":"name"},{"type":"string","optional":true,"field":"description"},{"type":"double","optional":true,"field":"weight"}],"optional":true,"name":"dbserver1.inventory.products.Value","field":"before"},{"type":"struct","fields":[{"type":"int32","optional":false,"field":"id"},{"type":"string","optional":false,"field":"name"},{"type":"string","opt
[...]
\ No newline at end of file