From e81a651e2d2fe65885aa8c6d51072f01e0236479 Mon Sep 17 00:00:00 2001 From: Ainur Date: Tue, 16 Jun 2026 16:58:33 +0200 Subject: [PATCH 1/2] fix(bigquery-jdbc): avoid rollback on statement close in manual commit mode --- .../bigquery/jdbc/BigQueryStatement.java | 3 - .../bigquery/jdbc/BigQueryStatementTest.java | 14 +++ .../bigquery/jdbc/it/ITBigQueryJDBCTest.java | 98 +++++++++++++++++++ 3 files changed, 112 insertions(+), 3 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java index 0d4d94175b8d..1cbd024ef9de 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java @@ -525,9 +525,6 @@ private void closeStatementResources() throws SQLException { this.currentUpdateCount = -1; this.currentJobIdIndex = -1; if (this.connection != null) { - if (this.connection.isTransactionStarted()) { - this.connection.rollback(); - } this.connection.removeStatement(this); } } diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryStatementTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryStatementTest.java index 674eb0df64e0..67dfbc71be48 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryStatementTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryStatementTest.java @@ -486,6 +486,20 @@ public void testCancelWithJoblessQuery() throws SQLException, InterruptedExcepti verify(bigquery, Mockito.never()).cancel(any(JobId.class)); } + @Test + public void testCancelDoesNotRollbackTransaction() throws SQLException { + doReturn(true).when(bigQueryConnection).isTransactionStarted(); + BigQueryStatement statementSpy = Mockito.spy(bigQueryStatement); + statementSpy.jobIds.add(jobId); + + statementSpy.cancel(); + + // Cancel should call bigquery.cancel() but not rollback the transaction + verify(bigquery).cancel(eq(jobId)); + verify(bigQueryConnection, Mockito.never()).rollback(); + verify(bigQueryConnection).removeStatement(statementSpy); + } + @ParameterizedTest @ValueSource(booleans = {true, false}) public void testGetStatementType(boolean isReadOnlyTokenUsed) throws Exception { diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java index 9fe1c4f0f2fc..b87de52576b0 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java @@ -2324,6 +2324,104 @@ public void testConnectionWithMultipleTransactionCommits() throws SQLException { connection.close(); } + @Test + public void testPreparedStatementCloseDoesNotRollbackTransaction() throws SQLException { + String TRANSACTION_TABLE = "JDBC_PS_CLOSE_TABLE" + randomNumber; + String createTransactionTable = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);", + DATASET, TRANSACTION_TABLE); + String insertQuery = + String.format("INSERT INTO %s.%s (id, name, age) VALUES (?, ?, ?);", DATASET, TRANSACTION_TABLE); + String selectQuery = + String.format("SELECT id, name, age FROM %s.%s ORDER BY id;", DATASET, TRANSACTION_TABLE); + + bigQueryStatement.execute(createTransactionTable); + + Connection connection = DriverManager.getConnection(session_enabled_connection_uri); + connection.setAutoCommit(false); + + PreparedStatement ps1 = connection.prepareStatement(insertQuery); + PreparedStatement ps2 = connection.prepareStatement(insertQuery); + try { + ps1.setInt(1, 1); + ps1.setString(2, "DwightShrute"); + ps1.setInt(3, 10); + assertEquals(1, ps1.executeUpdate()); + + ps2.setInt(1, 2); + ps2.setString(2, "MichaelScott"); + ps2.setInt(3, 20); + assertEquals(1, ps2.executeUpdate()); + + ps1.close(); + connection.commit(); + + ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery); + int rowCount = 0; + while (resultSet.next()) { + rowCount++; + assertEquals(rowCount, resultSet.getInt(1)); + } + assertEquals(2, rowCount); + } finally { + try { + ps2.close(); + } finally { + bigQueryStatement.execute( + String.format("DROP TABLE IF EXISTS %s.%s", DATASET, TRANSACTION_TABLE)); + connection.close(); + } + } + } + + @Test + public void testClosingUnusedPreparedStatementDoesNotRollbackPreviousExecute() + throws SQLException { + String TRANSACTION_TABLE = "JDBC_PS_UNUSED_CLOSE_TABLE" + randomNumber; + String createTransactionTable = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);", + DATASET, TRANSACTION_TABLE); + String insertQuery = + String.format("INSERT INTO %s.%s (id, name, age) VALUES (?, ?, ?);", DATASET, TRANSACTION_TABLE); + String selectQuery = + String.format("SELECT id, name, age FROM %s.%s ORDER BY id;", DATASET, TRANSACTION_TABLE); + + bigQueryStatement.execute(createTransactionTable); + + Connection connection = DriverManager.getConnection(session_enabled_connection_uri); + connection.setAutoCommit(false); + + PreparedStatement ps1 = connection.prepareStatement(insertQuery); + PreparedStatement ps2 = connection.prepareStatement(insertQuery); + try { + + ps2.setInt(1, 1); + ps2.setString(2, "MichaelScott"); + ps2.setInt(3, 20); + assertEquals(1, ps2.executeUpdate()); + + ps1.close(); + connection.commit(); + + ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery); + assertTrue(resultSet.next()); + assertEquals(1, resultSet.getInt(1)); + assertEquals("MichaelScott", resultSet.getString(2)); + assertEquals(20, resultSet.getInt(3)); + assertFalse(resultSet.next()); + } finally { + try { + ps2.close(); + } finally { + bigQueryStatement.execute( + String.format("DROP TABLE IF EXISTS %s.%s", DATASET, TRANSACTION_TABLE)); + connection.close(); + } + } + } + // Private Helper functions private String getSessionId() throws InterruptedException { QueryJobConfiguration stubJobConfig = From 275121986b15778ff17c651db3b61d9495f32365 Mon Sep 17 00:00:00 2001 From: Ainur Date: Thu, 18 Jun 2026 09:55:08 +0200 Subject: [PATCH 2/2] fix(bigquery-jdbc): rollback active transactions when closing connection --- .../bigquery/jdbc/BigQueryConnection.java | 14 +++ .../bigquery/jdbc/it/ITBigQueryJDBCTest.java | 106 ++++++++---------- 2 files changed, 62 insertions(+), 58 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java index 586a5c329405..0f18e692c643 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java @@ -970,6 +970,20 @@ private void closeImpl() throws SQLException { } this.openStatements.clear(); + if (isTransactionStarted()) { + try { + // It looks like there's no need to start a new transaction after a rollback, + // but the commit behavior is preserved since close() may still fail before isClosed is updated. + rollbackImpl(); + } catch (SQLException e) { + if (exceptionToThrow == null) { + exceptionToThrow = e; + } else { + exceptionToThrow.addSuppressed(e); + } + } + } + boolean interrupted = Thread.currentThread().isInterrupted(); try { diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java index b87de52576b0..49f68a0feeed 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java @@ -2338,39 +2338,34 @@ public void testPreparedStatementCloseDoesNotRollbackTransaction() throws SQLExc bigQueryStatement.execute(createTransactionTable); - Connection connection = DriverManager.getConnection(session_enabled_connection_uri); - connection.setAutoCommit(false); - - PreparedStatement ps1 = connection.prepareStatement(insertQuery); - PreparedStatement ps2 = connection.prepareStatement(insertQuery); - try { - ps1.setInt(1, 1); - ps1.setString(2, "DwightShrute"); - ps1.setInt(3, 10); - assertEquals(1, ps1.executeUpdate()); - - ps2.setInt(1, 2); - ps2.setString(2, "MichaelScott"); - ps2.setInt(3, 20); - assertEquals(1, ps2.executeUpdate()); - - ps1.close(); - connection.commit(); - - ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery); - int rowCount = 0; - while (resultSet.next()) { - rowCount++; - assertEquals(rowCount, resultSet.getInt(1)); - } - assertEquals(2, rowCount); - } finally { - try { - ps2.close(); + try (Connection connection = DriverManager.getConnection(session_enabled_connection_uri)) { + connection.setAutoCommit(false); + try (PreparedStatement ps1 = connection.prepareStatement(insertQuery); + PreparedStatement ps2 = connection.prepareStatement(insertQuery)) { + ps1.setInt(1, 1); + ps1.setString(2, "DwightShrute"); + ps1.setInt(3, 10); + assertEquals(1, ps1.executeUpdate()); + + ps2.setInt(1, 2); + ps2.setString(2, "MichaelScott"); + ps2.setInt(3, 20); + assertEquals(1, ps2.executeUpdate()); + + ps1.close(); + connection.commit(); + + try (ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery)) { + int rowCount = 0; + while (resultSet.next()) { + rowCount++; + assertEquals(rowCount, resultSet.getInt(1)); + } + assertEquals(2, rowCount); + } } finally { bigQueryStatement.execute( String.format("DROP TABLE IF EXISTS %s.%s", DATASET, TRANSACTION_TABLE)); - connection.close(); } } } @@ -2390,35 +2385,30 @@ public void testClosingUnusedPreparedStatementDoesNotRollbackPreviousExecute() bigQueryStatement.execute(createTransactionTable); - Connection connection = DriverManager.getConnection(session_enabled_connection_uri); - connection.setAutoCommit(false); - - PreparedStatement ps1 = connection.prepareStatement(insertQuery); - PreparedStatement ps2 = connection.prepareStatement(insertQuery); - try { - - ps2.setInt(1, 1); - ps2.setString(2, "MichaelScott"); - ps2.setInt(3, 20); - assertEquals(1, ps2.executeUpdate()); - - ps1.close(); - connection.commit(); - - ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery); - assertTrue(resultSet.next()); - assertEquals(1, resultSet.getInt(1)); - assertEquals("MichaelScott", resultSet.getString(2)); - assertEquals(20, resultSet.getInt(3)); - assertFalse(resultSet.next()); - } finally { - try { - ps2.close(); - } finally { - bigQueryStatement.execute( - String.format("DROP TABLE IF EXISTS %s.%s", DATASET, TRANSACTION_TABLE)); - connection.close(); + try (Connection connection = DriverManager.getConnection(session_enabled_connection_uri)) { + connection.setAutoCommit(false); + try (PreparedStatement ps1 = connection.prepareStatement(insertQuery); + PreparedStatement ps2 = connection.prepareStatement(insertQuery)) { + + ps2.setInt(1, 1); + ps2.setString(2, "MichaelScott"); + ps2.setInt(3, 20); + assertEquals(1, ps2.executeUpdate()); + + ps1.close(); + connection.commit(); + + try (ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery)) { + assertTrue(resultSet.next()); + assertEquals(1, resultSet.getInt(1)); + assertEquals("MichaelScott", resultSet.getString(2)); + assertEquals(20, resultSet.getInt(3)); + assertFalse(resultSet.next()); + } } + } finally { + bigQueryStatement.execute( + String.format("DROP TABLE IF EXISTS %s.%s", DATASET, TRANSACTION_TABLE)); } }