Skip to content

Commit fd7ba68

Browse files
committed
Fix executeBatch in auto-commit mode
This change fixes the problem when connection is left in inconsistent state when `executeBatch` throws constraint (or some other) error with auto-commit enabled. In duckdb#213 `executeBatch` was changed to effectively disable auto-commit for the whole batch. In this case the rollback on error needs to be handled inside `executeBatch` too. Testing: new tests added that cover constrain violation with `executeBatch`. Fixes: duckdb#259
1 parent 0c827aa commit fd7ba68

File tree

2 files changed

+117
-2
lines changed

2 files changed

+117
-2
lines changed

src/main/java/org/duckdb/DuckDBPreparedStatement.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -645,11 +645,13 @@ public long[] executeLargeBatch() throws SQLException {
645645

646646
private long[] executeBatchedPreparedStatement() throws SQLException {
647647
stmtRefLock.lock();
648+
boolean tranStarted = false;
649+
DuckDBConnection conn = this.conn;
648650
try {
649651
checkOpen();
650652
checkPrepared();
651653

652-
boolean tranStarted = startTransaction();
654+
tranStarted = startTransaction();
653655

654656
long[] updateCounts = new long[this.batchedParams.size()];
655657
for (int i = 0; i < this.batchedParams.size(); i++) {
@@ -664,17 +666,25 @@ private long[] executeBatchedPreparedStatement() throws SQLException {
664666
}
665667

666668
return updateCounts;
669+
670+
} catch (SQLException e) {
671+
if (tranStarted && conn.getAutoCommit()) {
672+
conn.rollback();
673+
}
674+
throw e;
667675
} finally {
668676
stmtRefLock.unlock();
669677
}
670678
}
671679

672680
private long[] executeBatchedStatements() throws SQLException {
673681
stmtRefLock.lock();
682+
boolean tranStarted = false;
683+
DuckDBConnection conn = this.conn;
674684
try {
675685
checkOpen();
676686

677-
boolean tranStarted = startTransaction();
687+
tranStarted = startTransaction();
678688

679689
long[] updateCounts = new long[this.batchedStatements.size()];
680690
for (int i = 0; i < this.batchedStatements.size(); i++) {
@@ -689,6 +699,12 @@ private long[] executeBatchedStatements() throws SQLException {
689699
}
690700

691701
return updateCounts;
702+
703+
} catch (SQLException e) {
704+
if (tranStarted && conn.getAutoCommit()) {
705+
conn.rollback();
706+
}
707+
throw e;
692708
} finally {
693709
stmtRefLock.unlock();
694710
}

src/test/java/org/duckdb/TestBatch.java

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.duckdb.test.Assertions.*;
55

66
import java.sql.*;
7+
import java.util.Properties;
78

89
public class TestBatch {
910

@@ -206,4 +207,102 @@ public static void test_statement_batch_rollback() throws Exception {
206207
}
207208
}
208209
}
210+
211+
public static void test_statement_batch_autocommit_constraint_violation() throws Exception {
212+
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
213+
assertTrue(conn.getAutoCommit());
214+
try (Statement stmt = conn.createStatement()) {
215+
stmt.execute("CREATE TABLE tab1 (col1 VARCHAR NOT NULL)");
216+
}
217+
try (Statement stmt = conn.createStatement()) {
218+
stmt.addBatch("INSERT INTO tab1 VALUES('foo')");
219+
stmt.addBatch("INSERT INTO tab1 VALUES(NULL)");
220+
assertThrows(stmt::executeBatch, SQLException.class);
221+
}
222+
try (Statement stmt = conn.createStatement();
223+
ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1")) {
224+
rs.next();
225+
assertEquals(rs.getLong(1), 0L);
226+
}
227+
}
228+
}
229+
230+
public static void test_prepared_statement_batch_autocommit_constraint_violation() throws Exception {
231+
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
232+
assertTrue(conn.getAutoCommit());
233+
try (Statement stmt = conn.createStatement()) {
234+
stmt.execute("CREATE TABLE tab1 (col1 VARCHAR NOT NULL)");
235+
}
236+
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO tab1 VALUES(?)")) {
237+
ps.setString(1, "foo");
238+
ps.addBatch();
239+
ps.setString(1, null);
240+
ps.addBatch();
241+
assertThrows(ps::executeBatch, SQLException.class);
242+
}
243+
try (Statement stmt = conn.createStatement();
244+
ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1")) {
245+
rs.next();
246+
assertEquals(rs.getLong(1), 0L);
247+
}
248+
}
249+
}
250+
251+
public static void test_statement_batch_constraint_violation() throws Exception {
252+
Properties config = new Properties();
253+
config.put(DuckDBDriver.JDBC_AUTO_COMMIT, false);
254+
try (Connection conn = DriverManager.getConnection(JDBC_URL, config)) {
255+
assertFalse(conn.getAutoCommit());
256+
try (Statement stmt = conn.createStatement()) {
257+
stmt.execute("CREATE TABLE tab1 (col1 VARCHAR NOT NULL)");
258+
conn.commit();
259+
}
260+
boolean thrown = false;
261+
try (Statement stmt = conn.createStatement()) {
262+
stmt.addBatch("INSERT INTO tab1 VALUES('foo')");
263+
stmt.addBatch("INSERT INTO tab1 VALUES(NULL)");
264+
stmt.executeBatch();
265+
conn.commit();
266+
} catch (SQLException e) {
267+
thrown = true;
268+
conn.rollback();
269+
}
270+
assertTrue(thrown);
271+
try (Statement stmt = conn.createStatement();
272+
ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1")) {
273+
rs.next();
274+
assertEquals(rs.getLong(1), 0L);
275+
}
276+
}
277+
}
278+
279+
public static void test_prepared_statement_batch_constraint_violation() throws Exception {
280+
Properties config = new Properties();
281+
config.put(DuckDBDriver.JDBC_AUTO_COMMIT, false);
282+
try (Connection conn = DriverManager.getConnection(JDBC_URL, config)) {
283+
assertFalse(conn.getAutoCommit());
284+
try (Statement stmt = conn.createStatement()) {
285+
stmt.execute("CREATE TABLE tab1 (col1 VARCHAR NOT NULL)");
286+
conn.commit();
287+
}
288+
boolean thrown = false;
289+
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO tab1 VALUES(?)")) {
290+
ps.setString(1, "foo");
291+
ps.addBatch();
292+
ps.setString(1, null);
293+
ps.addBatch();
294+
ps.executeBatch();
295+
conn.commit();
296+
} catch (SQLException e) {
297+
thrown = true;
298+
conn.rollback();
299+
}
300+
assertTrue(thrown);
301+
try (Statement stmt = conn.createStatement();
302+
ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1")) {
303+
rs.next();
304+
assertEquals(rs.getLong(1), 0L);
305+
}
306+
}
307+
}
209308
}

0 commit comments

Comments
 (0)