Skip to content

Commit 036d128

Browse files
authored
Merge pull request #2549 from potoo0/issue-2548
[JDBC-V2] fix(`StatementImpl`/`PreparedStatementImpl`): prevent duplicate data insertion by clearing batch after execution for issue-2548
2 parents 3d12e6e + 81b74a1 commit 036d128

File tree

5 files changed

+165
-3
lines changed

5 files changed

+165
-3
lines changed

examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/Basic.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,20 @@ static void insertDateWithPreparedStatement(String url, Properties properties) t
5959
pstmt.setString(3, "Alice");//Set the third parameter to "Alice"
6060
pstmt.setObject(4, Collections.singletonMap("key1", "value1"));
6161
pstmt.addBatch();//Add the current parameters to the batch
62+
pstmt.executeBatch();//Execute the batch
6263

6364
pstmt.setObject(1, ZonedDateTime.now());
6465
pstmt.setInt(2, 2);//Set the second parameter to 2
6566
pstmt.setString(3, "Bob");//Set the third parameter to "Bob"
6667
pstmt.setObject(4, Collections.singletonMap("key2", "value2"));
6768
pstmt.addBatch();//Add the current parameters to the batch
6869

70+
pstmt.setObject(1, ZonedDateTime.now());
71+
pstmt.setInt(2, 2);//Set the second parameter to 2
72+
pstmt.setString(3, "Chris");//Set the third parameter to "Chris"
73+
pstmt.setObject(4, Collections.singletonMap("key3", "value3"));
74+
pstmt.addBatch();//Add the current parameters to the batch
75+
6976
pstmt.executeBatch();//Execute the batch
7077
}
7178
}

jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,15 +332,23 @@ public long[] executeLargeBatch() throws SQLException {
332332
}
333333

334334
private List<Integer> executeBatchImpl() throws SQLException {
335+
List<Integer> results;
335336
if (insertStmtWithValues) {
336-
return executeInsertBatch();
337+
results = executeInsertBatch();
337338
} else {
338-
List<Integer> results = new ArrayList<>();
339+
results = new ArrayList<>();
339340
for (String sql : batch) {
340341
results.add((int) executeUpdateImpl(sql, localSettings));
341342
}
342-
return results;
343343
}
344+
clearBatch();
345+
return results;
346+
}
347+
348+
@Override
349+
public void clearBatch() throws SQLException {
350+
super.clearBatch(); /// clear super#batch
351+
batchValues.clear();
344352
}
345353

346354
private List<Integer> executeInsertBatch() throws SQLException {

jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ private List<Integer> executeBatchImpl() throws SQLException {
416416
for (String sql : batch) {
417417
results.add(executeUpdate(sql));
418418
}
419+
clearBatch();
419420
return results;
420421
}
421422

jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,114 @@ void testBatchInsertTextStatement(String sql) throws Exception {
953953
}
954954
}
955955

