diff --git a/debezium-connector-oracle/src/main/java/io/debezium/connector/oracle/logminer/parser/SelectLobParser.java b/debezium-connector-oracle/src/main/java/io/debezium/connector/oracle/logminer/parser/SelectLobParser.java index 7c7f588a3..65890fb05 100644 --- a/debezium-connector-oracle/src/main/java/io/debezium/connector/oracle/logminer/parser/SelectLobParser.java +++ b/debezium-connector-oracle/src/main/java/io/debezium/connector/oracle/logminer/parser/SelectLobParser.java @@ -150,7 +150,7 @@ else if (c == ')' && !inSingleQuotes) { else if (c == '\'') { // skip over double single quote if (inSingleQuotes && lookAhead == '\'') { - i += 2; + i += 1; continue; } if (inSingleQuotes) { diff --git a/debezium-connector-oracle/src/test/java/io/debezium/connector/oracle/OracleClobDataTypeIT.java b/debezium-connector-oracle/src/test/java/io/debezium/connector/oracle/OracleClobDataTypeIT.java index 85136e95c..ff1e65dc8 100644 --- a/debezium-connector-oracle/src/test/java/io/debezium/connector/oracle/OracleClobDataTypeIT.java +++ b/debezium-connector-oracle/src/test/java/io/debezium/connector/oracle/OracleClobDataTypeIT.java @@ -1908,13 +1908,14 @@ public void shouldStreamClobsWrittenInChunkedMode() throws Exception { } @Test - @FixFor({ "DBZ-4891", "DBZ-4862" }) + @FixFor({ "DBZ-4891", "DBZ-4862", "DBZ-4994" }) public void shouldStreamClobValueWithEscapedSingleQuoteValue() throws Exception { String ddl = "CREATE TABLE CLOB_TEST (" + "ID numeric(9,0), " + "VAL_CLOB clob, " + "VAL_NCLOB nclob, " + "VAL_USERNAME varchar2(100)," + + "VAL_DATA varchar2(100), " + "primary key(id))"; connection.execute(ddl); @@ -1929,15 +1930,10 @@ public void shouldStreamClobValueWithEscapedSingleQuoteValue() throws Exception assertConnectorIsRunning(); waitForSnapshotToBeCompleted(TestHelper.CONNECTOR_NAME, TestHelper.SERVER_NAME); - // Insert record - Clob clob1 = createClob(part(JSON_DATA, 0, 25000)); - NClob nclob1 = createNClob(part(JSON_DATA2, 0, 25000)); - connection.prepareQuery("INSERT INTO clob_test VALUES (1, ?, ?, ?)", ps -> { - ps.setClob(1, clob1); - ps.setNClob(2, nclob1); - ps.setString(3, "This will be fixed soon so please don't worry, she wrote."); - }, null); - connection.commit(); + // Create simple insert, will be used for updates later + final String simpleQuote = "This will be fixed soon so please don''t worry, she wrote."; + final String complexQuote = "2\"''\" sd f\"\"\" '''''''' ''''"; + connection.execute("INSERT INTO clob_test (id,val_username,val_data) values (1,'" + simpleQuote + "','" + complexQuote + "')"); SourceRecords records = consumeRecordsByTopic(1); assertThat(records.recordsForTopic(topicName("CLOB_TEST"))).hasSize(1); @@ -1945,11 +1941,29 @@ public void shouldStreamClobValueWithEscapedSingleQuoteValue() throws Exception SourceRecord record = records.recordsForTopic(topicName("CLOB_TEST")).get(0); VerifyRecord.isValidInsert(record, "ID", 1); + // Update the record this way to enforce that both varchar fields are present in the SELECT_LOB_LOCATOR + // event that will need to be parsed by the SelectLobParser component. + Clob clob1 = createClob(part(JSON_DATA, 0, 25000)); + NClob nclob1 = createNClob(part(JSON_DATA2, 0, 25000)); + connection.prepareQuery("update clob_test set val_clob=?, val_nclob=? where id=1", ps -> { + ps.setClob(1, clob1); + ps.setClob(2, nclob1); + }, null); + connection.commit(); + + records = consumeRecordsByTopic(1); + assertThat(records.recordsForTopic(topicName("CLOB_TEST"))).hasSize(1); + + record = records.recordsForTopic(topicName("CLOB_TEST")).get(0); + VerifyRecord.isValidUpdate(record, "ID", 1); + + // Validate update data Struct after = after(record); assertThat(after.get("ID")).isEqualTo(1); assertThat(after.get("VAL_CLOB")).isEqualTo(getClobString(clob1)); assertThat(after.get("VAL_NCLOB")).isEqualTo(getClobString(nclob1)); assertThat(after.get("VAL_USERNAME")).isEqualTo("This will be fixed soon so please don't worry, she wrote."); + assertThat(after.get("VAL_DATA")).isEqualTo("2\"'\" sd f\"\"\" '''' ''"); } private Clob createClob(String data) throws SQLException { diff --git a/debezium-connector-oracle/src/test/java/io/debezium/connector/oracle/logminer/SelectLobParserTest.java b/debezium-connector-oracle/src/test/java/io/debezium/connector/oracle/logminer/SelectLobParserTest.java index 06598374e..b053b86dc 100644 --- a/debezium-connector-oracle/src/test/java/io/debezium/connector/oracle/logminer/SelectLobParserTest.java +++ b/debezium-connector-oracle/src/test/java/io/debezium/connector/oracle/logminer/SelectLobParserTest.java @@ -206,4 +206,42 @@ public void shouldParseComplexBlobBasedLobSelect() throws Exception { assertThat(entry.getNewValues()[5]).isNull(); } + @Test + @FixFor("DBZ-4994") + public void shouldParseColumnWithEscapedSingleQuoteColumnValues() throws Exception { + final Table table = Table.editor() + .tableId(TableId.parse("DEBEZIUM.QUOTE_TABLE")) + .addColumn(Column.editor().name("ID").create()) + .addColumn(Column.editor().name("NAME").create()) + .addColumn(Column.editor().name("CLOB_COL").create()) + .create(); + + String redoSql = "DECLARE \n" + + " loc_c CLOB; \n" + + " buf_c VARCHAR2(6426); \n" + + " loc_b BLOB; \n" + + " buf_b RAW(6426); \n" + + " loc_nc NCLOB; \n" + + " buf_nc NVARCHAR2(6426); \n" + + "BEGIN\n" + + " select \"CLOB_COL\" into loc_c from \"DEBEZIUM\".\"QUOTE_TABLE\" where \"ID\" = '1' and \"NAME\" = " + + "'2\"''\" sd f\"\"\" '''''''' ''''' for update;"; + + LogMinerDmlEntry entry = parser.parse(redoSql, table); + + assertThat(parser.isBinary()).isFalse(); + assertThat(parser.getColumnName()).isEqualTo("CLOB_COL"); + + assertThat(entry.getObjectOwner()).isEqualTo("DEBEZIUM"); + assertThat(entry.getObjectName()).isEqualTo("QUOTE_TABLE"); + assertThat(entry.getEventType()).isEqualTo(EventType.SELECT_LOB_LOCATOR); + assertThat(entry.getOldValues()).hasSize(3); + assertThat(entry.getOldValues()[0]).isEqualTo("1"); + assertThat(entry.getOldValues()[1]).isEqualTo("2\"''\" sd f\"\"\" '''''''' ''''"); + assertThat(entry.getOldValues()[2]).isNull(); + assertThat(entry.getNewValues()).hasSize(3); + assertThat(entry.getNewValues()[0]).isEqualTo("1"); + assertThat(entry.getNewValues()[1]).isEqualTo("2\"''\" sd f\"\"\" '''''''' ''''"); + assertThat(entry.getNewValues()[2]).isNull(); + } }