|
27 | 27 | import io.cdap.delta.api.SourceTable; |
28 | 28 | import io.cdap.delta.plugin.common.Records; |
29 | 29 | import io.debezium.connector.mysql.MySqlValueConverters; |
| 30 | +import io.debezium.embedded.StopConnectorException; |
30 | 31 | import io.debezium.relational.Table; |
31 | 32 | import io.debezium.relational.TableId; |
32 | 33 | import io.debezium.relational.Tables; |
|
40 | 41 | import java.io.IOException; |
41 | 42 | import java.util.HashMap; |
42 | 43 | import java.util.Map; |
| 44 | +import java.util.concurrent.atomic.AtomicReference; |
43 | 45 | import java.util.function.Consumer; |
44 | 46 |
|
45 | 47 | /** |
@@ -128,88 +130,28 @@ public void accept(SourceRecord sourceRecord) { |
128 | 130 | // If the map is empty, we should read all DDL/DML events and columns of all tables |
129 | 131 | boolean readAllTables = sourceTableMap.isEmpty(); |
130 | 132 |
|
131 | | - if (ddl != null) { |
132 | | - ddlParser.getDdlChanges().reset(); |
133 | | - ddlParser.parse(ddl, tables); |
134 | | - ddlParser.getDdlChanges().groupEventsByDatabase((database, events) -> { |
135 | | - for (DdlParserListener.Event event : events) { |
136 | | - DDLEvent.Builder builder = DDLEvent.builder() |
137 | | - .setDatabase(database) |
138 | | - .setOffset(recordOffset) |
139 | | - .setSnapshot(isSnapshot); |
140 | | - // since current ddl blacklist implementation is bind with table level, we will only do the ddl blacklist |
141 | | - // checking only for table change related ddl event, includes: ALTER_TABLE, RENAME_TABLE, DROP_TABLE, |
142 | | - // CREATE_TABLE and TRUNCATE_TABLE. |
143 | | - switch (event.type()) { |
144 | | - case ALTER_TABLE: |
145 | | - DdlParserListener.TableAlteredEvent alteredEvent = (DdlParserListener.TableAlteredEvent) event; |
146 | | - TableId tableId = alteredEvent.tableId(); |
147 | | - Table table = tables.forTable(tableId); |
148 | | - SourceTable sourceTable = getSourceTable(database, tableId.table()); |
149 | | - DDLOperation ddlOp; |
150 | | - if (alteredEvent.previousTableId() != null) { |
151 | | - ddlOp = DDLOperation.RENAME_TABLE; |
152 | | - builder.setPrevTable(alteredEvent.previousTableId().table()); |
153 | | - } else { |
154 | | - ddlOp = DDLOperation.ALTER_TABLE; |
155 | | - } |
156 | | - |
157 | | - if (shouldEmitDdlEventForOperation(readAllTables, sourceTable, ddlOp)) { |
158 | | - emitter.emit(builder.setOperation(ddlOp) |
159 | | - .setTable(tableId.table()) |
160 | | - .setSchema(readAllTables ? Records.getSchema(table, mySqlValueConverters) : |
161 | | - Records.getSchema(table, mySqlValueConverters, sourceTable.getColumns())) |
162 | | - .setPrimaryKey(table.primaryKeyColumnNames()) |
163 | | - .build()); |
164 | | - } |
165 | | - break; |
166 | | - case DROP_TABLE: |
167 | | - DdlParserListener.TableDroppedEvent droppedEvent = (DdlParserListener.TableDroppedEvent) event; |
168 | | - sourceTable = getSourceTable(database, droppedEvent.tableId().table()); |
169 | | - if (shouldEmitDdlEventForOperation(readAllTables, sourceTable, DDLOperation.DROP_TABLE)) { |
170 | | - emitter.emit(builder.setOperation(DDLOperation.DROP_TABLE) |
171 | | - .setTable(droppedEvent.tableId().table()) |
172 | | - .build()); |
173 | | - } |
174 | | - break; |
175 | | - case CREATE_TABLE: |
176 | | - DdlParserListener.TableCreatedEvent createdEvent = (DdlParserListener.TableCreatedEvent) event; |
177 | | - tableId = createdEvent.tableId(); |
178 | | - table = tables.forTable(tableId); |
179 | | - sourceTable = getSourceTable(database, tableId.table()); |
180 | | - if (shouldEmitDdlEventForOperation(readAllTables, sourceTable, DDLOperation.CREATE_TABLE)) { |
181 | | - emitter.emit(builder.setOperation(DDLOperation.CREATE_TABLE) |
182 | | - .setTable(tableId.table()) |
183 | | - .setSchema(readAllTables ? Records.getSchema(table, mySqlValueConverters) : |
184 | | - Records.getSchema(table, mySqlValueConverters, sourceTable.getColumns())) |
185 | | - .setPrimaryKey(table.primaryKeyColumnNames()) |
186 | | - .build()); |
187 | | - } |
188 | | - break; |
189 | | - case DROP_DATABASE: |
190 | | - emitter.emit(builder.setOperation(DDLOperation.DROP_DATABASE).build()); |
191 | | - break; |
192 | | - case CREATE_DATABASE: |
193 | | - emitter.emit(builder.setOperation(DDLOperation.CREATE_DATABASE).build()); |
194 | | - break; |
195 | | - case TRUNCATE_TABLE: |
196 | | - DdlParserListener.TableTruncatedEvent truncatedEvent = |
197 | | - (DdlParserListener.TableTruncatedEvent) event; |
198 | | - sourceTable = getSourceTable(database, truncatedEvent.tableId().table()); |
199 | | - if (shouldEmitDdlEventForOperation(readAllTables, sourceTable, DDLOperation.TRUNCATE_TABLE)) { |
200 | | - emitter.emit(builder.setOperation(DDLOperation.TRUNCATE_TABLE) |
201 | | - .setTable(truncatedEvent.tableId().table()) |
202 | | - .build()); |
203 | | - } |
204 | | - break; |
205 | | - default: |
206 | | - return; |
207 | | - } |
208 | | - } |
209 | | - }); |
210 | | - return; |
| 133 | + try { |
| 134 | + if (ddl != null) { |
| 135 | + handleDDL(ddl, recordOffset, isSnapshot, readAllTables); |
| 136 | + return; |
| 137 | + } |
| 138 | + |
| 139 | + String databaseName = source.get("db"); |
| 140 | + String tableName = source.get("table"); |
| 141 | + SourceTable sourceTable = getSourceTable(databaseName, tableName); |
| 142 | + if (sourceTableNotValid(readAllTables, sourceTable)) { |
| 143 | + return; |
| 144 | + } |
| 145 | + |
| 146 | + handleDML(source, val, recordOffset, isSnapshot, readAllTables); |
| 147 | + } catch (InterruptedException e) { |
| 148 | + // happens when the event reader is stopped. throwing this exception tells Debezium to stop right away |
| 149 | + throw new StopConnectorException("Interrupted while emitting event."); |
211 | 150 | } |
| 151 | + } |
212 | 152 |
|
| 153 | + private void handleDML(StructuredRecord source, StructuredRecord val, Offset recordOffset, |
| 154 | + boolean isSnapshot, boolean readAllTables) throws InterruptedException { |
213 | 155 | String databaseName = source.get("db"); |
214 | 156 | String tableName = source.get("table"); |
215 | 157 | SourceTable sourceTable = getSourceTable(databaseName, tableName); |
@@ -274,6 +216,108 @@ public void accept(SourceRecord sourceRecord) { |
274 | 216 | } |
275 | 217 | } |
276 | 218 |
|
| 219 | + private void handleDDL(String ddlStatement, Offset recordOffset, |
| 220 | + boolean isSnapshot, boolean readAllTables) throws InterruptedException { |
| 221 | + ddlParser.getDdlChanges().reset(); |
| 222 | + ddlParser.parse(ddlStatement, tables); |
| 223 | + AtomicReference<InterruptedException> interrupted = new AtomicReference<>(); |
| 224 | + ddlParser.getDdlChanges().groupEventsByDatabase((database, events) -> { |
| 225 | + if (interrupted.get() != null) { |
| 226 | + return; |
| 227 | + } |
| 228 | + for (DdlParserListener.Event event : events) { |
| 229 | + DDLEvent.Builder builder = DDLEvent.builder() |
| 230 | + .setOffset(recordOffset) |
| 231 | + .setDatabase(database) |
| 232 | + .setSnapshot(isSnapshot); |
| 233 | + DDLEvent ddlEvent = null; |
| 234 | + // since current ddl blacklist implementation is bind with table level, we will only do the ddl blacklist |
| 235 | + // checking only for table change related ddl event, includes: ALTER_TABLE, RENAME_TABLE, DROP_TABLE, |
| 236 | + // CREATE_TABLE and TRUNCATE_TABLE. |
| 237 | + switch (event.type()) { |
| 238 | + case ALTER_TABLE: |
| 239 | + DdlParserListener.TableAlteredEvent alteredEvent = (DdlParserListener.TableAlteredEvent) event; |
| 240 | + TableId tableId = alteredEvent.tableId(); |
| 241 | + Table table = tables.forTable(tableId); |
| 242 | + SourceTable sourceTable = getSourceTable(database, tableId.table()); |
| 243 | + DDLOperation ddlOp; |
| 244 | + if (alteredEvent.previousTableId() != null) { |
| 245 | + ddlOp = DDLOperation.RENAME_TABLE; |
| 246 | + builder.setPrevTable(alteredEvent.previousTableId().table()); |
| 247 | + } else { |
| 248 | + ddlOp = DDLOperation.ALTER_TABLE; |
| 249 | + } |
| 250 | + |
| 251 | + if (shouldEmitDdlEventForOperation(readAllTables, sourceTable, ddlOp)) { |
| 252 | + ddlEvent = builder.setOperation(ddlOp) |
| 253 | + .setTable(tableId.table()) |
| 254 | + .setSchema(readAllTables ? Records.getSchema(table, mySqlValueConverters) : |
| 255 | + Records.getSchema(table, mySqlValueConverters, sourceTable.getColumns())) |
| 256 | + .setPrimaryKey(table.primaryKeyColumnNames()) |
| 257 | + .build(); |
| 258 | + } |
| 259 | + break; |
| 260 | + case DROP_TABLE: |
| 261 | + DdlParserListener.TableDroppedEvent droppedEvent = (DdlParserListener.TableDroppedEvent) event; |
| 262 | + sourceTable = getSourceTable(database, droppedEvent.tableId().table()); |
| 263 | + if (shouldEmitDdlEventForOperation(readAllTables, sourceTable, DDLOperation.DROP_TABLE)) { |
| 264 | + ddlEvent = builder.setOperation(DDLOperation.DROP_TABLE) |
| 265 | + .setTable(droppedEvent.tableId().table()) |
| 266 | + .build(); |
| 267 | + } |
| 268 | + break; |
| 269 | + case CREATE_TABLE: |
| 270 | + DdlParserListener.TableCreatedEvent createdEvent = (DdlParserListener.TableCreatedEvent) event; |
| 271 | + tableId = createdEvent.tableId(); |
| 272 | + table = tables.forTable(tableId); |
| 273 | + sourceTable = getSourceTable(database, tableId.table()); |
| 274 | + if (shouldEmitDdlEventForOperation(readAllTables, sourceTable, DDLOperation.CREATE_TABLE)) { |
| 275 | + ddlEvent = builder.setOperation(DDLOperation.CREATE_TABLE) |
| 276 | + .setTable(tableId.table()) |
| 277 | + .setSchema(readAllTables ? Records.getSchema(table, mySqlValueConverters) : |
| 278 | + Records.getSchema(table, mySqlValueConverters, sourceTable.getColumns())) |
| 279 | + .setPrimaryKey(table.primaryKeyColumnNames()) |
| 280 | + .build(); |
| 281 | + } |
| 282 | + break; |
| 283 | + case DROP_DATABASE: |
| 284 | + ddlEvent = builder.setOperation(DDLOperation.DROP_DATABASE).build(); |
| 285 | + break; |
| 286 | + case CREATE_DATABASE: |
| 287 | + // due to a bug in io.debezium.relational.ddl.AbstractDdlParser#signalDropDatabase |
| 288 | + // a DROP_DATABASE event will be mistakenly categorized as a CREATE_DATABASE event. |
| 289 | + // TODO: check if this is fixed in a newer debezium version |
| 290 | + if (event.statement() != null && event.statement().startsWith("DROP DATABASE")) { |
| 291 | + ddlEvent = builder.setOperation(DDLOperation.DROP_DATABASE).build(); |
| 292 | + } else { |
| 293 | + ddlEvent = builder.setOperation(DDLOperation.CREATE_DATABASE).build(); |
| 294 | + } |
| 295 | + break; |
| 296 | + case TRUNCATE_TABLE: |
| 297 | + DdlParserListener.TableTruncatedEvent truncatedEvent = |
| 298 | + (DdlParserListener.TableTruncatedEvent) event; |
| 299 | + sourceTable = getSourceTable(database, truncatedEvent.tableId().table()); |
| 300 | + if (shouldEmitDdlEventForOperation(readAllTables, sourceTable, DDLOperation.TRUNCATE_TABLE)) { |
| 301 | + ddlEvent = builder.setOperation(DDLOperation.TRUNCATE_TABLE) |
| 302 | + .setTable(truncatedEvent.tableId().table()) |
| 303 | + .build(); |
| 304 | + } |
| 305 | + break; |
| 306 | + } |
| 307 | + if (ddlEvent != null) { |
| 308 | + try { |
| 309 | + emitter.emit(ddlEvent); |
| 310 | + } catch (InterruptedException e) { |
| 311 | + interrupted.set(e); |
| 312 | + } |
| 313 | + } |
| 314 | + } |
| 315 | + }); |
| 316 | + if (interrupted.get() != null) { |
| 317 | + throw interrupted.get(); |
| 318 | + } |
| 319 | + } |
| 320 | + |
277 | 321 | private boolean shouldEmitDdlEventForOperation(boolean readAllTables, SourceTable sourceTable, DDLOperation op) { |
278 | 322 | return (!sourceTableNotValid(readAllTables, sourceTable)) && |
279 | 323 | (!isDDLOperationBlacklisted(readAllTables, sourceTable, op)); |
|
0 commit comments