956+
@Test(groups = {"integration"})
957+
void testBatchInsertNoValuesReuse() throws Exception {
958+
String table = "test_pstmt_batch_novalues_reuse";
959+
String sql = "INSERT INTO %s (v1, v2) VALUES (?, ?)";
960+
long seed = System.currentTimeMillis();
961+
Random rnd = new Random(seed);
962+
try (Connection conn = getJdbcConnection()) {
963+
964+
try (Statement stmt = conn.createStatement()) {
965+
stmt.execute("CREATE TABLE IF NOT EXISTS " + table +
966+
" (v1 Int32, v2 Int32) Engine MergeTree ORDER BY ()");
967+
}
968+
969+
final int nBatches = 10;
970+
try (PreparedStatement stmt = conn.prepareStatement(String.format(sql, table))) {
971+
Assert.assertEquals(stmt.getClass(), PreparedStatementImpl.class);
972+
// add a batch with invalid values
973+
stmt.setString(1, "invalid");
974+
stmt.setInt(2, rnd.nextInt());
975+
stmt.addBatch();
976+
assertThrows(SQLException.class, stmt::executeBatch);
977+
// should fail due to the previous batch data.
978+
assertThrows(SQLException.class, stmt::executeBatch);
979+
// clear previous batch data
980+
stmt.clearBatch();
981+
982+
for (int step = 0; step < 2; step++) {
983+
for (int bI = 0; bI < (nBatches >> 1); bI++) {
984+
stmt.setInt(1, rnd.nextInt());
985+
stmt.setInt(2, rnd.nextInt());
986+
stmt.addBatch();
987+
}
988+
989+
// reuse the same statement
990+
int[] result = stmt.executeBatch();
991+
for (int r : result) {
992+
Assert.assertEquals(r, 1);
993+
}
994+
}
995+
}
996+
997+
try (Statement stmt = conn.createStatement();
998+
ResultSet rs = stmt.executeQuery("SELECT * FROM " + table);) {
999+
1000+
int count = 0;
1001+
while (rs.next()) {
1002+
assertTrue(rs.getInt(1) != 0);
1003+
assertTrue(rs.getInt(2) != 0);
1004+
count++;
1005+
}
1006+
assertEquals(count, nBatches);
1007+
}
1008+
}
1009+
}
1010+
1011+
@Test()
1012+
void testBatchInsertValuesReuse() throws Exception {
1013+
String table = "test_pstmt_batch_values_reuse";
1014+
String sql = "INSERT INTO %s (v1, v2) VALUES (1, ?)";
1015+
long seed = System.currentTimeMillis();
1016+
Random rnd = new Random(seed);
1017+
try (Connection conn = getJdbcConnection()) {
1018+
1019+
try (Statement stmt = conn.createStatement()) {
1020+
stmt.execute("CREATE TABLE IF NOT EXISTS " + table +
1021+
" (v1 Int32, v2 Int32) Engine MergeTree ORDER BY ()");
1022+
}
1023+
1024+
final int nBatches = 10;
1025+
try (PreparedStatement stmt = conn.prepareStatement(String.format(sql, table))) {
1026+
Assert.assertEquals(stmt.getClass(), PreparedStatementImpl.class);
1027+
// add a batch with invalid values
1028+
stmt.setString(1, "invalid");
1029+
stmt.addBatch();
1030+
assertThrows(SQLException.class, stmt::executeBatch);
1031+
// should fail due to the previous batch data.
1032+
assertThrows(SQLException.class, stmt::executeBatch);
1033+
// clear previous batch data
1034+
stmt.clearBatch();
1035+
1036+
for (int step = 0; step < 2; step++) {
1037+
for (int bI = 0; bI < (nBatches >> 1); bI++) {
1038+
stmt.setInt(1, rnd.nextInt());
1039+
stmt.addBatch();
1040+
}
1041+
1042+
// reuse the same statement
1043+
int[] result = stmt.executeBatch();
1044+
for (int r : result) {
1045+
Assert.assertEquals(r, 1);
1046+
}
1047+
}
1048+
}
1049+
1050+
try (Statement stmt = conn.createStatement();
1051+
ResultSet rs = stmt.executeQuery("SELECT * FROM " + table);) {
1052+
1053+
int count = 0;
1054+
while (rs.next()) {
1055+
assertTrue(rs.getInt(1) != 0);
1056+
assertTrue(rs.getInt(2) != 0);
1057+
count++;
1058+
}
1059+
assertEquals(count, nBatches);
1060+
}
1061+
}
1062+
}
1063+
9561064
@Test(groups = {"integration"})
9571065
void testWriteUUID() throws Exception {
9581066
String sql = "insert into `test_issue_2327` (`id`, `uuid`) values (?, ?)";

jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,44 @@ public void testExecuteUpdateBatch() throws Exception {
290290
}
291291
}
292292

293+
@Test(groups = {"integration"})
294+
public void testExecuteUpdateBatchReuse() throws Exception {
295+
String tableClause = getDatabase() + ".batch_reuse";
296+
try (Connection conn = getJdbcConnection()) {
297+
try (Statement stmt = conn.createStatement()) {
298+
assertEquals(stmt.executeUpdate("CREATE TABLE IF NOT EXISTS " + tableClause + " (id UInt8, num UInt8) ENGINE = MergeTree ORDER BY ()"), 0);
299+
// add and execute first invalid batch
300+
stmt.addBatch("INSERT INTO " + tableClause + " VALUES (0, 'invalid')");
301+
assertThrows(SQLException.class, stmt::executeBatch);
302+
303+
// add and execute second batch, which should fail due to the previous batch data.
304+
stmt.addBatch("INSERT INTO " + tableClause + " VALUES (1, 2)");
305+
assertThrows(SQLException.class, stmt::executeBatch);
306+
307+
// add and execute third batch, which should not fail
308+
stmt.clearBatch();
309+
stmt.addBatch("INSERT INTO " + tableClause + " VALUES (0, 1)");
310+
stmt.addBatch("INSERT INTO " + tableClause + " VALUES (1, 2)");
311+
assertEquals(stmt.executeBatch(), new int[]{1, 1});
312+
313+
stmt.addBatch("INSERT INTO " + tableClause + " VALUES (2, 3), (3, 4)");
314+
assertEquals(stmt.executeBatch(), new int[]{2});
315+
316+
try (ResultSet rs = stmt.executeQuery("SELECT num FROM " + tableClause + " ORDER BY id")) {
317+
assertTrue(rs.next());
318+
assertEquals(rs.getShort(1), 1);
319+
assertTrue(rs.next());
320+
assertEquals(rs.getShort(1), 2);
321+
assertTrue(rs.next());
322+
assertEquals(rs.getShort(1), 3);
323+
assertTrue(rs.next());
324+
assertEquals(rs.getShort(1), 4);
325+
assertFalse(rs.next());
326+
}
327+
}
328+
}
329+
}
330+
293331
@Test(groups = {"integration"})
294332
public void testJdbcEscapeSyntax() throws Exception {
295333
if (ClickHouseVersion.of(getServerVersion()).check("(,23.8]")) {

0 commit comments

Comments
 (0)