Skip to content

Commit 9d99024

Browse files
committed
Run executeBatch in a single transaction
This change makes `executeBatch()` method to be run in a single transaction when auto-commit is enabled. When auto-commit is disabled, the logic remains the same as before. Testing: new tests added to check auto-commit and rollback behaviour of `executeBatch()`.
1 parent 21033ef commit 9d99024

File tree

3 files changed

+145
-30
lines changed

3 files changed

+145
-30
lines changed

src/main/java/org/duckdb/DuckDBConnection.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public final class DuckDBConnection implements java.sql.Connection {
4040
final LinkedHashSet<DuckDBPreparedStatement> preparedStatements = new LinkedHashSet<>();
4141
volatile boolean closing = false;
4242

43-
boolean autoCommit = true;
44-
boolean transactionRunning;
43+
volatile boolean autoCommit = true;
44+
volatile boolean transactionRunning;
4545
final String url;
4646
private final boolean readOnly;
4747

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

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,26 @@ public DuckDBPreparedStatement(DuckDBConnection conn, String sql) throws SQLExce
7979
prepare(sql);
8080
}
8181

82-
private void startTransaction() throws SQLException {
82+
private boolean isConnAutoCommit() throws SQLException {
8383
checkOpen();
8484
try {
85-
if (this.conn.autoCommit || this.conn.transactionRunning) {
86-
return;
85+
return this.conn.autoCommit;
86+
} catch (NullPointerException e) {
87+
throw new SQLException(e);
88+
}
89+
}
90+
91+
private boolean startTransaction() throws SQLException {
92+
checkOpen();
93+
try {
94+
if (this.conn.transactionRunning) {
95+
return false;
8796
}
8897
this.conn.transactionRunning = true;
8998
// Start transaction via Statement
9099
try (Statement s = conn.createStatement()) {
91100
s.execute("BEGIN TRANSACTION;");
101+
return true;
92102
}
93103
} catch (NullPointerException e) {
94104
throw new SQLException(e);
@@ -161,7 +171,7 @@ private boolean execute(boolean startTransaction) throws SQLException {
161171
}
162172
selectResult = null;
163173

164-
if (startTransaction) {
174+
if (startTransaction && !isConnAutoCommit()) {
165175
startTransaction();
166176
}
167177

@@ -576,41 +586,62 @@ public int[] executeBatch() throws SQLException {
576586
@Override
577587
public long[] executeLargeBatch() throws SQLException {
578588
checkOpen();
579-
try {
580-
if (this.isPreparedStatement) {
581-
return executeBatchedPreparedStatement();
582-
} else {
583-
return executeBatchedStatements();
584-
}
585-
} finally {
586-
if (!isClosed()) {
587-
clearBatch();
588-
}
589+
if (this.isPreparedStatement) {
590+
return executeBatchedPreparedStatement();
591+
} else {
592+
return executeBatchedStatements();
589593
}
590594
}
591595

592596
private long[] executeBatchedPreparedStatement() throws SQLException {
593-
long[] updateCounts = new long[this.batchedParams.size()];
597+
stmtRefLock.lock();
598+
try {
599+
checkOpen();
600+
checkPrepared();
601+
602+
boolean tranStarted = startTransaction();
594603

595-
startTransaction();
596-
for (int i = 0; i < this.batchedParams.size(); i++) {
597-
params = this.batchedParams.get(i);
598-
execute(false);
599-
updateCounts[i] = getUpdateCountInternal();
604+
long[] updateCounts = new long[this.batchedParams.size()];
605+
for (int i = 0; i < this.batchedParams.size(); i++) {
606+
params = this.batchedParams.get(i);
607+
execute(false);
608+
updateCounts[i] = getUpdateCountInternal();
609+
}
610+
clearBatch();
611+
612+
if (tranStarted && isConnAutoCommit()) {
613+
this.conn.commit();
614+
}
615+
616+
return updateCounts;
617+
} finally {
618+
stmtRefLock.unlock();
600619
}
601-
return updateCounts;
602620
}
603621

604622
private long[] executeBatchedStatements() throws SQLException {
605-
long[] updateCounts = new long[this.batchedStatements.size()];
623+
stmtRefLock.lock();
624+
try {
625+
checkOpen();
626+
627+
boolean tranStarted = startTransaction();
628+
629+
long[] updateCounts = new long[this.batchedStatements.size()];
630+
for (int i = 0; i < this.batchedStatements.size(); i++) {
631+
prepare(this.batchedStatements.get(i));
632+
execute(false);
633+
updateCounts[i] = getUpdateCountInternal();
634+
}
635+
clearBatch();
606636

607-
startTransaction();
608-
for (int i = 0; i < this.batchedStatements.size(); i++) {
609-
prepare(this.batchedStatements.get(i));
610-
execute(false);
611-
updateCounts[i] = getUpdateCountInternal();
637+
if (tranStarted && isConnAutoCommit()) {
638+
this.conn.commit();
639+
}
640+
641+
return updateCounts;
642+
} finally {
643+
stmtRefLock.unlock();
612644
}
613-
return updateCounts;
614645
}
615646

616647
@Override

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

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,88 @@ public static void test_prepared_statement_batch_exception() throws Exception {
124124
}
125125
}
126126
}
127+
128+
public static void test_prepared_statement_batch_autocommit() throws Exception {
129+
long count = 1 << 10;
130+
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
131+
assertTrue(conn.getAutoCommit());
132+
try (Statement stmt = conn.createStatement()) {
133+
stmt.execute("CREATE TABLE tab1 (col1 BIGINT, col2 VARCHAR)");
134+
}
135+
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO tab1 VALUES(?, ?)")) {
136+
for (long i = 0; i < count; i++) {
137+
ps.setLong(1, i);
138+
ps.setString(2, i + "foo");
139+
ps.addBatch();
140+
}
141+
ps.executeBatch();
142+
}
143+
try (Statement stmt = conn.createStatement();
144+
ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1")) {
145+
rs.next();
146+
assertEquals(rs.getLong(1), count);
147+
}
148+
}
149+
}
150+
151+
public static void test_statement_batch_autocommit() throws Exception {
152+
long count = 1 << 10;
153+
try (Connection conn = DriverManager.getConnection(JDBC_URL);
154+
Statement stmt = conn.createStatement()) {
155+
assertTrue(conn.getAutoCommit());
156+
stmt.execute("CREATE TABLE tab1 (col1 BIGINT, col2 VARCHAR)");
157+
for (long i = 0; i < count; i++) {
158+
stmt.addBatch("INSERT INTO tab1 VALUES(" + i + ", '" + i + "foo')");
159+
}
160+
stmt.executeBatch();
161+
try (ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1")) {
162+
rs.next();
163+
assertEquals(rs.getLong(1), count);
164+
}
165+
}
166+
}
167+
168+
public static void test_prepared_statement_batch_rollback() throws Exception {
169+
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
170+
try (Statement stmt = conn.createStatement()) {
171+
stmt.execute("CREATE TABLE tab1 (col1 BIGINT, col2 VARCHAR)");
172+
}
173+
conn.setAutoCommit(false);
174+
try (Statement stmt = conn.createStatement()) {
175+
stmt.execute("INSERT INTO tab1 VALUES(-1, 'bar')");
176+
}
177+
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO tab1 VALUES(?, ?)")) {
178+
for (long i = 0; i < 1 << 10; i++) {
179+
ps.setLong(1, i);
180+
ps.setString(2, i + "foo");
181+
ps.addBatch();
182+
}
183+
ps.executeBatch();
184+
}
185+
conn.rollback();
186+
try (Statement stmt = conn.createStatement();
187+
ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1")) {
188+
rs.next();
189+
assertEquals(rs.getLong(1), 0L);
190+
}
191+
}
192+
}
193+
194+
public static void test_statement_batch_rollback() throws Exception {
195+
try (Connection conn = DriverManager.getConnection(JDBC_URL);
196+
Statement stmt = conn.createStatement()) {
197+
stmt.execute("CREATE TABLE tab1 (col1 BIGINT, col2 VARCHAR)");
198+
conn.setAutoCommit(false);
199+
stmt.execute("INSERT INTO tab1 VALUES(-1, 'bar')");
200+
for (long i = 0; i < 1 << 10; i++) {
201+
stmt.addBatch("INSERT INTO tab1 VALUES(" + i + ", '" + i + "foo')");
202+
}
203+
stmt.executeBatch();
204+
conn.rollback();
205+
try (ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1")) {
206+
rs.next();
207+
assertEquals(rs.getLong(1), 0L);
208+
}
209+
}
210+
}
127211
}

0 commit comments

Comments
 (0)