This is an automated email from the ASF dual-hosted git repository.
mikexue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/eventmesh.git
The following commit(s) were added to refs/heads/master by this push:
new ce15df9b6 [ISSUE #4439]Support source connector jdbc handle CDC for
mysql (#4457)
ce15df9b6 is described below
commit ce15df9b6309a7a9f6bb2c43a44faf317be81e69
Author: mxsm <[email protected]>
AuthorDate: Tue Sep 26 14:40:37 2023 +0800
[ISSUE #4439]Support source connector jdbc handle CDC for mysql (#4457)
* [ISSUE #4439]Support source connector jdbc handle CDC for mysql
* fix code style
---
.../apache/eventmesh/common/utils/LogUtils.java | 214 ++++++
.../eventmesh/connector/jdbc/DataChanges.java | 71 ++
.../eventmesh/connector/jdbc/JdbcConnectData.java | 29 +
.../apache/eventmesh/connector/jdbc/Payload.java | 21 +-
.../apache/eventmesh/connector/jdbc/Schema.java | 17 +-
.../connector/jdbc/event/DeleteDataEvent.java | 2 +-
.../jdbc/event/GeneralDataChangeEvent.java | 3 +-
.../jdbc/event/GeneralSchemaChangeEvent.java | 2 +-
.../connector/jdbc/event/UpdateDataEvent.java | 2 +-
.../connector/jdbc/source/JdbcSourceConnector.java | 21 +-
.../jdbc/source/SourceEventMeshJdbcEventTask.java | 6 +-
.../listener/ColumnDefinitionParserListener.java | 3 +-
.../mysql/listener/CreateTableParserListener.java | 2 +
.../mysql/listener/TableBaseParserListener.java | 10 +-
.../jdbc/source/dialect/cdc/AbstractCdcEngine.java | 134 ++++
.../dialect/cdc/AbstractCdcEngineFactory.java} | 14 +-
.../source/dialect/cdc/mysql/MysqlCdcEngine.java | 809 +++++++++++++++++++++
.../dialect/cdc/mysql/MysqlCdcEngineFactory.java | 54 ++
.../source/dialect/cdc/mysql/RowDeserializers.java | 452 ++++++++++++
.../mysql/{GtidSet.java => EventMeshGtidSet.java} | 34 +-
.../dialect/mysql/MysqlConstants.java} | 27 +-
.../source/dialect/mysql/MysqlJdbcContext.java | 28 +-
.../jdbc/source/dialect/mysql/MysqlSourceInfo.java | 9 +-
.../dialect/snapshot/AbstractSnapshotEngine.java | 36 +-
.../snapshot/mysql/MysqlSnapshotEngine.java | 15 +-
.../table/catalog/AbstractColumnEditorImpl.java | 19 +
.../connector/jdbc/table/catalog/ColumnEditor.java | 8 +
.../connector/jdbc/table/catalog/TableId.java | 4 +
.../connector/jdbc/table/catalog/TableSchema.java | 23 +-
.../table/catalog/mysql/MysqlColumnEditorImpl.java | 2 +-
.../jdbc/table/catalog/mysql/MysqlTableSchema.java | 7 +-
...nector.jdbc.source.dialect.cdc.CdcEngineFactory | 2 +-
32 files changed, 1966 insertions(+), 114 deletions(-)
diff --git
a/eventmesh-common/src/main/java/org/apache/eventmesh/common/utils/LogUtils.java
b/eventmesh-common/src/main/java/org/apache/eventmesh/common/utils/LogUtils.java
new file mode 100644
index 000000000..227cdabbd
--- /dev/null
+++
b/eventmesh-common/src/main/java/org/apache/eventmesh/common/utils/LogUtils.java
@@ -0,0 +1,214 @@
+/*
+ * 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.eventmesh.common.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public final class LogUtils {
+
+ public static boolean isTraceEnabled(Logger logger) {
+ return logger.isTraceEnabled();
+ }
+
+ public static boolean isTraceEnabled(Logger logger, Marker marker) {
+ return logger.isTraceEnabled(marker);
+ }
+
+ public static void trace(Logger logger, String msg) {
+ if (isTraceEnabled(logger)) {
+ logger.trace(msg);
+ }
+ }
+
+ public static void trace(Logger logger, String format, Object arg) {
+ if (isTraceEnabled(logger)) {
+ logger.trace(format, arg);
+ }
+ }
+
+ public static void trace(Logger logger, String msg, Throwable t) {
+ if (isTraceEnabled(logger)) {
+ logger.trace(msg, t);
+ }
+ }
+
+ public static void trace(Logger logger, String format, Object...
arguments) {
+ if (isTraceEnabled(logger)) {
+ logger.trace(format, arguments);
+ }
+ }
+
+ public static void trace(Logger logger, String format, Object arg1, Object
arg2) {
+ if (isTraceEnabled(logger)) {
+ logger.trace(format, arg1, arg2);
+ }
+ }
+
+ public static void trace(Logger logger, Marker marker, String msg) {
+ if (isTraceEnabled(logger, marker)) {
+ logger.trace(marker, msg);
+ }
+ }
+
+ public static void trace(Logger logger, Marker marker, String format,
Object arg) {
+ if (isTraceEnabled(logger, marker)) {
+ logger.trace(marker, format, arg);
+ }
+ }
+
+ public static boolean isDebugEnabled(Logger logger) {
+ return logger.isDebugEnabled();
+ }
+
+ public static void debug(Logger logger, String msg) {
+ if (isDebugEnabled(logger)) {
+ logger.debug(msg);
+ }
+ }
+
+ public static void debug(Logger logger, String format, Object arg) {
+ if (isDebugEnabled(logger)) {
+ logger.debug(format, arg);
+ }
+ }
+
+ public static void debug(Logger logger, String format, Object arg1, Object
arg2) {
+ if (isDebugEnabled(logger)) {
+ logger.debug(format, arg1, arg2);
+ }
+ }
+
+ public static void debug(Logger logger, String format, Object...
arguments) {
+ if (isDebugEnabled(logger)) {
+ logger.debug(format, arguments);
+ }
+ }
+
+ public static void debug(Logger logger, String msg, Throwable t) {
+ if (isDebugEnabled(logger)) {
+ logger.debug(msg, t);
+ }
+ }
+
+ public static boolean isInfoEnabled(Logger logger) {
+ return logger.isInfoEnabled();
+ }
+
+ public static void info(Logger logger, String msg) {
+ if (isInfoEnabled(logger)) {
+ logger.info(msg);
+ }
+ }
+
+ public static void info(Logger logger, String format, Object arg) {
+ if (isInfoEnabled(logger)) {
+ logger.info(format, arg);
+ }
+ }
+
+ public static void info(Logger logger, String format, Object arg1, Object
arg2) {
+ if (isInfoEnabled(logger)) {
+ logger.info(format, arg1, arg2);
+ }
+ }
+
+ public static void info(Logger logger, String format, Object... arguments)
{
+ if (isInfoEnabled(logger)) {
+ logger.info(format, arguments);
+ }
+ }
+
+ public static void info(Logger logger, String msg, Throwable t) {
+ if (isInfoEnabled(logger)) {
+ logger.info(msg, t);
+ }
+ }
+
+ public static boolean isWarnEnabled(Logger logger) {
+ return logger.isWarnEnabled();
+ }
+
+ public static void warn(Logger logger, String msg) {
+ if (isWarnEnabled(logger)) {
+ logger.warn(msg);
+ }
+ }
+
+ public static void warn(Logger logger, String format, Object arg) {
+ if (isWarnEnabled(logger)) {
+ logger.warn(format, arg);
+ }
+ }
+
+ public static void warn(Logger logger, String format, Object arg1, Object
arg2) {
+ if (isWarnEnabled(logger)) {
+ logger.warn(format, arg1, arg2);
+ }
+ }
+
+ public static void warn(Logger logger, String format, Object... arguments)
{
+ if (isWarnEnabled(logger)) {
+ logger.warn(format, arguments);
+ }
+ }
+
+ public static void warn(Logger logger, String msg, Throwable t) {
+ if (isWarnEnabled(logger)) {
+ logger.warn(msg, t);
+ }
+ }
+
+ public static boolean isErrorEnabled(Logger logger) {
+ return logger.isErrorEnabled();
+ }
+
+ public static void error(Logger logger, String msg) {
+ if (isErrorEnabled(logger)) {
+ logger.error(msg);
+ }
+ }
+
+ public static void error(Logger logger, String format, Object arg) {
+ if (isErrorEnabled(logger)) {
+ logger.error(format, arg);
+ }
+ }
+
+ public static void error(Logger logger, String format, Object arg1, Object
arg2) {
+ if (isErrorEnabled(logger)) {
+ logger.error(format, arg1, arg2);
+ }
+ }
+
+ public static void error(Logger logger, String format, Object...
arguments) {
+ if (isErrorEnabled(logger)) {
+ logger.error(format, arguments);
+ }
+ }
+
+ public static void error(Logger logger, String msg, Throwable t) {
+ if (isErrorEnabled(logger)) {
+ logger.error(msg, t);
+ }
+ }
+
+}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/DataChanges.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/DataChanges.java
new file mode 100644
index 000000000..cb0298e91
--- /dev/null
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/DataChanges.java
@@ -0,0 +1,71 @@
+/*
+ * 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.eventmesh.connector.jdbc;
+
+import lombok.Data;
+
+@Data
+public class DataChanges {
+
+ private Object after;
+
+ private Object before;
+
+ private String type;
+
+ public DataChanges(Object after, Object before) {
+ this.after = after;
+ this.before = before;
+ }
+
+ public DataChanges(Object after, Object before, String type) {
+ this.after = after;
+ this.before = before;
+ this.type = type;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private String type;
+ private Object after;
+ private Object before;
+
+ public Builder withType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ public Builder withAfter(Object after) {
+ this.after = after;
+ return this;
+ }
+
+ public Builder withBefore(Object before) {
+ this.before = before;
+ return this;
+ }
+
+ public DataChanges build() {
+ return new DataChanges(after, before, type);
+ }
+ }
+}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/JdbcConnectData.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/JdbcConnectData.java
index f70819212..3dca4f42f 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/JdbcConnectData.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/JdbcConnectData.java
@@ -19,10 +19,23 @@ package org.apache.eventmesh.connector.jdbc;
public final class JdbcConnectData {
+ public static final byte DATA_CHANGES = 1;
+
+ public static final byte SCHEMA_CHANGES = 1 << 1;
+
private Payload payload = new Payload();
private Schema schema;
+ private byte type;
+
+ public JdbcConnectData() {
+ }
+
+ public JdbcConnectData(byte type) {
+ this.type = type;
+ }
+
public Payload getPayload() {
return payload;
}
@@ -38,4 +51,20 @@ public final class JdbcConnectData {
public void setSchema(Schema schema) {
this.schema = schema;
}
+
+ public byte getType() {
+ return type;
+ }
+
+ public void setType(byte type) {
+ this.type = type;
+ }
+
+ public void markDataChanges() {
+ this.type |= DATA_CHANGES;
+ }
+
+ public void markSchemaChanges() {
+ this.type |= SCHEMA_CHANGES;
+ }
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Payload.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Payload.java
index 262648c5e..47a5b42fe 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Payload.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Payload.java
@@ -23,6 +23,15 @@ import java.util.HashMap;
public final class Payload extends HashMap<String, Object> {
+ public static final String AFTER_FIELD = "after";
+
+ public static final String BEFORE_FIELD = "before";
+
+ public static final String SOURCE = "source";
+
+ public static final String DDL = "ddl";
+
+
/**
* Constructs an empty <code>HashMap</code> with the default initial
capacity (16) and the default load factor (0.75).
*/
@@ -31,12 +40,12 @@ public final class Payload extends HashMap<String, Object> {
}
public Payload withSource(SourceMateData source) {
- this.put("source", source);
+ this.put(SOURCE, source);
return this;
}
public Payload withDdl(String ddl) {
- this.put("ddl", ddl);
+ this.put(DDL, ddl);
return this;
}
@@ -45,13 +54,13 @@ public final class Payload extends HashMap<String, Object> {
return this;
}
- public Payload withAfterValue(Object value) {
- this.put("after", value);
+ public Payload withDataChanges(DataChanges dataChanges) {
+ this.put("dataChanges", dataChanges);
return this;
}
public SourceMateData ofSourceMateData() {
- return (SourceMateData) super.get("source");
+ return (SourceMateData) super.get(SOURCE);
}
public static Builder builder() {
@@ -72,7 +81,7 @@ public final class Payload extends HashMap<String, Object> {
}
public Builder withSource(SourceMateData source) {
- payload.put("source", source);
+ payload.put(SOURCE, source);
return this;
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Schema.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Schema.java
index 284bc14b1..084f9759b 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Schema.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Schema.java
@@ -17,16 +17,25 @@
package org.apache.eventmesh.connector.jdbc;
+import java.util.ArrayList;
import java.util.List;
-import lombok.AllArgsConstructor;
import lombok.Data;
-import lombok.NoArgsConstructor;
@Data
-@AllArgsConstructor
-@NoArgsConstructor
public class Schema {
private List<Field> fields;
+
+ public Schema(List<Field> fields) {
+ this.fields = fields;
+ }
+
+ public Schema() {
+ this.fields = new ArrayList<>();
+ }
+
+ public void add(Field field) {
+ this.fields.add(field);
+ }
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/DeleteDataEvent.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/DeleteDataEvent.java
index 68f23ece8..9e0d38e49 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/DeleteDataEvent.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/DeleteDataEvent.java
@@ -33,6 +33,6 @@ public class DeleteDataEvent extends GeneralDataChangeEvent {
@Override
public DataChangeEventType getDataChangeEventType() {
- return DataChangeEventType.INSERT;
+ return DataChangeEventType.DELETE;
}
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/GeneralDataChangeEvent.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/GeneralDataChangeEvent.java
index f7bcf24d3..c6a922237 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/GeneralDataChangeEvent.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/GeneralDataChangeEvent.java
@@ -22,9 +22,8 @@ import
org.apache.eventmesh.connector.jdbc.table.catalog.TableId;
public abstract class GeneralDataChangeEvent extends AbstractEvent implements
DataChangeEvent {
-
public GeneralDataChangeEvent(TableId tableId) {
- super(tableId);
+ super(tableId, new JdbcConnectData(JdbcConnectData.DATA_CHANGES));
}
public GeneralDataChangeEvent(TableId tableId, JdbcConnectData data) {
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/GeneralSchemaChangeEvent.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/GeneralSchemaChangeEvent.java
index f07fee84b..80996203c 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/GeneralSchemaChangeEvent.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/GeneralSchemaChangeEvent.java
@@ -23,7 +23,7 @@ import
org.apache.eventmesh.connector.jdbc.table.catalog.TableId;
public abstract class GeneralSchemaChangeEvent extends AbstractEvent
implements SchemaChangeEvent {
public GeneralSchemaChangeEvent(TableId tableId) {
- super(tableId);
+ super(tableId, new JdbcConnectData(JdbcConnectData.SCHEMA_CHANGES));
}
public GeneralSchemaChangeEvent(TableId tableId, JdbcConnectData data) {
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/UpdateDataEvent.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/UpdateDataEvent.java
index dc1ea9c45..d6c50859b 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/UpdateDataEvent.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/event/UpdateDataEvent.java
@@ -36,7 +36,7 @@ public class UpdateDataEvent extends GeneralDataChangeEvent {
@Override
public DataChangeEventType getDataChangeEventType() {
- return DataChangeEventType.INSERT;
+ return DataChangeEventType.UPDATE;
}
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/JdbcSourceConnector.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/JdbcSourceConnector.java
index 618f56339..bc3b55006 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/JdbcSourceConnector.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/JdbcSourceConnector.java
@@ -22,6 +22,7 @@ import org.apache.eventmesh.connector.jdbc.event.Event;
import org.apache.eventmesh.connector.jdbc.source.config.JdbcSourceConfig;
import
org.apache.eventmesh.connector.jdbc.source.dialect.DatabaseDialectFactory;
import org.apache.eventmesh.connector.jdbc.source.dialect.cdc.CdcEngine;
+import org.apache.eventmesh.connector.jdbc.source.dialect.cdc.CdcEngineFactory;
import
org.apache.eventmesh.connector.jdbc.source.dialect.snapshot.SnapshotEngine;
import
org.apache.eventmesh.connector.jdbc.source.dialect.snapshot.SnapshotEngineFactory;
import
org.apache.eventmesh.connector.jdbc.source.dialect.snapshot.SnapshotResult;
@@ -34,6 +35,8 @@ import
org.apache.eventmesh.openconnect.api.connector.SourceConnector;
import org.apache.eventmesh.openconnect.api.connector.SourceConnectorContext;
import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+import org.apache.commons.collections4.CollectionUtils;
+
import java.util.List;
import java.util.Set;
@@ -117,6 +120,20 @@ public class JdbcSourceConnector extends SourceConnector {
this.snapshotEngine.registerSnapshotEventConsumer(this::eventConsumer);
this.snapshotEngine.init();
+ // Get the CDC engine factory and create the CDC engine.
+ final CdcEngineFactory cdcEngineFactory =
JdbcAllFactoryLoader.getCdcEngineFactory(databaseType);
+ // Check if the CDC engine factory supports the JDBC protocol.
+ if
(!cdcEngineFactory.acceptJdbcProtocol(this.databaseDialect.jdbcProtocol())) {
+ throw new IllegalArgumentException("CdcEngineFactory not supports
" + databaseType);
+ }
+ // Set the CDC engine and register the CDC event consumer.
+ this.cdcEngine = cdcEngineFactory.createCdcEngine(this.sourceConfig,
this.databaseDialect);
+ if (CollectionUtils.isEmpty(this.cdcEngine.getHandledTables())) {
+ throw new RuntimeException("No database tables need to be
processed");
+ }
+ this.cdcEngine.registerCdcEventConsumer(this::eventConsumer);
+ this.cdcEngine.init();
+
Set<TableId> handledTables = this.snapshotEngine.getHandledTables();
// Create the task manager and dispatcher.
@@ -150,8 +167,8 @@ public class JdbcSourceConnector extends SourceConnector {
//success and skip status can run cdc engine
if (result.getStatus() != SnapshotResultStatus.ABORTED) {
log.info("Start Cdc Engine to handle cdc event");
- /*this.cdcEngine.setContext(result.getContext());
- this.cdcEngine.start();*/
+ this.cdcEngine.setContext(result.getContext());
+ this.cdcEngine.start();
}
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/SourceEventMeshJdbcEventTask.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/SourceEventMeshJdbcEventTask.java
index 7c2b0d717..f24cccc65 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/SourceEventMeshJdbcEventTask.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/SourceEventMeshJdbcEventTask.java
@@ -53,11 +53,11 @@ public class SourceEventMeshJdbcEventTask extends
AbstractEventMeshJdbcEventTask
public void run() {
while (isRunning) {
try {
- Event snapshotEvent = eventBlockingQueue.poll(5,
TimeUnit.SECONDS);
- if (Objects.isNull(snapshotEvent)) {
+ Event event = eventBlockingQueue.poll(5, TimeUnit.SECONDS);
+ if (Objects.isNull(event)) {
continue;
}
- eventHandler.handle(snapshotEvent);
+ eventHandler.handle(event);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/ColumnDefinitionParserListener.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/ColumnDefinitionParserListener.java
index 499ac0910..4877360f9 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/ColumnDefinitionParserListener.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/ColumnDefinitionParserListener.java
@@ -130,8 +130,7 @@ public class ColumnDefinitionParserListener extends
MySqlParserBaseListener {
@SuppressWarnings("unchecked")
public void exitColumnDefinition(ColumnDefinitionContext ctx) {
if (!ignoreColumn.get().booleanValue()) {
- this.tableEditor.addColumns(columnEditor.build());
- ignoreColumn.set(false);
+ this.ignoreColumn.set(false);
}
//When exit column definition needs to remove
DefaultValueParserListener from listener list
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/CreateTableParserListener.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/CreateTableParserListener.java
index 2ed93cfba..cf117952b 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/CreateTableParserListener.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/CreateTableParserListener.java
@@ -120,6 +120,8 @@ public class CreateTableParserListener extends
TableBaseParserListener {
payload.withSource(sourceMateData).withDdl(ddl).withCatalogChanges(changes);
parser.handleEvent(event);
}, tableEditor);
+ //reset column order
+ columnOrder.set(1);
super.exitColumnCreateTable(ctx);
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/TableBaseParserListener.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/TableBaseParserListener.java
index b978214b1..98d35fa6a 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/TableBaseParserListener.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/antlr4/mysql/listener/TableBaseParserListener.java
@@ -35,6 +35,7 @@ import org.apache.commons.collections4.CollectionUtils;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.antlr.v4.runtime.tree.ParseTreeListener;
@@ -49,6 +50,8 @@ public class TableBaseParserListener extends
MySqlParserBaseListener {
protected ColumnDefinitionParserListener columnDefinitionListener;
+ protected AtomicInteger columnOrder = new AtomicInteger(1);
+
public TableBaseParserListener(List<ParseTreeListener> listeners,
MysqlAntlr4DdlParser parser) {
this.listeners = listeners;
this.parser = parser;
@@ -95,8 +98,11 @@ public class TableBaseParserListener extends
MySqlParserBaseListener {
@Override
public void exitColumnDeclaration(ColumnDeclarationContext ctx) {
- parser.runIfAllNotNull(() ->
tableEditor.addColumns(columnDefinitionListener.getColumnEditor().build()),
tableEditor,
- columnDefinitionListener);
+ parser.runIfAllNotNull(() -> {
+ MysqlColumnEditor columnEditor =
columnDefinitionListener.getColumnEditor();
+ columnEditor.withOrder(columnOrder.getAndIncrement());
+ tableEditor.addColumns(columnEditor.build());
+ }, tableEditor, columnDefinitionListener);
super.exitColumnDeclaration(ctx);
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/AbstractCdcEngine.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/AbstractCdcEngine.java
new file mode 100644
index 000000000..c164cb7ee
--- /dev/null
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/AbstractCdcEngine.java
@@ -0,0 +1,134 @@
+/*
+ * 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.eventmesh.connector.jdbc.source.dialect.cdc;
+
+
+import org.apache.eventmesh.common.ThreadWrapper;
+import org.apache.eventmesh.connector.jdbc.DatabaseDialect;
+import org.apache.eventmesh.connector.jdbc.JdbcContext;
+import org.apache.eventmesh.connector.jdbc.ddl.DdlParser;
+import org.apache.eventmesh.connector.jdbc.source.config.JdbcSourceConfig;
+import org.apache.eventmesh.connector.jdbc.source.config.SourceConnectorConfig;
+import org.apache.eventmesh.connector.jdbc.table.catalog.TableId;
+import org.apache.eventmesh.openconnect.api.config.SourceConfig;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public abstract class AbstractCdcEngine<Parse extends DdlParser, Ctx extends
JdbcContext, DbDialect extends DatabaseDialect> extends
+ ThreadWrapper implements CdcEngine<Ctx> {
+
+ protected final JdbcSourceConfig jdbcSourceConfig;
+
+ protected final DbDialect databaseDialect;
+
+ protected final SourceConnectorConfig sourceConnectorConfig;
+
+ private final Set<TableId> includeDatabaseTable = new HashSet<>(64);
+
+ public AbstractCdcEngine(SourceConfig config, DbDialect databaseDialect) {
+ if (!(config instanceof JdbcSourceConfig)) {
+ throw new IllegalArgumentException("config ");
+ }
+ this.jdbcSourceConfig = (JdbcSourceConfig) config;
+ this.databaseDialect = databaseDialect;
+ this.sourceConnectorConfig =
this.jdbcSourceConfig.getSourceConnectorConfig();
+
+ calculateNeedHandleTable();
+ }
+
+ @Override
+ public Set<TableId> getHandledTables() {
+ return includeDatabaseTable;
+ }
+
+
+ protected Set<TableId> calculateNeedHandleTable() {
+ // Get the database and table include and exclude lists from the
connector configuration
+ List<String> databaseIncludeList =
sourceConnectorConfig.getDatabaseIncludeList();
+
+ // If the database include list is empty, get a list of all databases
and use that as the include list
+ if (CollectionUtils.isEmpty(databaseIncludeList)) {
+ List<String> allDatabases = databaseDialect.listDatabases();
+ databaseIncludeList = new ArrayList<>(allDatabases);
+ }
+ Set<String> defaultExcludeDatabase = defaultExcludeDatabase();
+ if (CollectionUtils.isNotEmpty(defaultExcludeDatabase)) {
+ databaseIncludeList.removeAll(defaultExcludeDatabase);
+ }
+
+ List<String> databaseExcludeList =
sourceConnectorConfig.getDatabaseExcludeList();
+ // Remove the default excluded databases from the include list
+ if (CollectionUtils.isNotEmpty(databaseExcludeList)) {
+ databaseIncludeList.removeAll(databaseExcludeList);
+ }
+
+ List<String> tableIncludeList =
sourceConnectorConfig.getTableIncludeList();
+ // Create a list of included tables based on the table include list
+ List<TableId> includeTableList = new ArrayList<>();
+ if (CollectionUtils.isNotEmpty(tableIncludeList)) {
+ List<TableId> tableIdList = buildTableId(tableIncludeList);
+ includeTableList.addAll(tableIdList);
+ }
+
+ // If the table include list is empty, get a list of all tables for
each database in the include list
+ if (CollectionUtils.isEmpty(tableIncludeList)) {
+ for (String database : databaseIncludeList) {
+ try {
+ List<TableId> tableIds =
databaseDialect.listTables(database);
+ includeTableList.addAll(tableIds);
+ } catch (SQLException e) {
+ log.warn("List database[{}] table error", database, e);
+ }
+ }
+ }
+
+ List<String> tableExcludeList =
sourceConnectorConfig.getTableExcludeList();
+ // Remove any tables in the exclude list from the included tables list
+ if (CollectionUtils.isNotEmpty(tableExcludeList)) {
+ includeTableList.removeAll(buildTableId(tableExcludeList));
+ }
+
+ includeDatabaseTable.addAll(includeTableList);
+
+ return includeDatabaseTable;
+ }
+
+
+ private List<TableId> buildTableId(List<String> tables) {
+ return Optional.ofNullable(tables).orElse(new
ArrayList<>(0)).stream().map(table -> {
+ String[] split = table.split("\\.");
+ return new TableId(split[0], null, split[1]);
+ }).collect(Collectors.toList());
+ }
+
+ protected abstract Set<String> defaultExcludeDatabase();
+
+ protected abstract Parse getDdlParser();
+}
+
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Schema.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/AbstractCdcEngineFactory.java
similarity index 75%
copy from
eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Schema.java
copy to
eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/AbstractCdcEngineFactory.java
index 284bc14b1..d00d4c56f 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/Schema.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/AbstractCdcEngineFactory.java
@@ -15,18 +15,8 @@
* limitations under the License.
*/
-package org.apache.eventmesh.connector.jdbc;
+package org.apache.eventmesh.connector.jdbc.source.dialect.cdc;
-import java.util.List;
+public abstract class AbstractCdcEngineFactory implements CdcEngineFactory {
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class Schema {
-
- private List<Field> fields;
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/mysql/MysqlCdcEngine.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/mysql/MysqlCdcEngine.java
new file mode 100644
index 000000000..8a29bf420
--- /dev/null
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/mysql/MysqlCdcEngine.java
@@ -0,0 +1,809 @@
+/*
+ * 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.eventmesh.connector.jdbc.source.dialect.cdc.mysql;
+
+import org.apache.eventmesh.common.EventMeshThreadFactory;
+import org.apache.eventmesh.common.utils.LogUtils;
+import org.apache.eventmesh.connector.jdbc.DataChanges;
+import org.apache.eventmesh.connector.jdbc.DataChanges.Builder;
+import org.apache.eventmesh.connector.jdbc.Field;
+import org.apache.eventmesh.connector.jdbc.Payload;
+import org.apache.eventmesh.connector.jdbc.Schema;
+import org.apache.eventmesh.connector.jdbc.config.JdbcConfig;
+import
org.apache.eventmesh.connector.jdbc.connection.mysql.MysqlJdbcConnection;
+import org.apache.eventmesh.connector.jdbc.event.DeleteDataEvent;
+import org.apache.eventmesh.connector.jdbc.event.EventConsumer;
+import org.apache.eventmesh.connector.jdbc.event.GeneralDataChangeEvent;
+import org.apache.eventmesh.connector.jdbc.event.InsertDataEvent;
+import org.apache.eventmesh.connector.jdbc.event.UpdateDataEvent;
+import org.apache.eventmesh.connector.jdbc.source.config.JdbcSourceConfig;
+import org.apache.eventmesh.connector.jdbc.source.config.MysqlConfig;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.antlr4.mysql.MysqlAntlr4DdlParser;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.cdc.AbstractCdcEngine;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.cdc.mysql.RowDeserializers.DeleteRowsEventMeshDeserializer;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.cdc.mysql.RowDeserializers.UpdateRowsEventMeshDeserializer;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.cdc.mysql.RowDeserializers.WriteRowsEventMeshDeserializer;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.mysql.EventDataDeserializationExceptionData;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.mysql.EventMeshGtidSet;
+import org.apache.eventmesh.connector.jdbc.source.dialect.mysql.MysqlConstants;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.mysql.MysqlDatabaseDialect;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.mysql.MysqlJdbcContext;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.mysql.MysqlSourceMateData;
+import org.apache.eventmesh.connector.jdbc.table.catalog.Column;
+import org.apache.eventmesh.connector.jdbc.table.catalog.TableId;
+import org.apache.eventmesh.connector.jdbc.table.catalog.TableSchema;
+import org.apache.eventmesh.connector.jdbc.table.type.Pair;
+import org.apache.eventmesh.openconnect.api.config.Config;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import com.github.shyiko.mysql.binlog.BinaryLogClient;
+import com.github.shyiko.mysql.binlog.BinaryLogClient.LifecycleListener;
+import com.github.shyiko.mysql.binlog.event.DeleteRowsEventData;
+import com.github.shyiko.mysql.binlog.event.Event;
+import com.github.shyiko.mysql.binlog.event.EventData;
+import com.github.shyiko.mysql.binlog.event.EventHeader;
+import com.github.shyiko.mysql.binlog.event.EventHeaderV4;
+import com.github.shyiko.mysql.binlog.event.EventType;
+import com.github.shyiko.mysql.binlog.event.GtidEventData;
+import com.github.shyiko.mysql.binlog.event.QueryEventData;
+import com.github.shyiko.mysql.binlog.event.RotateEventData;
+import com.github.shyiko.mysql.binlog.event.TableMapEventData;
+import com.github.shyiko.mysql.binlog.event.TransactionPayloadEventData;
+import com.github.shyiko.mysql.binlog.event.UpdateRowsEventData;
+import com.github.shyiko.mysql.binlog.event.WriteRowsEventData;
+import com.github.shyiko.mysql.binlog.event.XidEventData;
+import
com.github.shyiko.mysql.binlog.event.deserialization.EventDataDeserializationException;
+import com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer;
+import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class MysqlCdcEngine extends AbstractCdcEngine<MysqlAntlr4DdlParser,
MysqlJdbcContext, MysqlDatabaseDialect> {
+
+ private BinaryLogClient client;
+
+ private BlockingQueue<Event> eventQueue = new LinkedBlockingQueue<>(10000);
+
+ private final EnumMap<EventType, Consumer<Event>> eventHandlers = new
EnumMap<>(EventType.class);
+
+ private Map<Long/*table number*/, TableId> tableIdMap = new HashMap<>(64);
+
+ private MysqlJdbcContext context;
+
+ private List<EventConsumer> consumers = new ArrayList<>(16);
+
+ private MysqlAntlr4DdlParser ddlParser;
+
+ private MysqlJdbcConnection connection;
+
+ private com.github.shyiko.mysql.binlog.GtidSet localGtidSet;
+
+ public MysqlCdcEngine(Config config, MysqlDatabaseDialect databaseDialect)
{
+ super((JdbcSourceConfig) config, databaseDialect);
+ this.ddlParser = new MysqlAntlr4DdlParser(false, false,
getHandledTables(), (JdbcSourceConfig) config);
+ this.connection = databaseDialect.getConnection();
+ }
+
+ /**
+ * Initializes the CDC Engine.
+ */
+ @Override
+ public void init() {
+ final JdbcConfig jdbcConfig =
this.sourceConnectorConfig.getJdbcConfig();
+ client = new BinaryLogClient(jdbcConfig.getHostname(),
jdbcConfig.getPort(), jdbcConfig.getUser(), jdbcConfig.getPassword());
+ client.setThreadFactory(new
EventMeshThreadFactory("mysql-binlog-client"));
+ final MysqlConfig mysqlConfig =
this.sourceConnectorConfig.getMysqlConfig();
+ client.setServerId(mysqlConfig.getServerId());
+ client.setKeepAlive(mysqlConfig.isKeepAlive());
+ final long keepAliveInterval = mysqlConfig.getKeepAliveInterval();
+ client.setKeepAliveInterval(keepAliveInterval);
+
+ final Map<Long, TableMapEventData> tableMapEventByTableId = new
HashMap<>(32);
+
+ // mysql dev url:https://dev.mysql.com/doc/dev/mysql-server/latest/
+ EventDeserializer eventDeserializer = new EventDeserializer() {
+ /**
+ * @param inputStream input stream to fetch event from
+ * @return deserialized event or null in case of end-of-stream
+ * @throws IOException if connection gets closed
+ */
+ @Override
+ public Event nextEvent(ByteArrayInputStream inputStream) throws
IOException {
+ try {
+ // Delegate to the superclass
+ Event event = super.nextEvent(inputStream);
+ if (log.isDebugEnabled()) {
+ log.debug("MYSQL Binlog---EventType={}, EventData={}",
event.getHeader().getEventType(), event);
+ }
+ // We have to record the most recent TableMapEventData for
each table number for our custom deserializers
+ if (event.getHeader().getEventType() ==
EventType.TABLE_MAP) {
+ TableMapEventData tableMapEvent = event.getData();
+ tableMapEventByTableId.put(tableMapEvent.getTableId(),
tableMapEvent);
+ }
+
+ // Handle for transaction payload and capture the table
map event and add it to the map
+ if (event.getHeader().getEventType() ==
EventType.TRANSACTION_PAYLOAD) {
+ TransactionPayloadEventData
transactionPayloadEventData = event.getData();
+ /**
+ * Loop over the uncompressed events in the
transaction payload event and add the table map
+ * event in the map of table events
+ **/
+ for (Event uncompressedEvent :
transactionPayloadEventData.getUncompressedEvents()) {
+ if (uncompressedEvent.getHeader().getEventType()
== EventType.TABLE_MAP && uncompressedEvent.getData() != null) {
+ TableMapEventData tableMapEvent =
uncompressedEvent.getData();
+
tableMapEventByTableId.put(tableMapEvent.getTableId(), tableMapEvent);
+ }
+ }
+ }
+ // Clean cache on rotate event to prevent it from growing
indefinitely.
+ if (event.getHeader().getEventType() == EventType.ROTATE) {
+ tableMapEventByTableId.clear();
+ }
+ return event;
+ } catch (EventDataDeserializationException ex) {
+ if (ex.getCause() instanceof IOException) {
+ throw ex;
+ }
+ EventHeaderV4 header = new EventHeaderV4();
+ header.setEventType(EventType.INCIDENT);
+ header.setTimestamp(ex.getEventHeader().getTimestamp());
+ header.setServerId(ex.getEventHeader().getServerId());
+ if (ex.getEventHeader() instanceof EventHeaderV4) {
+ header.setEventLength(((EventHeaderV4)
ex.getEventHeader()).getEventLength());
+ header.setNextPosition(((EventHeaderV4)
ex.getEventHeader()).getNextPosition());
+ header.setFlags(((EventHeaderV4)
ex.getEventHeader()).getFlags());
+ }
+ EventData data = new
EventDataDeserializationExceptionData(ex);
+ return new Event(header, data);
+ }
+ }
+ };
+
+ eventDeserializer.setEventDataDeserializer(EventType.WRITE_ROWS, new
WriteRowsEventMeshDeserializer(tableMapEventByTableId));
+ eventDeserializer.setEventDataDeserializer(EventType.UPDATE_ROWS, new
UpdateRowsEventMeshDeserializer(tableMapEventByTableId));
+ eventDeserializer.setEventDataDeserializer(EventType.DELETE_ROWS, new
DeleteRowsEventMeshDeserializer(tableMapEventByTableId));
+ eventDeserializer.setEventDataDeserializer(EventType.EXT_WRITE_ROWS,
+ new
WriteRowsEventMeshDeserializer(tableMapEventByTableId).setMayContainExtraInformation(true));
+ eventDeserializer.setEventDataDeserializer(EventType.EXT_UPDATE_ROWS,
+ new
UpdateRowsEventMeshDeserializer(tableMapEventByTableId).setMayContainExtraInformation(true));
+ eventDeserializer.setEventDataDeserializer(EventType.EXT_DELETE_ROWS,
+ new
DeleteRowsEventMeshDeserializer(tableMapEventByTableId).setMayContainExtraInformation(true));
+
+ // Set the event deserializer for the MySQL client
+ client.setEventDeserializer(eventDeserializer);
+ // Register an event listener for the MySQL client
+ client.registerEventListener((event) ->
eventMeshMysqlEventListener(event, context));
+ // Register a lifecycle listener for the MySQL client
+ client.registerLifecycleListener(new LifecycleListener() {
+ @Override
+ public void onConnect(BinaryLogClient client) {
+ log.info("Client connect MySQL Server success");
+ }
+
+ @Override
+ public void onCommunicationFailure(BinaryLogClient client,
Exception ex) {
+ log.error("Communicate with mysql error", ex);
+ }
+
+ @Override
+ public void onEventDeserializationFailure(BinaryLogClient client,
Exception ex) {
+ log.error("Event deserialization failure", ex);
+ }
+
+ @Override
+ public void onDisconnect(BinaryLogClient client) {
+ log.info("Disconnect Mysql");
+ }
+ });
+
+ //Register custom event handlers...
+ eventHandlers.put(EventType.STOP, event -> handleStopEvent(context,
event));
+ eventHandlers.put(EventType.HEARTBEAT, event ->
handleHeartbeatEvent(context, event));
+ eventHandlers.put(EventType.INCIDENT, event ->
handleServerIncident(context, event));
+ eventHandlers.put(EventType.ROTATE, event ->
handleRotateEvent(context, event));
+ eventHandlers.put(EventType.TABLE_MAP, event ->
handleTableMapEvent(context, event));
+ eventHandlers.put(EventType.QUERY, event -> handleQueryEvent(context,
event));
+ eventHandlers.put(EventType.TRANSACTION_PAYLOAD, event ->
handleTransactionPayload(context, event));
+
+ //Used to support 5.1.16 - mysql-trunk
+ eventHandlers.put(EventType.WRITE_ROWS, event ->
handleInsertEvent(context, event));
+ eventHandlers.put(EventType.UPDATE_ROWS, event ->
handleUpdateEvent(context, event));
+ eventHandlers.put(EventType.DELETE_ROWS, event ->
handleDeleteEvent(context, event));
+
+ // Used in case of RBR (5.1.18+).
+ eventHandlers.put(EventType.EXT_WRITE_ROWS, event ->
handleInsertEvent(context, event));
+ eventHandlers.put(EventType.EXT_UPDATE_ROWS, event ->
handleUpdateEvent(context, event));
+ eventHandlers.put(EventType.EXT_DELETE_ROWS, event ->
handleDeleteEvent(context, event));
+
+ eventHandlers.put(EventType.VIEW_CHANGE, (event) ->
handleViewChangeEvent(context, event));
+ eventHandlers.put(EventType.XA_PREPARE, (event) ->
handleXAPrepareTransactionEvent(context, event));
+ eventHandlers.put(EventType.XID, (event) ->
handleTransactionCompletionEvent(context, event));
+ }
+
+ public EventMeshGtidSet filterGtidSet(MysqlJdbcContext offsetContext,
EventMeshGtidSet availableServerEventMeshGtidSet,
+ EventMeshGtidSet purgedServerGtid) {
+ String gtidStr = offsetContext.getGtidSet();
+ if (gtidStr == null) {
+ return null;
+ }
+
+ EventMeshGtidSet filteredEventMeshGtidSet = new
EventMeshGtidSet(gtidStr);
+
+ final EventMeshGtidSet knownEventMeshGtidSet =
filteredEventMeshGtidSet;
+ final EventMeshGtidSet relevantAvailableServerEventMeshGtidSet =
availableServerEventMeshGtidSet;
+
+ EventMeshGtidSet mergedEventMeshGtidSet =
relevantAvailableServerEventMeshGtidSet
+ .retainAll(uuid -> knownEventMeshGtidSet.forServerWithId(uuid) !=
null)
+ .with(purgedServerGtid)
+ .with(filteredEventMeshGtidSet);
+
+ return mergedEventMeshGtidSet;
+ }
+
+
+ /**
+ * handles events from the MySQL
+ *
+ * @param event mysql binlog event
+ * @param context mysql context
+ */
+ private void eventMeshMysqlEventListener(Event event, MysqlJdbcContext
context) {
+
+ if (null == event) {
+ return;
+ }
+ final EventHeader eventHeader = event.getHeader();
+ final EventType eventType = eventHeader.getEventType();
+ /**
+ * @see <a
href="https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_replication_binlog_event.html#sect_protocol_replication_event_rotate">ROTATE_EVENT</a>
+ * +----------------------------------------------------+
+ * | ROTATE_EVENT |
+ * +----------------------------------------------------+
+ * | Event Header | Position Info | Filename|
+ * +----------------------------------------------------+
+ * The rotate event is added to the binlog as last event to tell the
reader what binlog to request next.
+ */
+ if (eventType == EventType.ROTATE) {
+ RotateEventData rotateEventData = unwrapData(event);
+ context.setBinlogStartPoint(rotateEventData.getBinlogFilename(),
rotateEventData.getBinlogPosition());
+ } else if (eventHeader instanceof EventHeaderV4) {
+ EventHeaderV4 eventHeaderV4 = (EventHeaderV4) eventHeader;
+ context.setEventPosition(eventHeaderV4.getPosition(),
eventHeaderV4.getEventLength());
+ }
+ if (eventType == EventType.HEARTBEAT) {
+ return;
+ }
+ try {
+ eventQueue.put(event);
+ } catch (InterruptedException e) {
+ log.warn("Put event to queue error", e);
+ }
+ context.complete();
+ }
+
+
+ @Override
+ public String getThreadName() {
+ return "MySQL-CdcEngine";
+ }
+
+ /**
+ * Returns the name of the CDC Engine.
+ *
+ * @return String representing the name of the CDC Engine.
+ */
+ @Override
+ public String getCdcEngineName() {
+ return "MySQL CDC Engine";
+ }
+
+ @Override
+ public void close() throws Exception {
+ client.disconnect();
+ }
+
+ /**
+ * When an object implementing interface <code>Runnable</code> is used to
create a thread, starting the thread causes the object's
+ * <code>run</code> method to be called in that separately executing
+ * thread.
+ * <p>
+ * The general contract of the method <code>run</code> is that it may take
any action whatsoever.
+ *
+ * @see Thread#run()
+ */
+ @Override
+ public void run() {
+ enableGtidHandle();
+ do {
+ try {
+ client.connect(TimeUnit.SECONDS.toMillis(5));
+ } catch (IOException | TimeoutException e) {
+ log.error("Binary log client connect to mysql server error,
The connection will be retried in three seconds", e);
+ await(3, TimeUnit.SECONDS);
+ }
+ } while (!client.isConnected());
+
+ while (isRunning) {
+ Event event = null;
+ try {
+ event = eventQueue.poll(5, TimeUnit.SECONDS);
+ if (null == event) {
+ continue;
+ }
+ eventHandlers.getOrDefault(event.getHeader().getEventType(),
ignore -> ignoreEvent(context, ignore)).accept(event);
+ } catch (Exception e) {
+ if (event != null) {
+ log.warn("Handle EventType={} error",
event.getHeader().getEventType(), e);
+ }
+ }
+ }
+ }
+
+ private void enableGtidHandle() {
+ //Query whether mysql supports GTID
+ if (this.connection.enableGTID()) {
+ eventHandlers.put(EventType.GTID, event ->
handleGtidEvent(context, event));
+
+ //Query GtidSet from the MySQL Server
+ String availableServerGtid = this.connection.executedGTID();
+ EventMeshGtidSet executedEventMeshGtidSet = new
EventMeshGtidSet(availableServerGtid);
+
+ //Get purged GTID
+ String purgedServerGtid = this.connection.purgedGTID();
+ EventMeshGtidSet purgedServerEventMeshGtidSet = new
EventMeshGtidSet(purgedServerGtid);
+
+ EventMeshGtidSet filteredEventMeshGtidSet = filterGtidSet(context,
executedEventMeshGtidSet, purgedServerEventMeshGtidSet);
+ if (null != filteredEventMeshGtidSet) {
+ client.setGtidSet(filteredEventMeshGtidSet.toString());
+
this.context.completedGtidSet(filteredEventMeshGtidSet.toString());
+ localGtidSet = new
com.github.shyiko.mysql.binlog.GtidSet(filteredEventMeshGtidSet.toString());
+ } else {
+
client.setBinlogFilename(this.context.getSourceInfo().getCurrentBinlogFileName());
+
client.setBinlogPosition(this.context.getSourceInfo().getCurrentBinlogPosition());
+ localGtidSet = new com.github.shyiko.mysql.binlog.GtidSet("");
+ }
+ } else {
+
client.setBinlogFilename(this.context.getSourceInfo().getCurrentBinlogFileName());
+
client.setBinlogPosition(this.context.getSourceInfo().getCurrentBinlogPosition());
+ }
+ }
+
+ /**
+ * Handles the STOP_EVENT
+ *
+ * @param context the MySQL context
+ * @param event the event to be handled
+ */
+ protected void handleStopEvent(MysqlJdbcContext context, Event event) {
+ //The purpose of STOP_EVENT is to inform MySQL that the slave or
replication client has reached the end of the binary log
+ // and no new events will be generated. When the slave receives a
STOP_EVENT, it can take appropriate actions based on its needs,
+ // such as closing the connection to the master server or reconnecting
to obtain a new binary log file.
+ log.debug("Replication client has reached the end of the binary log:
{}", event);
+ }
+
+ /**
+ * Handles the HEARTBEAT_EVENT
+ *
+ * @param context the MySQL context
+ * @param event the event to be handled
+ */
+ protected void handleHeartbeatEvent(MysqlJdbcContext context, Event event)
{
+ if (log.isDebugEnabled()) {
+ log.debug("Replication client handle {}",
event.getHeader().getEventType());
+ }
+ }
+
+ /**
+ * Handles the <a
href="https://dev.mysql.com/doc/dev/mysql-server/latest/classbinary__log_1_1Incident__event.html">INCIDENT_EVENT</a>
+ *
+ * @param context the MySQL context
+ * @param event the event to be handled
+ */
+ protected void handleServerIncident(MysqlJdbcContext context, Event event)
{
+
+ final EventData eventData = event.getData();
+ if (eventData instanceof EventDataDeserializationExceptionData) {
+ log.error("Server incident: {}", event);
+ }
+
+ }
+
+ /**
+ * Handles the ROTATE_EVENT
+ *
+ * @param context the MySQL context
+ * @param event the event to be handled
+ */
+ protected void handleRotateEvent(MysqlJdbcContext context, Event event) {
+ RotateEventData eventData = unwrapData(event);
+ assert eventData != null;
+ tableIdMap.clear();
+ }
+
+ @SuppressWarnings("unchecked")
+ protected <T extends EventData> T unwrapData(Event event) {
+ EventData eventData = event.getData();
+ if (eventData instanceof EventDeserializer.EventDataWrapper) {
+ eventData = ((EventDeserializer.EventDataWrapper)
eventData).getInternal();
+ }
+ return (T) eventData;
+ }
+
+ /**
+ * Handles the TABLE_MAP_EVENT Format @see <a
+ *
href="https://dev.mysql.com/doc/dev/mysql-server/latest/classbinary__log_1_1Table__map__event.html">TABLE_MAP_EVENT</a>
+ *
+ * @param context the MySQL context
+ * @param event the event to be handled
+ */
+ protected void handleTableMapEvent(MysqlJdbcContext context, Event event) {
+ TableMapEventData tableMapEventData = event.getData();
+ final long tableId = tableMapEventData.getTableId();
+ final String tableName = tableMapEventData.getTable();
+ final String database = tableMapEventData.getDatabase();
+ tableIdMap.put(tableId, new TableId(database, null, tableName));
+ }
+
+ /**
+ * Handles the QUERY_EVENT
+ *
+ * @param context mysql context
+ * @param event query event
+ */
+ protected void handleQueryEvent(MysqlJdbcContext context, Event event) {
+ QueryEventData queryEventData = unwrapData(event);
+ final String sql = queryEventData.getSql().trim();
+ log.debug("Received query event SQL:{}", sql);
+ if (StringUtils.equalsIgnoreCase("BEGIN", sql)) {
+ //start transaction
+ context.startTransaction();
+ return;
+ }
+
+ if (StringUtils.equalsIgnoreCase("COMMIT", sql)) {
+ context.commitTransaction();
+ return;
+ }
+
+ if (StringUtils.startsWithIgnoreCase("XA", sql)) {
+ //TODO: next version support
+
+ return;
+ }
+ String sqlBegin = sql.substring(0, 6).toUpperCase();
+ if (StringUtils.startsWithAny(sqlBegin, "INSERT", "UPDATE", "DELETE"))
{
+ LogUtils.warn(log, "Received DML '[SQL={}]' for processing, binlog
probably contains events generated with statement", sql);
+ return;
+ }
+
+ //set current parse database to Ddl parser
+ ddlParser.setCurrentDatabase(queryEventData.getDatabase());
+ ddlParser.parse(sql, this::handleDdlEvent);
+ }
+
+ private void
handleDdlEvent(org.apache.eventmesh.connector.jdbc.event.Event event) {
+ if (event == null) {
+ return;
+ }
+
event.getJdbcConnectData().getPayload().ofSourceMateData().setSnapshot(false);
+ consumers.stream().forEach(consumer -> consumer.accept(event));
+ }
+
+ /**
+ * Handles the TRANSACTION_PAYLOAD_EVENT
+ * <p>
+ * "binlog_transaction_compression" is a new feature introduced in MySQL
8.0.23, used for compressing transactions in the binary log (binlog).
+ * This event is a wrapper event and encloses many other events.It is
mostly used for carrying compressed payloads as its content can be
+ * compressed, in which case, its metadata shall contain information about
the compression metadata as well.
+ *
+ * </p>
+ *
+ * @param context the MySQL context
+ * @param event the event to be handled
+ */
+ protected void handleTransactionPayload(MysqlJdbcContext context, Event
event) {
+
+ TransactionPayloadEventData transactionPayloadEventData =
event.getData();
+ //unpack Event and handle
+ ArrayList<Event> uncompressedEvents =
transactionPayloadEventData.getUncompressedEvents();
+ for (Event uncompressedEvent : uncompressedEvents) {
+ final EventType eventType =
uncompressedEvent.getHeader().getEventType();
+ eventHandlers.getOrDefault(eventType, et -> ignoreEvent(context,
et)).accept(uncompressedEvent);
+ }
+ }
+
+ /**
+ * Handles the insert event.
+ *
+ * @param context The MySQL context.
+ * @param event The insert event.
+ */
+ protected void handleInsertEvent(MysqlJdbcContext context, Event event) {
+ WriteRowsEventData writeRowsEventData = unwrapData(event);
+ log.debug("Received Write rows event, TableId={}",
writeRowsEventData.getTableId());
+ long tableNumber = writeRowsEventData.getTableId();
+ TableId tableId = tableIdMap.get(tableNumber);
+
+ if (!getHandledTables().contains(tableId)) {
+ log.warn("Write rows-Table {} is excluded", tableId);
+ return;
+ }
+ MysqlSourceMateData sourceMateData = buildMysqlSourceMateData(context,
event, tableId);
+ List<Serializable[]> insertRows = writeRowsEventData.getRows();
+ if (CollectionUtils.isEmpty(insertRows)) {
+ return;
+ }
+ List<Pair<Pair<Serializable[], BitSet>, Pair<Serializable[], BitSet>>>
rows = new ArrayList<>();
+ for (Serializable[] row : insertRows) {
+ Pair<Serializable[], BitSet> item = new Pair<>(row,
writeRowsEventData.getIncludedColumns());
+ rows.add(new Pair<>(null, item));
+ }
+ handleCdcDmlData(context, sourceMateData, tableId, rows,
CdcDmlType.INSERT);
+ }
+
+ private MysqlSourceMateData buildMysqlSourceMateData(MysqlJdbcContext
context, Event event, TableId tableId) {
+ MysqlSourceMateData sourceMateData = MysqlSourceMateData.newBuilder()
+ .name(sourceConnectorConfig.getName())
+ .withTableId(tableId)
+ .serverId(sourceConnectorConfig.getMysqlConfig().getServerId())
+ .binlogFile(context.getSourceInfo().getCurrentBinlogFileName())
+ .position(((EventHeaderV4) event.getHeader()).getPosition())
+ .build();
+ return sourceMateData;
+ }
+
+ private enum CdcDmlType {
+ INSERT,
+ UPDATE,
+ DELETE
+ }
+
+ private GeneralDataChangeEvent buildEvent(CdcDmlType type, TableId
tableId) {
+ switch (type) {
+ case UPDATE:
+ return new UpdateDataEvent(tableId);
+ case INSERT:
+ return new InsertDataEvent(tableId);
+ case DELETE:
+ return new DeleteDataEvent(tableId);
+ default:
+ return null;
+ }
+ }
+
+ private void handleCdcDmlData(MysqlJdbcContext context,
MysqlSourceMateData sourceMateData, TableId tableId,
+ List<Pair<Pair<Serializable[], BitSet>, Pair<Serializable[], BitSet>>>
rows, CdcDmlType type) {
+
+ TableSchema tableSchema =
context.getCatalogTableSet().getTableSchema(tableId);
+ Map<Integer, ? extends Column> orderColumnMap =
tableSchema.getOrderColumnMap();
+ List<? extends Column> columns = tableSchema.getColumns();
+ List<Field> fields = null;
+ Builder builder = DataChanges.newBuilder();
+ if (CollectionUtils.isNotEmpty(columns)) {
+ fields = columns.stream()
+ .map(col -> new Field(col.getDataType().getName(),
col.isNotNull(), col.getName(), tableId.toString()))
+ .collect(Collectors.toList());
+ }
+ int columnsSize = orderColumnMap.size();
+ for (Pair<Pair<Serializable[], BitSet>, Pair<Serializable[], BitSet>>
pair : rows) {
+ GeneralDataChangeEvent dataEvent = buildEvent(type, tableId);
+ Payload payload = dataEvent.getJdbcConnectData().getPayload();
+ Schema schema = new Schema();
+ Pair<Serializable[], BitSet> beforePair =
Optional.ofNullable(pair.getLeft()).orElse(new Pair<>());
+ Serializable[] beforeRows = beforePair.getLeft();
+ if (null != beforeRows && beforeRows.length != 0) {
+ BitSet includedColumns = beforePair.getRight();
+ Map<String, Object> beforeValues = new
HashMap<>(beforeRows.length);
+ for (int index = 0; index < columnsSize; ++index) {
+ //Filter out empty fields
+ if (!includedColumns.get(index)) {
+ continue;
+ }
+ beforeValues.put(orderColumnMap.get(index + 1).getName(),
beforeRows[index]);
+ }
+ builder.withBefore(beforeValues);
+ Field beforeField = new
Field().withField(Payload.BEFORE_FIELD).withType("field").withName("payload.before").withRequired(false);
+ beforeField.withRequired(true).withFields(fields);
+ schema.add(beforeField);
+ }
+
+ Pair<Serializable[], BitSet> afterPair =
Optional.ofNullable(pair.getRight()).orElse(new Pair<>());
+ Serializable[] afterRows = afterPair.getLeft();
+ if (null != afterRows && afterRows.length != 0) {
+ BitSet includedColumns = afterPair.getRight();
+ Map<String, Object> afterValues = new
HashMap<>(afterRows.length);
+ for (int index = 0; index < columnsSize; ++index) {
+ //Filter out empty fields
+ if (!includedColumns.get(index)) {
+ continue;
+ }
+ afterValues.put(orderColumnMap.get(index + 1).getName(),
afterRows[index]);
+ }
+ builder.withBefore(afterValues);
+ Field afterField = new
Field().withField(Payload.AFTER_FIELD).withType("field").withName("payload.after").withRequired(false);
+ afterField.withRequired(true).withFields(fields);
+ schema.add(afterField);
+ }
+
payload.withSource(sourceMateData).withDataChanges(builder.build());
+ dataEvent.getJdbcConnectData().setSchema(schema);
+ consumers.stream().forEach(consumer -> consumer.accept(dataEvent));
+
+ }
+ }
+
+ /**
+ * Handles the update event.
+ *
+ * @param context The MySQL context.
+ * @param event The update event.
+ */
+ protected void handleUpdateEvent(MysqlJdbcContext context, Event event) {
+ UpdateRowsEventData updateRowsEventData = unwrapData(event);
+ log.debug("Received Update rows event, Update table is {}",
tableIdMap.get(updateRowsEventData.getTableId()));
+ long id = updateRowsEventData.getTableId();
+ TableId tableId = tableIdMap.get(id);
+ if (!getHandledTables().contains(tableId)) {
+ log.debug("Update rows-Table {} is excluded", tableId);
+ return;
+ }
+ MysqlSourceMateData sourceMateData = buildMysqlSourceMateData(context,
event, tableId);
+ List<Entry<Serializable[], Serializable[]>> updateRows =
updateRowsEventData.getRows();
+ if (CollectionUtils.isEmpty(updateRows)) {
+ return;
+ }
+ List<Pair<Pair<Serializable[], BitSet>, Pair<Serializable[], BitSet>>>
rows = new ArrayList<>();
+ for (Entry<Serializable[], Serializable[]> row : updateRows) {
+ Pair<Serializable[], BitSet> before = new Pair<>(row.getKey(),
updateRowsEventData.getIncludedColumnsBeforeUpdate());
+ Pair<Serializable[], BitSet> after = new Pair<>(row.getValue(),
updateRowsEventData.getIncludedColumns());
+ rows.add(new Pair<>(before, after));
+ }
+ handleCdcDmlData(context, sourceMateData, tableId, rows,
CdcDmlType.UPDATE);
+ }
+
+ /**
+ * Handles the delete event.
+ *
+ * @param context The MySQL context.
+ * @param event The delete event.
+ */
+ protected void handleDeleteEvent(MysqlJdbcContext context, Event event) {
+ DeleteRowsEventData deleteRowsEventData = unwrapData(event);
+ log.debug("Received Delete rows event, Delete table is {}",
tableIdMap.get(deleteRowsEventData.getTableId()));
+ long id = deleteRowsEventData.getTableId();
+ TableId tableId = tableIdMap.get(id);
+
+ if (!getHandledTables().contains(tableId)) {
+ log.debug("Update rows-Table {} is excluded", tableId);
+ return;
+ }
+ MysqlSourceMateData sourceMateData = buildMysqlSourceMateData(context,
event, tableId);
+ List<Serializable[]> deleteRows = deleteRowsEventData.getRows();
+ if (CollectionUtils.isEmpty(deleteRows)) {
+ return;
+ }
+ List<Pair<Pair<Serializable[], BitSet>, Pair<Serializable[], BitSet>>>
rows = new ArrayList<>();
+ for (Serializable[] row : deleteRows) {
+ Pair<Serializable[], BitSet> item = new Pair<>(row,
deleteRowsEventData.getIncludedColumns());
+ rows.add(new Pair<>(item, null));
+ }
+ handleCdcDmlData(context, sourceMateData, tableId, rows,
CdcDmlType.DELETE);
+ }
+
+ /**
+ * Handles the GTID event.
+ *
+ * @param context The MySQL context.
+ * @param event The GTID event.
+ */
+ protected void handleGtidEvent(MysqlJdbcContext context, Event event) {
+ GtidEventData gtidEvent = unwrapData(event);
+ String gtid = gtidEvent.getMySqlGtid().toString();
+ LogUtils.debug(log, "Received GTID event: {}", gtid);
+ localGtidSet.add(gtid);
+ context.beginGtid(gtid);
+ }
+
+ /**
+ * Handles the view change event.
+ *
+ * @param context The MySQL context.
+ * @param event The view change event.
+ */
+ protected void handleViewChangeEvent(MysqlJdbcContext context, Event
event) {
+ // TODO: Add support for handling view change event
+ }
+
+ /**
+ * Handles the XA prepare transaction event.
+ *
+ * @param context The MySQL context.
+ * @param event The XA prepare transaction event.
+ */
+ protected void handleXAPrepareTransactionEvent(MysqlJdbcContext context,
Event event) {
+ // TODO: Add support for handling XA prepare transaction event
+ }
+
+ /**
+ * Handles the transaction completion event.
+ *
+ * @param context The MySQL context.
+ * @param event The transaction completion event.
+ */
+ protected void handleTransactionCompletionEvent(MysqlJdbcContext context,
Event event) {
+ XidEventData xidEventData = unwrapData(event);
+ log.debug("Received XID event, Xid={}", xidEventData.getXid());
+ context.commitTransaction();
+ }
+
+ /**
+ * Default handler that ignores events.
+ *
+ * @param context The MySQL context.
+ * @param event The event to be ignored.
+ */
+ protected void ignoreEvent(MysqlJdbcContext context, Event event) {
+ log.debug("Ignoring event due to missing handler: {}", event);
+ }
+
+ @Override
+ public void registerCdcEventConsumer(EventConsumer consumer) {
+ if (consumer == null) {
+ return;
+ }
+ consumers.add(consumer);
+ }
+
+ @Override
+ protected Set<String> defaultExcludeDatabase() {
+ return MysqlConstants.DEFAULT_EXCLUDE_DATABASE;
+ }
+
+ @Override
+ protected MysqlAntlr4DdlParser getDdlParser() {
+ return ddlParser;
+ }
+
+ @Override
+ public void setContext(MysqlJdbcContext context) {
+ if (context == null) {
+ context = MysqlJdbcContext.initialize(this.jdbcSourceConfig);
+ }
+ this.context = context;
+ }
+}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/mysql/MysqlCdcEngineFactory.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/mysql/MysqlCdcEngineFactory.java
new file mode 100644
index 000000000..60c3c7b7c
--- /dev/null
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/mysql/MysqlCdcEngineFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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.eventmesh.connector.jdbc.source.dialect.cdc.mysql;
+
+import org.apache.eventmesh.connector.jdbc.DatabaseDialect;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.cdc.AbstractCdcEngineFactory;
+import org.apache.eventmesh.connector.jdbc.source.dialect.cdc.CdcEngine;
+import org.apache.eventmesh.connector.jdbc.source.dialect.cdc.CdcEngineFactory;
+import
org.apache.eventmesh.connector.jdbc.source.dialect.mysql.MysqlDatabaseDialect;
+import org.apache.eventmesh.openconnect.api.config.SourceConfig;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class MysqlCdcEngineFactory extends AbstractCdcEngineFactory implements
CdcEngineFactory {
+
+ @Override
+ public boolean acceptJdbcProtocol(String url) {
+ if (StringUtils.isBlank(url)) {
+ return true;
+ }
+ return url.toLowerCase().startsWith("jdbc:mysql");
+ }
+
+ /**
+ * Creates a CDC engine based on the provided source configuration and
database dialect.
+ *
+ * @param config the source configuration for the CDC engine
+ * @param databaseDialect the database dialect for the CDC engine
+ * @return the created CDC engine
+ */
+ @Override
+ public CdcEngine createCdcEngine(SourceConfig config, DatabaseDialect
databaseDialect) {
+ return new MysqlCdcEngine(config, (MysqlDatabaseDialect)
databaseDialect);
+ }
+
+}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/mysql/RowDeserializers.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/mysql/RowDeserializers.java
new file mode 100644
index 000000000..e4b1fde87
--- /dev/null
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/cdc/mysql/RowDeserializers.java
@@ -0,0 +1,452 @@
+/*
+ * 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.eventmesh.connector.jdbc.source.dialect.cdc.mysql;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Year;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Map;
+
+import com.github.shyiko.mysql.binlog.event.TableMapEventData;
+import
com.github.shyiko.mysql.binlog.event.deserialization.DeleteRowsEventDataDeserializer;
+import
com.github.shyiko.mysql.binlog.event.deserialization.UpdateRowsEventDataDeserializer;
+import
com.github.shyiko.mysql.binlog.event.deserialization.WriteRowsEventDataDeserializer;
+import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Custom deserializers for the MySQL Binlog Client.MySQL Binlog Client row
deserializers convert MySQL raw row data into {@link java.sql.Date},
+ * {@link java.sql.Time}, and {@link java.sql.Timestamp} values using {@link
java.util.Calendar} instances. EventMesh convert the raw MySQL row values
+ * directly into {@link LocalDate}, {@link LocalTime}, {@link LocalDateTime},
and {@link java.time.OffsetDateTime}.
+ */
+@Slf4j
+public class RowDeserializers {
+
+ public static class WriteRowsEventMeshDeserializer extends
WriteRowsEventDataDeserializer {
+
+ public WriteRowsEventMeshDeserializer(Map<Long, TableMapEventData>
tableMapEventByTableId) {
+ super(tableMapEventByTableId);
+ }
+
+ @Override
+ protected Serializable deserializeString(int length,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeString(length, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeVarString(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeVarString(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeDate(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeDate(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeDatetime(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeDatetime(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeDatetimeV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeDatetimeV2(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTimeV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeTimeV2(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTime(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeTime(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTimestamp(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeTimestamp(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTimestampV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeTimestampV2(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeYear(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeYear(inputStream);
+ }
+ }
+
+ public static class UpdateRowsEventMeshDeserializer extends
UpdateRowsEventDataDeserializer {
+
+ public UpdateRowsEventMeshDeserializer(Map<Long, TableMapEventData>
tableMapEventByTableId) {
+ super(tableMapEventByTableId);
+ }
+
+ @Override
+ protected Serializable deserializeString(int length,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeString(length, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeVarString(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeVarString(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeDate(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeDate(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeDatetime(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeDatetime(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeDatetimeV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeDatetimeV2(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTimeV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeTimeV2(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTime(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeTime(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTimestamp(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeTimestamp(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTimestampV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeTimestampV2(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeYear(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeYear(inputStream);
+ }
+ }
+
+ public static class DeleteRowsEventMeshDeserializer extends
DeleteRowsEventDataDeserializer {
+
+ public DeleteRowsEventMeshDeserializer(Map<Long, TableMapEventData>
tableMapEventByTableId) {
+ super(tableMapEventByTableId);
+ }
+
+ @Override
+ protected Serializable deserializeString(int length,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeString(length, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeVarString(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeVarString(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeDate(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeDate(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeDatetime(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeDatetime(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeDatetimeV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeDatetimeV2(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTimeV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeTimeV2(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTime(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeTime(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTimestamp(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeTimestamp(inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeTimestampV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ return RowDeserializers.deserializeTimestampV2(meta, inputStream);
+ }
+
+ @Override
+ protected Serializable deserializeYear(ByteArrayInputStream
inputStream) throws IOException {
+ return RowDeserializers.deserializeYear(inputStream);
+ }
+ }
+
+ protected static Serializable deserializeTimestamp(ByteArrayInputStream
inputStream) throws IOException {
+ long epochSecond = inputStream.readLong(4);
+ int nanoSeconds = 0; // no fractional seconds
+ return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSecond,
nanoSeconds), ZoneOffset.UTC);
+ }
+
+ protected static Serializable deserializeTimestampV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ long epochSecond = bigEndianLong(inputStream.read(4), 0, 4);
+ int nanoSeconds = deserializeFractionalSecondsInNanos(meta,
inputStream);
+ return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSecond,
nanoSeconds), ZoneOffset.UTC);
+ }
+
+ protected static Serializable deserializeYear(ByteArrayInputStream
inputStream) throws IOException {
+ return Year.of(1900 + inputStream.readInteger(1));
+ }
+
+ /**
+ * Deserializes a string from the given input stream. Since the charset is
not present in the binary log, it is impossible to distinguish between
+ * CHAR and BINARY types. Therefore, the method returns a byte array
instead of a String.
+ *
+ * @param length The length of the string.
+ * @param inputStream The input stream from which to read the string.
+ * @return A byte array representing the deserialized string.
+ * @throws IOException If an I/O error occurs while reading the input
stream.
+ */
+ private static Serializable deserializeString(int length,
ByteArrayInputStream inputStream) throws IOException {
+ // charset is not present in the binary log (meaning there is no way
to distinguish between CHAR / BINARY)
+ // as a result - return byte[] instead of an actual String
+ int stringLength = length < 256 ? inputStream.readInteger(1) :
inputStream.readInteger(2);
+ return inputStream.read(stringLength);
+ }
+
+
+ private static Serializable deserializeVarString(int meta,
ByteArrayInputStream inputStream) throws IOException {
+ int varcharLength = meta < 256 ? inputStream.readInteger(1) :
inputStream.readInteger(2);
+ return inputStream.read(varcharLength);
+ }
+
+ private static Serializable deserializeDate(ByteArrayInputStream
inputStream) throws IOException {
+ int value = inputStream.readInteger(3);
+ int day = value % 32;
+ value >>>= 5;
+ int month = value % 16;
+ int year = value >> 4;
+ // https://dev.mysql.com/doc/refman/8.0/en/datetime.html
+ if (year == 0 || month == 0 || day == 0) {
+ return null;
+ }
+ return LocalDate.of(year, month, day);
+ }
+
+ protected static Serializable deserializeDatetime(ByteArrayInputStream
inputStream) throws IOException {
+ int[] split = split(inputStream.readLong(8), 100, 6);
+ int year = split[5];
+ int month = split[4]; // 1-based month number
+ int day = split[3]; // 1-based day of the month
+ int hours = split[2];
+ int minutes = split[1];
+ int seconds = split[0];
+ int nanoOfSecond = 0; // This version does not support fractional
seconds
+ if (year == 0 || month == 0 || day == 0) {
+ return null;
+ }
+ return LocalDateTime.of(year, month, day, hours, minutes, seconds,
nanoOfSecond);
+ }
+
+ private static final int MASK_10_BITS = (1 << 10) - 1;
+ private static final int MASK_6_BITS = (1 << 6) - 1;
+
+ protected static Serializable deserializeTimeV2(int meta,
ByteArrayInputStream inputStream) throws IOException {
+
+ /**
+ * Binary Format for TIME in MySQL binlog:
+ * <a
href="https://dev.mysql.com/doc/dev/mysql-server/latest/group__MY__TIME.html#time_low_level_rep">TIME</a>
+ *
+------+---------------+----------------------------------------------------+
+ * | Bits | Field | Value Range
|
+ *
+------+---------------+----------------------------------------------------+
+ * | 1 | sign | (Used for sign, when on disk)
|
+ * | 1 | unused | (Reserved for wider hour range, e.g. for
intervals)|
+ * | 10 | hour | (0-838)
|
+ * | 6 | minute | (0-59)
|
+ * | 6 | second | (0-59)
|
+ * | 24 | microseconds | (0-999999)
|
+ *
+------+---------------+----------------------------------------------------+
+ *
+ * + fractional-seconds storage (size depends on meta)
+ */
+ long time = bigEndianLong(inputStream.read(3), 0, 3);
+ boolean isNegative = bitSlice(time, 0, 1, 24) == 0;
+ int hours = bitSlice(time, 2, 10, 24);
+ int minutes = bitSlice(time, 12, 6, 24);
+ int seconds = bitSlice(time, 18, 6, 24);
+ int nanoSeconds;
+ if (isNegative) { // mysql binary arithmetic for negative encoded
values
+ hours = ~hours & MASK_10_BITS;
+ hours = hours & ~(1 << 10); // unset sign bit
+ minutes = ~minutes & MASK_6_BITS;
+ minutes = minutes & ~(1 << 6); // unset sign bit
+ seconds = ~seconds & MASK_6_BITS;
+ seconds = seconds & ~(1 << 6); // unset sign bit
+ nanoSeconds = deserializeFractionalSecondsInNanosNegative(meta,
inputStream);
+ if (nanoSeconds == 0 && seconds < 59) { // weird java Duration
behavior
+ ++seconds;
+ }
+ hours = -hours;
+ minutes = -minutes;
+ seconds = -seconds;
+ nanoSeconds = -nanoSeconds;
+ } else {
+ nanoSeconds = deserializeFractionalSecondsInNanos(meta,
inputStream);
+ }
+
+ return LocalTime.of(hours, minutes, seconds, nanoSeconds);
+ }
+
+ protected static Serializable deserializeTime(ByteArrayInputStream
inputStream) throws IOException {
+ // Times are stored as an integer as `HHMMSS`, so we need to split out
the digits ...
+ int value = inputStream.readInteger(3);
+ int[] split = split(value, 100, 3);
+ int hours = split[2];
+ int minutes = split[1];
+ int seconds = split[0];
+ return LocalTime.of(hours, minutes, seconds);
+ }
+
+ protected static int deserializeFractionalSecondsInNanosNegative(int fsp,
ByteArrayInputStream inputStream) throws IOException {
+ // Calculate the number of bytes to read, which is
+ // '1' when fsp=(1,2)
+ // '2' when fsp=(3,4) and
+ // '3' when fsp=(5,6)
+ int length = (fsp + 1) / 2;
+ if (length > 0) {
+ long fraction = bigEndianLong(inputStream.read(length), 0, length);
+ int maskBits = 0;
+ switch (length) { // mask bits according to field precision
+ case 1:
+ maskBits = 8;
+ break;
+ case 2:
+ maskBits = 15;
+ break;
+ case 3:
+ maskBits = 20;
+ break;
+ default:
+ break;
+ }
+ fraction = ~fraction & ((1 << maskBits) - 1);
+ fraction = (fraction & ~(1 << maskBits)) + 1; // unset sign bit
+ // Convert the fractional value (which has extra trailing digit
for fsp=1,3, and 5) to nanoseconds ...
+ return (int) (fraction / (0.0000001 * Math.pow(100, length - 1)));
+ }
+ return 0;
+ }
+
+ private static int bigEndianInteger(byte[] bytes, int offset, int length) {
+ int result = 0;
+ for (int i = offset; i < (offset + length); i++) {
+ byte b = bytes[i];
+ result = (result << 8) | (b >= 0 ? (int) b : (b + 256));
+ }
+ return result;
+ }
+
+ protected static Serializable deserializeDatetimeV2(int meta,
ByteArrayInputStream inputStream)
+ throws IOException {
+
+ /**
+ * <a href =
"https://dev.mysql.com/doc/dev/mysql-server/latest/group__MY__TIME.html">DATETIME</a>
+ * 1 sign (used when on disk)
+ * 17 year*13+month (year 0-9999, month 0-12)
+ * 5 day (0-31)
+ * 5 hour (0-23)
+ * 6 minute (0-59)
+ * 6 second (0-59)
+ * 24 microseconds (0-999999)
+ *
+ * (5 bytes in total)
+ *
+ * + fractional-seconds storage (size depends on meta)
+ */
+ long datetime = bigEndianLong(inputStream.read(5), 0, 5);
+ int yearMonth = bitSlice(datetime, 1, 17, 40);
+ int year = yearMonth / 13;
+ int month = yearMonth % 13; // 1-based month number
+ int day = bitSlice(datetime, 18, 5, 40); // 1-based day of the month
+ int hours = bitSlice(datetime, 23, 5, 40);
+ int minutes = bitSlice(datetime, 28, 6, 40);
+ int seconds = bitSlice(datetime, 34, 6, 40);
+ int nanoOfSecond = deserializeFractionalSecondsInNanos(meta,
inputStream);
+ if (year == 0 || month == 0 || day == 0) {
+ return null;
+ }
+ return LocalDateTime.of(year, month, day, hours, minutes, seconds,
nanoOfSecond);
+ }
+
+ private static int[] split(long value, int divider, int length) {
+ int[] result = new int[length];
+ for (int i = 0; i < length - 1; i++) {
+ result[i] = (int) (value % divider);
+ value /= divider;
+ }
+ result[length - 1] = (int) value;
+ return result;
+ }
+
+ protected static long bigEndianLong(byte[] bytes, int offset, int length) {
+ long result = 0;
+ for (int i = offset; i < (offset + length); i++) {
+ byte b = bytes[i];
+ result = (result << 8) | (b >= 0 ? (int) b : (b + 256));
+ }
+ return result;
+ }
+
+ protected static int bitSlice(long value, int bitOffset, int numberOfBits,
int payloadSize) {
+ long result = value >> payloadSize - (bitOffset + numberOfBits);
+ return (int) (result & ((1 << numberOfBits) - 1));
+ }
+
+ protected static int deserializeFractionalSecondsInNanos(int fsp,
ByteArrayInputStream inputStream) throws IOException {
+ // Calculate the number of bytes to read, which is
+ // '1' when fsp=(1,2) -- 7
+ // '2' when fsp=(3,4) and -- 12
+ // '3' when fsp=(5,6) -- 21
+ int length = (fsp + 1) / 2;
+ if (length > 0) {
+ long fraction = bigEndianLong(inputStream.read(length), 0, length);
+ // Convert the fractional value (which has extra trailing digit
for fsp=1,3, and 5) to nanoseconds ...
+ return (int) (fraction / (0.0000001 * Math.pow(100, length - 1)));
+ }
+ return 0;
+ }
+
+}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/GtidSet.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/EventMeshGtidSet.java
similarity index 93%
rename from
eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/GtidSet.java
rename to
eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/EventMeshGtidSet.java
index a1cb8738e..481aedb3d 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/GtidSet.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/EventMeshGtidSet.java
@@ -32,19 +32,19 @@ import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-public final class GtidSet {
+public final class EventMeshGtidSet {
private final Map<String, UUIDSet> uuidSetsByServerId = new TreeMap<>();
// sorts on keys
public static Pattern GTID_DELIMITER = Pattern.compile(":");
- protected GtidSet(Map<String, UUIDSet> uuidSetsByServerId) {
+ protected EventMeshGtidSet(Map<String, UUIDSet> uuidSetsByServerId) {
this.uuidSetsByServerId.putAll(uuidSetsByServerId);
}
/**
* @param gtids the string representation of the GTIDs.
*/
- public GtidSet(String gtids) {
+ public EventMeshGtidSet(String gtids) {
new
com.github.shyiko.mysql.binlog.GtidSet(gtids).getUUIDSets().forEach(uuidSet -> {
uuidSetsByServerId.put(uuidSet.getServerId().toString(), new
UUIDSet(uuidSet));
});
@@ -58,12 +58,12 @@ public final class GtidSet {
}
/**
- * Obtain a copy of this {@link GtidSet} except with only the GTID ranges
that have server UUIDs that match the given predicate.
+ * Obtain a copy of this {@link EventMeshGtidSet} except with only the
GTID ranges that have server UUIDs that match the given predicate.
*
* @param sourceFilter the predicate that returns whether a server UUID is
to be included
* @return the new GtidSet, or this object if {@code sourceFilter} is
null; never null
*/
- public GtidSet retainAll(Predicate<String> sourceFilter) {
+ public EventMeshGtidSet retainAll(Predicate<String> sourceFilter) {
if (sourceFilter == null) {
return this;
}
@@ -71,7 +71,7 @@ public final class GtidSet {
.stream()
.filter(entry -> sourceFilter.test(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
- return new GtidSet(newSets);
+ return new EventMeshGtidSet(newSets);
}
/**
@@ -99,7 +99,7 @@ public final class GtidSet {
* @param other the other set of GTIDs; may be null
* @return {@code true} if all of the GTIDs in this set are completely
contained within the supplied set of GTIDs, or {@code false} otherwise
*/
- public boolean isContainedWithin(GtidSet other) {
+ public boolean isContainedWithin(EventMeshGtidSet other) {
if (other == null) {
return false;
}
@@ -116,19 +116,19 @@ public final class GtidSet {
}
/**
- * Obtain a copy of this {@link GtidSet} except overwritten with all of
the GTID ranges in the supplied {@link GtidSet}.
+ * Obtain a copy of this {@link EventMeshGtidSet} except overwritten with
all of the GTID ranges in the supplied {@link EventMeshGtidSet}.
*
- * @param other the other {@link GtidSet} with ranges to add/overwrite on
top of those in this set;
+ * @param other the other {@link EventMeshGtidSet} with ranges to
add/overwrite on top of those in this set;
* @return the new GtidSet, or this object if {@code other} is null or
empty; never null
*/
- public GtidSet with(GtidSet other) {
+ public EventMeshGtidSet with(EventMeshGtidSet other) {
if (other == null || other.uuidSetsByServerId.isEmpty()) {
return this;
}
Map<String, UUIDSet> newSet = new HashMap<>();
newSet.putAll(this.uuidSetsByServerId);
newSet.putAll(other.uuidSetsByServerId);
- return new GtidSet(newSet);
+ return new EventMeshGtidSet(newSet);
}
/**
@@ -136,7 +136,7 @@ public final class GtidSet {
*
* @return The starting state of the GTID set.
*/
- public GtidSet getGtidSetBeginning() {
+ public EventMeshGtidSet getGtidSetBeginning() {
// Create a new map to store the GTID set
Map<String, UUIDSet> newSet = new HashMap<>();
@@ -147,7 +147,7 @@ public final class GtidSet {
}
// Create and return a new GTID set object using the new map
- return new GtidSet(newSet);
+ return new EventMeshGtidSet(newSet);
}
public boolean contains(String gtid) {
@@ -161,7 +161,7 @@ public final class GtidSet {
return uuidSet.contains(transactionId);
}
- public GtidSet subtract(GtidSet other) {
+ public EventMeshGtidSet subtract(EventMeshGtidSet other) {
if (other == null) {
return this;
}
@@ -170,7 +170,7 @@ public final class GtidSet {
.filter(entry ->
!entry.getValue().isContainedWithin(other.forServerWithId(entry.getKey())))
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(),
entry.getValue().subtract(other.forServerWithId(entry.getKey()))))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
- return new GtidSet(newSets);
+ return new EventMeshGtidSet(newSets);
}
@Override
@@ -183,8 +183,8 @@ public final class GtidSet {
if (obj == this) {
return true;
}
- if (obj instanceof GtidSet) {
- GtidSet that = (GtidSet) obj;
+ if (obj instanceof EventMeshGtidSet) {
+ EventMeshGtidSet that = (EventMeshGtidSet) obj;
return this.uuidSetsByServerId.equals(that.uuidSetsByServerId);
}
return false;
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/JdbcConnectData.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlConstants.java
similarity index 64%
copy from
eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/JdbcConnectData.java
copy to
eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlConstants.java
index f70819212..44f10cee6 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/JdbcConnectData.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlConstants.java
@@ -15,27 +15,20 @@
* limitations under the License.
*/
-package org.apache.eventmesh.connector.jdbc;
+package org.apache.eventmesh.connector.jdbc.source.dialect.mysql;
-public final class JdbcConnectData {
+import java.util.HashSet;
+import java.util.Set;
- private Payload payload = new Payload();
+public final class MysqlConstants {
- private Schema schema;
+ public static final Set<String> DEFAULT_EXCLUDE_DATABASE = new HashSet<>();
- public Payload getPayload() {
- return payload;
+ static {
+ DEFAULT_EXCLUDE_DATABASE.add("information_schema");
+ DEFAULT_EXCLUDE_DATABASE.add("mysql");
+ DEFAULT_EXCLUDE_DATABASE.add("performance_schema");
+ DEFAULT_EXCLUDE_DATABASE.add("sys");
}
- public void setPayload(Payload payload) {
- this.payload = payload;
- }
-
- public Schema getSchema() {
- return schema;
- }
-
- public void setSchema(Schema schema) {
- this.schema = schema;
- }
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlJdbcContext.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlJdbcContext.java
index 8dc64d041..c67e065c2 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlJdbcContext.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlJdbcContext.java
@@ -36,12 +36,19 @@ public class MysqlJdbcContext extends
UniversalJdbcContext<MysqlPartition, Mysql
private volatile long currentHandleEventSize = 0;
- private volatile boolean isStartTransaction = false;
+ private volatile boolean onTransaction = false;
+ //need to load from store when start
private String restartGtidSet;
private String currentGtidSet;
+ private String restartBinlogFilename;
+
+ private long restartBinlogPosition;
+
+ private String transactionId;
+
private JdbcSourceConfig jdbcSourceConfig;
public MysqlJdbcContext(PartitionOffSetContextPair<MysqlPartition,
MysqlOffsetContext> poCtx, MysqlAntlr4DdlParser parser) {
@@ -80,19 +87,22 @@ public class MysqlJdbcContext extends
UniversalJdbcContext<MysqlPartition, Mysql
}
public void startTransaction() {
- this.isStartTransaction = true;
+ this.onTransaction = true;
}
public void commitTransaction() {
- this.isStartTransaction = false;
+ this.onTransaction = false;
+ this.restartGtidSet = this.currentGtidSet;
+ this.restartBinlogFilename = sourceInfo.getCurrentBinlogFileName();
+ this.restartBinlogPosition = sourceInfo.getCurrentBinlogPosition() +
this.currentHandleEventSize;
+ resetTransactionId();
}
public void complete() {
- //TODO: What to do?
this.currentHandleEventSize = 0;
}
- public void setCompletedGtidSet(String gtidSet) {
+ public void completedGtidSet(String gtidSet) {
if (StringUtils.isNotBlank(gtidSet)) {
String trimmedGtidSet = gtidSet.replace("\n", "").replace("\r",
"");
this.currentGtidSet = trimmedGtidSet;
@@ -101,8 +111,14 @@ public class MysqlJdbcContext extends
UniversalJdbcContext<MysqlPartition, Mysql
}
public String getGtidSet() {
- return this.currentGtidSet != null ? this.currentGtidSet : null;
+ return this.currentGtidSet;
}
+ public void beginGtid(String gtid) {
+ this.sourceInfo.beginGtid(gtid);
+ }
+ private void resetTransactionId() {
+ transactionId = null;
+ }
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlSourceInfo.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlSourceInfo.java
index f362c9be9..7ff536aaa 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlSourceInfo.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/mysql/MysqlSourceInfo.java
@@ -27,10 +27,13 @@ public class MysqlSourceInfo implements SourceInfo {
private String currentBinlogFileName;
- private long currentBinlogPosition = 0L;
+ private volatile long currentBinlogPosition = 0L;
private int currentRowNumber = 0;
+ // Support mysql gtid
+ private String currentGtid;
+
public void setBinlogPosition(String binlogFileName, long
beginProcessPosition) {
if (binlogFileName != null) {
this.currentBinlogFileName = binlogFileName;
@@ -39,4 +42,8 @@ public class MysqlSourceInfo implements SourceInfo {
this.currentBinlogPosition = beginProcessPosition;
this.currentRowNumber = 0;
}
+
+ public void beginGtid(String gtid) {
+ this.currentGtid = gtid;
+ }
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/snapshot/AbstractSnapshotEngine.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/snapshot/AbstractSnapshotEngine.java
index 348bb7efa..598068d20 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/snapshot/AbstractSnapshotEngine.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/snapshot/AbstractSnapshotEngine.java
@@ -18,6 +18,8 @@
package org.apache.eventmesh.connector.jdbc.source.dialect.snapshot;
import org.apache.eventmesh.common.ThreadPoolFactory;
+import org.apache.eventmesh.connector.jdbc.DataChanges;
+import org.apache.eventmesh.connector.jdbc.DataChanges.Builder;
import org.apache.eventmesh.connector.jdbc.DatabaseDialect;
import org.apache.eventmesh.connector.jdbc.Field;
import org.apache.eventmesh.connector.jdbc.JdbcContext;
@@ -217,6 +219,21 @@ public abstract class AbstractSnapshotEngine<DbDialect
extends DatabaseDialect<J
universalJdbcContext.withTableId(tableId);
return () -> {
JdbcConnection connection = connectionPool.poll();
+ MysqlSourceMateData sourceMateData =
MysqlSourceMateData.newBuilder()
+ .name(sourceConnectorConfig.getName())
+ .snapshot(true)
+ .withTableId(tableId)
+ .serverId(sourceConnectorConfig.getMysqlConfig().getServerId())
+ .build();
+ TableSchema tableSchema =
universalJdbcContext.getCatalogTableSet().getTableSchema(tableId);
+ Field field = new
Field().withField("after").withType("field").withName("payload.after").withRequired(false);
+ List<? extends Column> columns = tableSchema.getColumns();
+ if (CollectionUtils.isNotEmpty(columns)) {
+ List<Field> fields = columns.stream()
+ .map(col -> new Field(col.getDataType().getName(),
col.isNotNull(), col.getName(), tableId.toString()))
+ .collect(Collectors.toList());
+ field.withRequired(true).withFields(fields);
+ }
try (Statement statement =
connection.createStatement(jdbcSourceConfig.getSourceConnectorConfig().getSnapshotFetchSize(),
100)) {
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
@@ -227,23 +244,10 @@ public abstract class AbstractSnapshotEngine<DbDialect
extends DatabaseDialect<J
for (int index = 1; index <= columnCount; ++index) {
values.put(resultSet.getMetaData().getColumnName(index),
resultSet.getObject(index));
}
- payload.withAfterValue(values);
- MysqlSourceMateData sourceMateData =
MysqlSourceMateData.newBuilder()
- .name(sourceConnectorConfig.getName())
- .snapshot(true)
- .withTableId(tableId)
-
.serverId(sourceConnectorConfig.getMysqlConfig().getServerId())
- .build();
+ Builder builder = DataChanges.newBuilder();
+ builder.withAfter(values);
+ payload.withDataChanges(builder.build());
payload.withSource(sourceMateData);
- TableSchema tableSchema =
universalJdbcContext.getCatalogTableSet().getTableSchema(tableId);
- Field field = new
Field().withField("after").withType("field").withName("payload.after").withRequired(false);
- List<? extends Column> columns = tableSchema.getColumns();
- if (CollectionUtils.isNotEmpty(columns)) {
- List<Field> fields = columns.stream()
- .map(col -> new Field(col.getDataType().getName(),
col.isNotNull(), col.getName(), tableId.toString()))
- .collect(Collectors.toList());
- field.withRequired(true).withFields(fields);
- }
event.getJdbcConnectData().setSchema(new
Schema(Collections.singletonList(field)));
eventQueue.put(event);
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/snapshot/mysql/MysqlSnapshotEngine.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/snapshot/mysql/MysqlSnapshotEngine.java
index 77f054bed..cc8e6eafc 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/snapshot/mysql/MysqlSnapshotEngine.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/source/dialect/snapshot/mysql/MysqlSnapshotEngine.java
@@ -24,6 +24,7 @@ import org.apache.eventmesh.connector.jdbc.event.Event;
import org.apache.eventmesh.connector.jdbc.event.EventConsumer;
import org.apache.eventmesh.connector.jdbc.source.config.JdbcSourceConfig;
import org.apache.eventmesh.connector.jdbc.source.config.MysqlConfig;
+import org.apache.eventmesh.connector.jdbc.source.dialect.mysql.MysqlConstants;
import
org.apache.eventmesh.connector.jdbc.source.dialect.mysql.MysqlDatabaseDialect;
import
org.apache.eventmesh.connector.jdbc.source.dialect.mysql.MysqlDialectSql;
import
org.apache.eventmesh.connector.jdbc.source.dialect.mysql.MysqlJdbcContext;
@@ -38,7 +39,6 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
@@ -52,15 +52,6 @@ import lombok.extern.slf4j.Slf4j;
public class MysqlSnapshotEngine extends
AbstractSnapshotEngine<MysqlDatabaseDialect, MysqlJdbcContext,
MysqlPartition, MysqlOffsetContext, MysqlJdbcConnection> {
- private static final Set<String> DEFAULT_EXCLUDE_DATABASE = new
HashSet<>();
-
- static {
- DEFAULT_EXCLUDE_DATABASE.add("information_schema");
- DEFAULT_EXCLUDE_DATABASE.add("mysql");
- DEFAULT_EXCLUDE_DATABASE.add("performance_schema");
- DEFAULT_EXCLUDE_DATABASE.add("sys");
- }
-
private volatile boolean globalLockAcquired = false;
private volatile boolean tableLockAcquired = false;
@@ -77,7 +68,7 @@ public class MysqlSnapshotEngine extends
@Override
protected Set<String> defaultExcludeDatabase() {
- return DEFAULT_EXCLUDE_DATABASE;
+ return MysqlConstants.DEFAULT_EXCLUDE_DATABASE;
}
@Override
@@ -133,7 +124,7 @@ public class MysqlSnapshotEngine extends
jdbcContext.setBinlogStartPoint(binlogFilename, position);
if (resultSet.getMetaData().getColumnCount() >= 5) {
final String gtidSet = resultSet.getString(5);
- jdbcContext.setCompletedGtidSet(gtidSet);
+ jdbcContext.completedGtidSet(gtidSet);
log.info("Using binlog '{}' at position '{}' and gtid
'{}'", binlogFilename, position, gtidSet);
} else {
log.info("Using binlog '{}' at position '{}' ",
binlogFilename, position);
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/AbstractColumnEditorImpl.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/AbstractColumnEditorImpl.java
index 32c416c5b..4f7d1d5b4 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/AbstractColumnEditorImpl.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/AbstractColumnEditorImpl.java
@@ -66,6 +66,8 @@ public abstract class AbstractColumnEditorImpl<CE extends
ColumnEditor, Col exte
private boolean optional;
+ private int order;
+
public AbstractColumnEditorImpl(String name) {
this.name = name;
}
@@ -136,6 +138,19 @@ public abstract class AbstractColumnEditorImpl<CE extends
ColumnEditor, Col exte
return (CE) this;
}
+ /**
+ * Sets the order or position of the column within a table.
+ *
+ * @param order The order or position of the column.
+ * @return The column editor instance.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public CE withOrder(int order) {
+ this.order = order;
+ return (CE) this;
+ }
+
/**
* Sets the length of the column (if applicable).
*
@@ -266,4 +281,8 @@ public abstract class AbstractColumnEditorImpl<CE extends
ColumnEditor, Col exte
public boolean isOptional() {
return optional;
}
+
+ public int ofOrder() {
+ return this.order;
+ }
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/ColumnEditor.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/ColumnEditor.java
index c72c3f22e..30a039b63 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/ColumnEditor.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/ColumnEditor.java
@@ -69,6 +69,14 @@ public interface ColumnEditor<CE extends ColumnEditor, Col
extends Column> {
*/
CE withEventMeshType(EventMeshDataType<?> eventMeshType);
+ /**
+ * Sets the order or position of the column within a table.
+ *
+ * @param order The order or position of the column.
+ * @return The column editor instance.
+ */
+ CE withOrder(int order);
+
/**
* Sets the length of the column (if applicable).
*
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/TableId.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/TableId.java
index 2e6db21d3..37be43329 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/TableId.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/TableId.java
@@ -159,4 +159,8 @@ public class TableId implements Serializable {
return tableId(tableId.getCatalogName(), tableId.getSchemaName(),
tableId.getTableName());
}
}
+
+ public TableId copy() {
+ return new TableId(this.catalogName, this.schemaName, this.tableName);
+ }
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/TableSchema.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/TableSchema.java
index 94f88052e..3eadd78d5 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/TableSchema.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/TableSchema.java
@@ -28,12 +28,10 @@ import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
-import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
-@AllArgsConstructor
@NoArgsConstructor
public class TableSchema implements Serializable {
@@ -49,6 +47,8 @@ public class TableSchema implements Serializable {
*/
private List<? extends Column> columns;
+ private Map<Integer, ? extends Column> orderColumnMap;
+
/**
* The primary key of the table.
*/
@@ -58,6 +58,17 @@ public class TableSchema implements Serializable {
private String comment;
+ public TableSchema(TableId tableId, Map<String, ? extends Column>
columnMap, List<? extends Column> columns,
+ Map<Integer, ? extends Column> orderColumnMap, PrimaryKey primaryKey,
List<UniqueKey> uniqueKeys, String comment) {
+ this.tableId = tableId;
+ this.columnMap = columnMap;
+ this.columns = columns;
+ this.orderColumnMap = orderColumnMap;
+ this.primaryKey = primaryKey;
+ this.uniqueKeys = uniqueKeys;
+ this.comment = comment;
+ }
+
public TableSchema(String name) {
this.tableId.setTableName(name);
}
@@ -78,6 +89,7 @@ public class TableSchema implements Serializable {
private TableId tableId;
private Map<String, Column> columnMap;
+ private Map<Integer, Column> orderColumnMap;
private List<Column> columns;
private PrimaryKey primaryKey;
private List<UniqueKey> uniqueKeys;
@@ -118,7 +130,7 @@ public class TableSchema implements Serializable {
}
public TableSchema build() {
- return new TableSchema(tableId, columnMap, columns, primaryKey,
uniqueKeys, comment);
+ return new TableSchema(tableId, columnMap, columns,
orderColumnMap, primaryKey, uniqueKeys, comment);
}
}
@@ -131,6 +143,7 @@ public class TableSchema implements Serializable {
private TableId tableId = new TableId();
private Map<String, MysqlColumn> columnMap;
+ private Map<Integer, MysqlColumn> orderColumnMap;
private List<MysqlColumn> columns;
private PrimaryKey primaryKey;
private List<UniqueKey> uniqueKeys;
@@ -155,6 +168,8 @@ public class TableSchema implements Serializable {
this.columns = columns;
this.columnMap = Optional.ofNullable(columns).orElse(new
ArrayList<>(0)).stream()
.collect(Collectors.toMap(MysqlColumn::getName,
Function.identity()));
+ this.orderColumnMap = Optional.ofNullable(columns).orElse(new
ArrayList<>(0)).stream()
+ .collect(Collectors.toMap(MysqlColumn::getOrder,
Function.identity()));
return this;
}
@@ -184,7 +199,7 @@ public class TableSchema implements Serializable {
}
public MysqlTableSchema build() {
- return new MysqlTableSchema(tableId, columnMap, columns,
primaryKey, uniqueKeys, comment);
+ return new MysqlTableSchema(tableId, columnMap, columns,
orderColumnMap, primaryKey, uniqueKeys, comment, tableOptions);
}
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/mysql/MysqlColumnEditorImpl.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/mysql/MysqlColumnEditorImpl.java
index 2b7db0591..c7b7648af 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/mysql/MysqlColumnEditorImpl.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/mysql/MysqlColumnEditorImpl.java
@@ -78,6 +78,6 @@ public class MysqlColumnEditorImpl extends
AbstractColumnEditorImpl<MysqlColumnE
@Override
public MysqlColumn build() {
return MysqlColumn.of(ofName(), ofEventMeshDataType(), ofJdbcType(),
ofColumnLength(), ofScale(), isNotNull(), ofComment(), ofDefaultValue(),
- ofDefaultValueExpression(), autoIncremented, generated,
collationName);
+ ofDefaultValueExpression(), autoIncremented, generated,
collationName, ofOrder());
}
}
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/mysql/MysqlTableSchema.java
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/mysql/MysqlTableSchema.java
index 8e61d7af9..c26ca5c3c 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/mysql/MysqlTableSchema.java
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/java/org/apache/eventmesh/connector/jdbc/table/catalog/mysql/MysqlTableSchema.java
@@ -30,9 +30,10 @@ public class MysqlTableSchema extends TableSchema {
private Options tableOptions;
- public MysqlTableSchema(TableId tableId, Map<String, MysqlColumn>
columnMap, List<MysqlColumn> columns, PrimaryKey primaryKey,
- List<UniqueKey> uniqueKeys, String comment) {
- super(tableId, columnMap, columns, primaryKey, uniqueKeys, comment);
+ public MysqlTableSchema(TableId tableId, Map<String, MysqlColumn>
columnMap, List<MysqlColumn> columns, Map<Integer, MysqlColumn> orderColumnMap,
+ PrimaryKey primaryKey, List<UniqueKey> uniqueKeys, String comment,
Options tableOptions) {
+ super(tableId, columnMap, columns, orderColumnMap, primaryKey,
uniqueKeys, comment);
+ this.tableOptions = tableOptions;
}
public MysqlTableSchema() {
diff --git
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.connector.jdbc.source.dialect.cdc.CdcEngineFactory
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.connector.jdbc.source.dialect.cdc.CdcEngineFactory
index 8b07afd30..06eb1b99e 100644
---
a/eventmesh-connectors/eventmesh-connector-jdbc/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.connector.jdbc.source.dialect.cdc.CdcEngineFactory
+++
b/eventmesh-connectors/eventmesh-connector-jdbc/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.connector.jdbc.source.dialect.cdc.CdcEngineFactory
@@ -13,4 +13,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-#mysql=org.apache.eventmesh.connector.jdbc.source.dialect.cdc.mysql.MysqlCdcEngineFactory
+mysql=org.apache.eventmesh.connector.jdbc.source.dialect.cdc.mysql.MysqlCdcEngineFactory
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]