[FLINK-36858][pipeline-connector][kafka] Fix JsonRowDataSerializationSchema compatibility issue with Flink 1.20
This closes #3784 Co-authored-by: Leonard Xu <xbjtdcq@gmail.com>pull/3858/head
parent
75b8a0cdf3
commit
a130718c96
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.cdc.connectors.kafka.utils;
|
||||
|
||||
import org.apache.flink.configuration.ConfigOption;
|
||||
import org.apache.flink.configuration.ReadableConfig;
|
||||
import org.apache.flink.formats.common.TimestampFormat;
|
||||
import org.apache.flink.formats.json.JsonFormatOptions;
|
||||
import org.apache.flink.formats.json.JsonRowDataSerializationSchema;
|
||||
import org.apache.flink.table.types.logical.RowType;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Utils for creating JsonRowDataSerializationSchema.TODO: Remove this class after bump to Flink
|
||||
* 1.20 or higher.
|
||||
*/
|
||||
public class JsonRowDataSerializationSchemaUtils {
|
||||
|
||||
/**
|
||||
* In flink>=1.20, the constructor of JsonRowDataSerializationSchema has 6 parameters, and in
|
||||
* flink<1.20, the constructor of JsonRowDataSerializationSchema has 5 parameters.
|
||||
*/
|
||||
public static JsonRowDataSerializationSchema createSerializationSchema(
|
||||
RowType rowType,
|
||||
TimestampFormat timestampFormat,
|
||||
JsonFormatOptions.MapNullKeyMode mapNullKeyMode,
|
||||
String mapNullKeyLiteral,
|
||||
boolean encodeDecimalAsPlainNumber,
|
||||
boolean ignoreNullFields) {
|
||||
try {
|
||||
Class<?>[] fullParams =
|
||||
new Class[] {
|
||||
RowType.class,
|
||||
TimestampFormat.class,
|
||||
JsonFormatOptions.MapNullKeyMode.class,
|
||||
String.class,
|
||||
boolean.class,
|
||||
boolean.class
|
||||
};
|
||||
|
||||
Object[] fullParamValues =
|
||||
new Object[] {
|
||||
rowType,
|
||||
timestampFormat,
|
||||
mapNullKeyMode,
|
||||
mapNullKeyLiteral,
|
||||
encodeDecimalAsPlainNumber,
|
||||
ignoreNullFields
|
||||
};
|
||||
|
||||
for (int i = fullParams.length; i >= 5; i--) {
|
||||
try {
|
||||
Constructor<?> constructor =
|
||||
JsonRowDataSerializationSchema.class.getConstructor(
|
||||
Arrays.copyOfRange(fullParams, 0, i));
|
||||
|
||||
return (JsonRowDataSerializationSchema)
|
||||
constructor.newInstance(Arrays.copyOfRange(fullParamValues, 0, i));
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to create JsonRowDataSerializationSchema,please check your Flink version is 1.19 or 1.20.",
|
||||
e);
|
||||
}
|
||||
throw new RuntimeException(
|
||||
"Failed to find appropriate constructor for JsonRowDataSerializationSchema,please check your Flink version is 1.19 or 1.20.");
|
||||
}
|
||||
|
||||
/** flink>=1.20 only has the ENCODE_IGNORE_NULL_FIELDS parameter. */
|
||||
public static boolean enableIgnoreNullFields(ReadableConfig formatOptions) {
|
||||
try {
|
||||
Field field = JsonFormatOptions.class.getField("ENCODE_IGNORE_NULL_FIELDS");
|
||||
ConfigOption<Boolean> encodeOption = (ConfigOption<Boolean>) field.get(null);
|
||||
return formatOptions.get(encodeOption);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,388 @@
|
||||
/*
|
||||
* 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.cdc.pipeline.tests;
|
||||
|
||||
import org.apache.flink.cdc.common.event.TableId;
|
||||
import org.apache.flink.cdc.common.test.utils.TestUtils;
|
||||
import org.apache.flink.cdc.connectors.kafka.sink.KafkaUtil;
|
||||
import org.apache.flink.cdc.connectors.mysql.testutils.MySqlContainer;
|
||||
import org.apache.flink.cdc.connectors.mysql.testutils.MySqlVersion;
|
||||
import org.apache.flink.cdc.connectors.mysql.testutils.UniqueDatabase;
|
||||
import org.apache.flink.cdc.pipeline.tests.utils.PipelineTestEnvironment;
|
||||
|
||||
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.apache.kafka.clients.CommonClientConfigs;
|
||||
import org.apache.kafka.clients.admin.AdminClient;
|
||||
import org.apache.kafka.clients.admin.CreateTopicsResult;
|
||||
import org.apache.kafka.clients.admin.NewTopic;
|
||||
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.common.serialization.ByteArrayDeserializer;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testcontainers.containers.KafkaContainer;
|
||||
import org.testcontainers.containers.output.Slf4jLogConsumer;
|
||||
import org.testcontainers.lifecycle.Startables;
|
||||
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.apache.flink.util.DockerImageVersions.KAFKA;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/** End-to-end tests for mysql cdc to Kafka pipeline job. */
|
||||
@RunWith(Parameterized.class)
|
||||
public class MysqlToKafkaE2eITCase extends PipelineTestEnvironment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MysqlToKafkaE2eITCase.class);
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// MySQL Variables (we always use MySQL as the data source for easier verifying)
|
||||
// ------------------------------------------------------------------------------------------
|
||||
protected static final String MYSQL_TEST_USER = "mysqluser";
|
||||
protected static final String MYSQL_TEST_PASSWORD = "mysqlpw";
|
||||
protected static final String MYSQL_DRIVER_CLASS = "com.mysql.cj.jdbc.Driver";
|
||||
protected static final String INTER_CONTAINER_MYSQL_ALIAS = "mysql";
|
||||
protected static final long EVENT_WAITING_TIMEOUT = 60000L;
|
||||
|
||||
private static AdminClient admin;
|
||||
private static final String INTER_CONTAINER_KAFKA_ALIAS = "kafka";
|
||||
private static final int ZK_TIMEOUT_MILLIS = 30000;
|
||||
private static final short TOPIC_REPLICATION_FACTOR = 1;
|
||||
private TableId table;
|
||||
private String topic;
|
||||
private KafkaConsumer<byte[], byte[]> consumer;
|
||||
|
||||
@ClassRule
|
||||
public static final MySqlContainer MYSQL =
|
||||
(MySqlContainer)
|
||||
new MySqlContainer(
|
||||
MySqlVersion.V8_0) // v8 support both ARM and AMD architectures
|
||||
.withConfigurationOverride("docker/mysql/my.cnf")
|
||||
.withSetupSQL("docker/mysql/setup.sql")
|
||||
.withDatabaseName("flink-test")
|
||||
.withUsername("flinkuser")
|
||||
.withPassword("flinkpw")
|
||||
.withNetwork(NETWORK)
|
||||
.withNetworkAliases(INTER_CONTAINER_MYSQL_ALIAS)
|
||||
.withLogConsumer(new Slf4jLogConsumer(LOG));
|
||||
|
||||
@ClassRule
|
||||
public static final KafkaContainer KAFKA_CONTAINER =
|
||||
KafkaUtil.createKafkaContainer(KAFKA, LOG)
|
||||
.withNetworkAliases("kafka")
|
||||
.withEmbeddedZookeeper()
|
||||
.withNetwork(NETWORK)
|
||||
.withNetworkAliases(INTER_CONTAINER_KAFKA_ALIAS);
|
||||
|
||||
protected final UniqueDatabase mysqlInventoryDatabase =
|
||||
new UniqueDatabase(MYSQL, "mysql_inventory", MYSQL_TEST_USER, MYSQL_TEST_PASSWORD);
|
||||
|
||||
@BeforeClass
|
||||
public static void initializeContainers() {
|
||||
LOG.info("Starting containers...");
|
||||
Startables.deepStart(Stream.of(MYSQL)).join();
|
||||
Startables.deepStart(Stream.of(KAFKA_CONTAINER)).join();
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(
|
||||
CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
|
||||
KAFKA_CONTAINER.getBootstrapServers());
|
||||
admin = AdminClient.create(properties);
|
||||
LOG.info("Containers are started.");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
createTestTopic(1, TOPIC_REPLICATION_FACTOR);
|
||||
Properties properties = getKafkaClientConfiguration();
|
||||
consumer = new KafkaConsumer<>(properties);
|
||||
consumer.subscribe(Collections.singletonList(topic));
|
||||
mysqlInventoryDatabase.createAndInitialize();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
super.after();
|
||||
admin.deleteTopics(Collections.singletonList(topic));
|
||||
consumer.close();
|
||||
mysqlInventoryDatabase.dropDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSyncWholeDatabaseWithDebeziumJson() throws Exception {
|
||||
String pipelineJob =
|
||||
String.format(
|
||||
"source:\n"
|
||||
+ " type: mysql\n"
|
||||
+ " hostname: %s\n"
|
||||
+ " port: 3306\n"
|
||||
+ " username: %s\n"
|
||||
+ " password: %s\n"
|
||||
+ " tables: %s.\\.*\n"
|
||||
+ " server-id: 5400-5404\n"
|
||||
+ " server-time-zone: UTC\n"
|
||||
+ "\n"
|
||||
+ "sink:\n"
|
||||
+ " type: kafka\n"
|
||||
+ " properties.bootstrap.servers: kafka:9092\n"
|
||||
+ " topic: %s\n"
|
||||
+ "\n"
|
||||
+ "pipeline:\n"
|
||||
+ " parallelism: %d",
|
||||
INTER_CONTAINER_MYSQL_ALIAS,
|
||||
MYSQL_TEST_USER,
|
||||
MYSQL_TEST_PASSWORD,
|
||||
mysqlInventoryDatabase.getDatabaseName(),
|
||||
topic,
|
||||
parallelism);
|
||||
Path mysqlCdcJar = TestUtils.getResource("mysql-cdc-pipeline-connector.jar");
|
||||
Path kafkaCdcJar = TestUtils.getResource("kafka-cdc-pipeline-connector.jar");
|
||||
Path mysqlDriverJar = TestUtils.getResource("mysql-driver.jar");
|
||||
submitPipelineJob(pipelineJob, mysqlCdcJar, kafkaCdcJar, mysqlDriverJar);
|
||||
waitUntilJobRunning(Duration.ofSeconds(30));
|
||||
LOG.info("Pipeline job is running");
|
||||
List<ConsumerRecord<byte[], byte[]>> collectedRecords = new ArrayList<>();
|
||||
int expectedEventCount = 13;
|
||||
waitUntilSpecificEventCount(collectedRecords, expectedEventCount);
|
||||
List<String> expectedRecords =
|
||||
getExpectedRecords("expectedEvents/mysqlToKafka/debezium-json.txt");
|
||||
assertThat(expectedRecords).containsAll(deserializeValues(collectedRecords));
|
||||
LOG.info("Begin incremental reading stage.");
|
||||
// generate binlogs
|
||||
String mysqlJdbcUrl =
|
||||
String.format(
|
||||
"jdbc:mysql://%s:%s/%s",
|
||||
MYSQL.getHost(),
|
||||
MYSQL.getDatabasePort(),
|
||||
mysqlInventoryDatabase.getDatabaseName());
|
||||
try (Connection conn =
|
||||
DriverManager.getConnection(
|
||||
mysqlJdbcUrl, MYSQL_TEST_USER, MYSQL_TEST_PASSWORD);
|
||||
Statement stat = conn.createStatement()) {
|
||||
stat.execute("UPDATE products SET description='18oz carpenter hammer' WHERE id=106;");
|
||||
stat.execute("UPDATE products SET weight='5.1' WHERE id=107;");
|
||||
|
||||
// modify table schema
|
||||
stat.execute("ALTER TABLE products ADD COLUMN new_col INT;");
|
||||
stat.execute(
|
||||
"INSERT INTO products VALUES (default,'jacket','water resistent white wind breaker',0.2, null, null, null, 1);"); // 110
|
||||
stat.execute(
|
||||
"INSERT INTO products VALUES (default,'scooter','Big 2-wheel scooter ',5.18, null, null, null, 1);"); // 111
|
||||
stat.execute(
|
||||
"UPDATE products SET description='new water resistent white wind breaker', weight='0.5' WHERE id=110;");
|
||||
stat.execute("UPDATE products SET weight='5.17' WHERE id=111;");
|
||||
stat.execute("DELETE FROM products WHERE id=111;");
|
||||
} catch (SQLException e) {
|
||||
LOG.error("Update table for CDC failed.", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
expectedEventCount = 20;
|
||||
waitUntilSpecificEventCount(collectedRecords, expectedEventCount);
|
||||
assertThat(expectedRecords)
|
||||
.containsExactlyInAnyOrderElementsOf(deserializeValues(collectedRecords));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSyncWholeDatabaseWithCanalJson() throws Exception {
|
||||
String pipelineJob =
|
||||
String.format(
|
||||
"source:\n"
|
||||
+ " type: mysql\n"
|
||||
+ " hostname: %s\n"
|
||||
+ " port: 3306\n"
|
||||
+ " username: %s\n"
|
||||
+ " password: %s\n"
|
||||
+ " tables: %s.\\.*\n"
|
||||
+ " server-id: 5400-5404\n"
|
||||
+ " server-time-zone: UTC\n"
|
||||
+ "\n"
|
||||
+ "sink:\n"
|
||||
+ " type: kafka\n"
|
||||
+ " properties.bootstrap.servers: kafka:9092\n"
|
||||
+ " topic: %s\n"
|
||||
+ " value.format: canal-json\n"
|
||||
+ "\n"
|
||||
+ "pipeline:\n"
|
||||
+ " parallelism: %d",
|
||||
INTER_CONTAINER_MYSQL_ALIAS,
|
||||
MYSQL_TEST_USER,
|
||||
MYSQL_TEST_PASSWORD,
|
||||
mysqlInventoryDatabase.getDatabaseName(),
|
||||
topic,
|
||||
parallelism);
|
||||
Path mysqlCdcJar = TestUtils.getResource("mysql-cdc-pipeline-connector.jar");
|
||||
Path kafkaCdcJar = TestUtils.getResource("kafka-cdc-pipeline-connector.jar");
|
||||
Path mysqlDriverJar = TestUtils.getResource("mysql-driver.jar");
|
||||
submitPipelineJob(pipelineJob, mysqlCdcJar, kafkaCdcJar, mysqlDriverJar);
|
||||
waitUntilJobRunning(Duration.ofSeconds(30));
|
||||
LOG.info("Pipeline job is running");
|
||||
List<ConsumerRecord<byte[], byte[]>> collectedRecords = new ArrayList<>();
|
||||
int expectedEventCount = 13;
|
||||
waitUntilSpecificEventCount(collectedRecords, expectedEventCount);
|
||||
List<String> expectedRecords =
|
||||
getExpectedRecords("expectedEvents/mysqlToKafka/canal-json.txt");
|
||||
assertThat(expectedRecords).containsAll(deserializeValues(collectedRecords));
|
||||
LOG.info("Begin incremental reading stage.");
|
||||
// generate binlogs
|
||||
String mysqlJdbcUrl =
|
||||
String.format(
|
||||
"jdbc:mysql://%s:%s/%s",
|
||||
MYSQL.getHost(),
|
||||
MYSQL.getDatabasePort(),
|
||||
mysqlInventoryDatabase.getDatabaseName());
|
||||
try (Connection conn =
|
||||
DriverManager.getConnection(
|
||||
mysqlJdbcUrl, MYSQL_TEST_USER, MYSQL_TEST_PASSWORD);
|
||||
Statement stat = conn.createStatement()) {
|
||||
stat.execute("UPDATE products SET description='18oz carpenter hammer' WHERE id=106;");
|
||||
stat.execute("UPDATE products SET weight='5.1' WHERE id=107;");
|
||||
|
||||
// modify table schema
|
||||
stat.execute("ALTER TABLE products ADD COLUMN new_col INT;");
|
||||
stat.execute(
|
||||
"INSERT INTO products VALUES (default,'jacket','water resistent white wind breaker',0.2, null, null, null, 1);"); // 110
|
||||
stat.execute(
|
||||
"INSERT INTO products VALUES (default,'scooter','Big 2-wheel scooter ',5.18, null, null, null, 1);"); // 111
|
||||
stat.execute(
|
||||
"UPDATE products SET description='new water resistent white wind breaker', weight='0.5' WHERE id=110;");
|
||||
stat.execute("UPDATE products SET weight='5.17' WHERE id=111;");
|
||||
stat.execute("DELETE FROM products WHERE id=111;");
|
||||
} catch (SQLException e) {
|
||||
LOG.error("Update table for CDC failed.", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
expectedEventCount = 20;
|
||||
waitUntilSpecificEventCount(collectedRecords, expectedEventCount);
|
||||
assertThat(expectedRecords)
|
||||
.containsExactlyInAnyOrderElementsOf(deserializeValues(collectedRecords));
|
||||
}
|
||||
|
||||
private void waitUntilSpecificEventCount(
|
||||
List<ConsumerRecord<byte[], byte[]>> actualEvent, int expectedCount) throws Exception {
|
||||
boolean result = false;
|
||||
long endTimeout = System.currentTimeMillis() + MysqlToKafkaE2eITCase.EVENT_WAITING_TIMEOUT;
|
||||
while (System.currentTimeMillis() < endTimeout) {
|
||||
ConsumerRecords<byte[], byte[]> records = consumer.poll(Duration.ofSeconds(1));
|
||||
records.forEach(actualEvent::add);
|
||||
if (actualEvent.size() == expectedCount) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
if (!result) {
|
||||
throw new TimeoutException(
|
||||
"failed to get specific event count: "
|
||||
+ expectedCount
|
||||
+ " from stdout: "
|
||||
+ actualEvent.size());
|
||||
}
|
||||
}
|
||||
|
||||
private static Properties getKafkaClientConfiguration() {
|
||||
final Properties standardProps = new Properties();
|
||||
standardProps.put("bootstrap.servers", KAFKA_CONTAINER.getBootstrapServers());
|
||||
standardProps.put("group.id", UUID.randomUUID().toString());
|
||||
standardProps.put("enable.auto.commit", false);
|
||||
standardProps.put("auto.offset.reset", "earliest");
|
||||
standardProps.put("max.partition.fetch.bytes", 256);
|
||||
standardProps.put("zookeeper.session.timeout.ms", ZK_TIMEOUT_MILLIS);
|
||||
standardProps.put("zookeeper.connection.timeout.ms", ZK_TIMEOUT_MILLIS);
|
||||
standardProps.put("key.deserializer", ByteArrayDeserializer.class.getName());
|
||||
standardProps.put("value.deserializer", ByteArrayDeserializer.class.getName());
|
||||
return standardProps;
|
||||
}
|
||||
|
||||
private void createTestTopic(int numPartitions, short replicationFactor)
|
||||
throws ExecutionException, InterruptedException {
|
||||
table =
|
||||
TableId.tableId(
|
||||
"default_namespace", "default_schema", UUID.randomUUID().toString());
|
||||
topic = table.toString();
|
||||
final CreateTopicsResult result =
|
||||
admin.createTopics(
|
||||
Collections.singletonList(
|
||||
new NewTopic(topic, numPartitions, replicationFactor)));
|
||||
result.all().get();
|
||||
}
|
||||
|
||||
private List<String> deserializeValues(List<ConsumerRecord<byte[], byte[]>> records)
|
||||
throws IOException {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (ConsumerRecord<byte[], byte[]> record : records) {
|
||||
result.add(new String(record.value(), "UTF-8"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected List<String> getExpectedRecords(String resourceDirFormat) throws Exception {
|
||||
URL url =
|
||||
MysqlToKafkaE2eITCase.class
|
||||
.getClassLoader()
|
||||
.getResource(String.format(resourceDirFormat));
|
||||
return Files.readAllLines(Paths.get(url.toURI())).stream()
|
||||
.filter(this::isValidJsonRecord)
|
||||
.map(
|
||||
line ->
|
||||
line.replace(
|
||||
"$databaseName", mysqlInventoryDatabase.getDatabaseName()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected boolean isValidJsonRecord(String line) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.readTree(line);
|
||||
return !StringUtils.isEmpty(line);
|
||||
} catch (JsonProcessingException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
||||
|
||||
{"old":null,"data":[{"id":103,"name":"user_3","address":"Shanghai","phone_number":"123567891234"}],"type":"INSERT","database":"$databaseName","table":"customers","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":104,"name":"user_4","address":"Shanghai","phone_number":"123567891234"}],"type":"INSERT","database":"$databaseName","table":"customers","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":102,"name":"user_2","address":"Shanghai","phone_number":"123567891234"}],"type":"INSERT","database":"$databaseName","table":"customers","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":101,"name":"user_1","address":"Shanghai","phone_number":"123567891234"}],"type":"INSERT","database":"$databaseName","table":"customers","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":107,"name":"rocks","description":"box of assorted rocks","weight":5.3,"enum_c":null,"json_c":null,"point_c":null}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":108,"name":"jacket","description":"water resistent black wind breaker","weight":0.1,"enum_c":null,"json_c":null,"point_c":null}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":103,"name":"12-pack drill bits","description":"12-pack of drill bits with sizes ranging from #40 to #3","weight":0.8,"enum_c":"red","json_c":"{\"key3\": \"value3\"}","point_c":"{\"coordinates\":[3,3],\"type\":\"Point\",\"srid\":0}"}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":105,"name":"hammer","description":"14oz carpenter's hammer","weight":0.875,"enum_c":"red","json_c":"{\"k1\": \"v1\", \"k2\": \"v2\"}","point_c":"{\"coordinates\":[5,5],\"type\":\"Point\",\"srid\":0}"}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":109,"name":"spare tire","description":"24 inch spare tire","weight":22.2,"enum_c":null,"json_c":null,"point_c":null}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":101,"name":"scooter","description":"Small 2-wheel scooter","weight":3.14,"enum_c":"red","json_c":"{\"key1\": \"value1\"}","point_c":"{\"coordinates\":[1,1],\"type\":\"Point\",\"srid\":0}"}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":106,"name":"hammer","description":"16oz carpenter's hammer","weight":1.0,"enum_c":null,"json_c":null,"point_c":null}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":102,"name":"car battery","description":"12V car battery","weight":8.1,"enum_c":"white","json_c":"{\"key2\": \"value2\"}","point_c":"{\"coordinates\":[2,2],\"type\":\"Point\",\"srid\":0}"}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":104,"name":"hammer","description":"12oz carpenter's hammer","weight":0.75,"enum_c":"white","json_c":"{\"key4\": \"value4\"}","point_c":"{\"coordinates\":[4,4],\"type\":\"Point\",\"srid\":0}"}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":[{"id":107,"name":"rocks","description":"box of assorted rocks","weight":5.3,"enum_c":null,"json_c":null,"point_c":null}],"data":[{"id":107,"name":"rocks","description":"box of assorted rocks","weight":5.1,"enum_c":null,"json_c":null,"point_c":null}],"type":"UPDATE","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":[{"id":106,"name":"hammer","description":"16oz carpenter's hammer","weight":1.0,"enum_c":null,"json_c":null,"point_c":null}],"data":[{"id":106,"name":"hammer","description":"18oz carpenter hammer","weight":1.0,"enum_c":null,"json_c":null,"point_c":null}],"type":"UPDATE","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":111,"name":"scooter","description":"Big 2-wheel scooter ","weight":5.18,"enum_c":null,"json_c":null,"point_c":null,"new_col":1}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":110,"name":"jacket","description":"water resistent white wind breaker","weight":0.2,"enum_c":null,"json_c":null,"point_c":null,"new_col":1}],"type":"INSERT","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":[{"id":111,"name":"scooter","description":"Big 2-wheel scooter ","weight":5.18,"enum_c":null,"json_c":null,"point_c":null,"new_col":1}],"data":[{"id":111,"name":"scooter","description":"Big 2-wheel scooter ","weight":5.17,"enum_c":null,"json_c":null,"point_c":null,"new_col":1}],"type":"UPDATE","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":null,"data":[{"id":111,"name":"scooter","description":"Big 2-wheel scooter ","weight":5.17,"enum_c":null,"json_c":null,"point_c":null,"new_col":1}],"type":"DELETE","database":"$databaseName","table":"products","pkNames":["id"]}
|
||||
{"old":[{"id":110,"name":"jacket","description":"water resistent white wind breaker","weight":0.2,"enum_c":null,"json_c":null,"point_c":null,"new_col":1}],"data":[{"id":110,"name":"jacket","description":"new water resistent white wind breaker","weight":0.5,"enum_c":null,"json_c":null,"point_c":null,"new_col":1}],"type":"UPDATE","database":"$databaseName","table":"products","pkNames":["id"]}
|
@ -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.
|
||||
*/
|
||||
|
||||
{"before":null,"after":{"id":102,"name":"user_2","address":"Shanghai","phone_number":"123567891234"},"op":"c","source":{"db":"$databaseName","table":"customers"}}
|
||||
{"before":null,"after":{"id":101,"name":"user_1","address":"Shanghai","phone_number":"123567891234"},"op":"c","source":{"db":"$databaseName","table":"customers"}}
|
||||
{"before":null,"after":{"id":103,"name":"user_3","address":"Shanghai","phone_number":"123567891234"},"op":"c","source":{"db":"$databaseName","table":"customers"}}
|
||||
{"before":null,"after":{"id":104,"name":"user_4","address":"Shanghai","phone_number":"123567891234"},"op":"c","source":{"db":"$databaseName","table":"customers"}}
|
||||
{"before":null,"after":{"id":106,"name":"hammer","description":"16oz carpenter's hammer","weight":1.0,"enum_c":null,"json_c":null,"point_c":null},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":105,"name":"hammer","description":"14oz carpenter's hammer","weight":0.875,"enum_c":"red","json_c":"{\"k1\": \"v1\", \"k2\": \"v2\"}","point_c":"{\"coordinates\":[5,5],\"type\":\"Point\",\"srid\":0}"},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":109,"name":"spare tire","description":"24 inch spare tire","weight":22.2,"enum_c":null,"json_c":null,"point_c":null},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":102,"name":"car battery","description":"12V car battery","weight":8.1,"enum_c":"white","json_c":"{\"key2\": \"value2\"}","point_c":"{\"coordinates\":[2,2],\"type\":\"Point\",\"srid\":0}"},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":101,"name":"scooter","description":"Small 2-wheel scooter","weight":3.14,"enum_c":"red","json_c":"{\"key1\": \"value1\"}","point_c":"{\"coordinates\":[1,1],\"type\":\"Point\",\"srid\":0}"},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":108,"name":"jacket","description":"water resistent black wind breaker","weight":0.1,"enum_c":null,"json_c":null,"point_c":null},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":104,"name":"hammer","description":"12oz carpenter's hammer","weight":0.75,"enum_c":"white","json_c":"{\"key4\": \"value4\"}","point_c":"{\"coordinates\":[4,4],\"type\":\"Point\",\"srid\":0}"},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":107,"name":"rocks","description":"box of assorted rocks","weight":5.3,"enum_c":null,"json_c":null,"point_c":null},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":103,"name":"12-pack drill bits","description":"12-pack of drill bits with sizes ranging from #40 to #3","weight":0.8,"enum_c":"red","json_c":"{\"key3\": \"value3\"}","point_c":"{\"coordinates\":[3,3],\"type\":\"Point\",\"srid\":0}"},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":{"id":106,"name":"hammer","description":"16oz carpenter's hammer","weight":1.0,"enum_c":null,"json_c":null,"point_c":null},"after":{"id":106,"name":"hammer","description":"18oz carpenter hammer","weight":1.0,"enum_c":null,"json_c":null,"point_c":null},"op":"u","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":{"id":107,"name":"rocks","description":"box of assorted rocks","weight":5.3,"enum_c":null,"json_c":null,"point_c":null},"after":{"id":107,"name":"rocks","description":"box of assorted rocks","weight":5.1,"enum_c":null,"json_c":null,"point_c":null},"op":"u","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":110,"name":"jacket","description":"water resistent white wind breaker","weight":0.2,"enum_c":null,"json_c":null,"point_c":null,"new_col":1},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":null,"after":{"id":111,"name":"scooter","description":"Big 2-wheel scooter ","weight":5.18,"enum_c":null,"json_c":null,"point_c":null,"new_col":1},"op":"c","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":{"id":110,"name":"jacket","description":"water resistent white wind breaker","weight":0.2,"enum_c":null,"json_c":null,"point_c":null,"new_col":1},"after":{"id":110,"name":"jacket","description":"new water resistent white wind breaker","weight":0.5,"enum_c":null,"json_c":null,"point_c":null,"new_col":1},"op":"u","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":{"id":111,"name":"scooter","description":"Big 2-wheel scooter ","weight":5.18,"enum_c":null,"json_c":null,"point_c":null,"new_col":1},"after":{"id":111,"name":"scooter","description":"Big 2-wheel scooter ","weight":5.17,"enum_c":null,"json_c":null,"point_c":null,"new_col":1},"op":"u","source":{"db":"$databaseName","table":"products"}}
|
||||
{"before":{"id":111,"name":"scooter","description":"Big 2-wheel scooter ","weight":5.17,"enum_c":null,"json_c":null,"point_c":null,"new_col":1},"after":null,"op":"d","source":{"db":"$databaseName","table":"products"}}
|
Loading…
Reference in New Issue