[Git][java-team/libpostgresql-jdbc-java][master] 3 commits: New upstream version 42.7.7
Christoph Berg (@myon)
gitlab at salsa.debian.org
Fri Jun 13 14:31:42 BST 2025
Christoph Berg pushed to branch master at Debian Java Maintainers / libpostgresql-jdbc-java
Commits:
4ac37a14 by Christoph Berg at 2025-06-13T15:26:50+02:00
New upstream version 42.7.7
- - - - -
7d5fcf3f by Christoph Berg at 2025-06-13T15:26:53+02:00
Update upstream source from tag 'upstream/42.7.7'
Update to upstream version '42.7.7'
with Debian dir 0babbfc13b6830d7b9814c99b46e925e729dcee3
- - - - -
e4dff383 by Christoph Berg at 2025-06-13T15:31:05+02:00
New upstream version 42.7.7. Fixes CVE-2025-49146: When the PostgreSQL JDBC driver is configured with channel binding set to required (default value is prefer), the driver would incorrectly allow connections to proceed with authentication methods that do not support channel binding (such as password, MD5, GSS, or SSPI authentication). This could allow a man-in-the-middle attacker to intercept connections that users believed were protected by channel binding requirements.
- - - - -
8 changed files:
- debian/changelog
- pom.xml
- src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
- src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
- src/main/java/org/postgresql/util/DriverInfo.java
- src/main/resources/META-INF/MANIFEST.MF
- + src/test/java/org/postgresql/test/core/ChannelBindingRequiredTest.java
- + src/test/java/org/postgresql/test/jdbc2/AutoRollbackTest.java
Changes:
=====================================
debian/changelog
=====================================
@@ -1,3 +1,16 @@
+libpgjava (42.7.7-1) unstable; urgency=medium
+
+ * New upstream version 42.7.7.
+ Fixes CVE-2025-49146: When the PostgreSQL JDBC driver is configured with
+ channel binding set to required (default value is prefer), the driver
+ would incorrectly allow connections to proceed with authentication methods
+ that do not support channel binding (such as password, MD5, GSS, or SSPI
+ authentication). This could allow a man-in-the-middle attacker to
+ intercept connections that users believed were protected by channel
+ binding requirements.
+
+ -- Christoph Berg <myon at debian.org> Fri, 13 Jun 2025 15:26:53 +0200
+
libpgjava (42.7.6-1) experimental; urgency=medium
* New upstream version 42.7.6.
=====================================
pom.xml
=====================================
@@ -8,7 +8,7 @@
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
- <version>42.7.6</version>
+ <version>42.7.7</version>
<packaging>jar</packaging>
<name>PostgreSQL JDBC Driver - JDBC 4.2</name>
<description>Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database</description>
=====================================
src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
=====================================
@@ -702,6 +702,29 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
pgStream.flush();
}
+ private static String getAuthenticationMethodName(int authReq) {
+ switch (authReq) {
+ case AUTH_REQ_OK:
+ return "none";
+ case AUTH_REQ_PASSWORD:
+ return "password";
+ case AUTH_REQ_MD5:
+ return "md5";
+ case AUTH_REQ_GSS:
+ return "gss";
+ case AUTH_REQ_SSPI:
+ return "sspi";
+ case AUTH_REQ_SASL:
+ return "sasl";
+ case AUTH_REQ_SASL_CONTINUE:
+ return "sasl-continue";
+ case AUTH_REQ_SASL_FINAL:
+ return "sasl-final";
+ default:
+ return String.valueOf(authReq);
+ }
+ }
+
private static void doAuthentication(PGStream pgStream, String host, String user, Properties info) throws IOException, SQLException {
// Now get the response from the backend, either an error message
// or an authentication request
@@ -714,6 +737,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
// TODO: figure out how to deal with new protocols
int protocol = 3 << 16;
+ boolean saslHandshakeCompleted = false;
+
try {
authloop: while (true) {
int beresp = pgStream.receiveChar();
@@ -759,6 +784,23 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
// Get the type of request
int areq = pgStream.receiveInteger4();
+ if (ChannelBindingOption.of(info) == ChannelBindingOption.REQUIRE) {
+ if (areq == AUTH_REQ_OK) {
+ if (!saslHandshakeCompleted) {
+ throw new PSQLException(
+ GT.tr("Channel binding is required, but server skipped authentication. "
+ + "Channel binding is only supported with SCRAM authentication over encrypted connections."),
+ PSQLState.CONNECTION_REJECTED);
+ }
+ } else if (areq != AUTH_REQ_SASL && areq != AUTH_REQ_SASL_CONTINUE && areq != AUTH_REQ_SASL_FINAL) {
+ throw new PSQLException(
+ GT.tr("Channel binding is required, but server requested ''{0}'' authentication. "
+ + "Channel binding is only supported with SCRAM authentication over encrypted connections.",
+ getAuthenticationMethodName(areq)),
+ PSQLState.CONNECTION_REJECTED);
+ }
+ }
+
// Process the request.
switch (areq) {
case AUTH_REQ_MD5: {
@@ -914,6 +956,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
case AUTH_REQ_SASL_FINAL:
castNonNull(scramAuthenticator).handleAuthenticationSASLFinal(msgLen - 4 - 4);
+ saslHandshakeCompleted = true;
break;
case AUTH_REQ_OK:
=====================================
src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
=====================================
@@ -318,8 +318,6 @@ public class QueryExecutorImpl extends QueryExecutorBase {
switch (getPreferQueryMode()) {
case SIMPLE:
return flags | QUERY_EXECUTE_AS_SIMPLE;
- case EXTENDED:
- return flags & ~QUERY_EXECUTE_AS_SIMPLE;
default:
return flags;
}
=====================================
src/main/java/org/postgresql/util/DriverInfo.java
=====================================
@@ -16,13 +16,13 @@ public final class DriverInfo {
// Driver name
public static final String DRIVER_NAME = "PostgreSQL JDBC Driver";
public static final String DRIVER_SHORT_NAME = "PgJDBC";
- public static final String DRIVER_VERSION = "42.7.6";
+ public static final String DRIVER_VERSION = "42.7.7";
public static final String DRIVER_FULL_NAME = DRIVER_NAME + " " + DRIVER_VERSION;
// Driver version
public static final int MAJOR_VERSION = 42;
public static final int MINOR_VERSION = 7;
- public static final int PATCH_VERSION = 6;
+ public static final int PATCH_VERSION = 7;
// JDBC specification
public static final String JDBC_VERSION = "4.2";
=====================================
src/main/resources/META-INF/MANIFEST.MF
=====================================
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
Bundle-License: BSD-2-Clause
Implementation-Title: PostgreSQL JDBC Driver
-Implementation-Version: 42.7.6
+Implementation-Version: 42.7.7
Specification-Vendor: Oracle Corporation
Specification-Version: 4.2
Specification-Title: JDBC
=====================================
src/test/java/org/postgresql/test/core/ChannelBindingRequiredTest.java
=====================================
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import org.postgresql.PGProperty;
+import org.postgresql.core.ServerVersion;
+import org.postgresql.test.TestUtil;
+import org.postgresql.util.PSQLException;
+import org.postgresql.util.PSQLState;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Locale;
+import java.util.Properties;
+
+class ChannelBindingRequiredTest {
+
+ private static final String MD5_ROLE_NAME = "test_channel_binding_md5";
+ private static final String SASL_ROLE_NAME = "test_channel_binding_sasl";
+ private static final String TEST_PASSWORD = "test_password_123";
+
+ @BeforeAll
+ static void setUp() throws Exception {
+ TestUtil.assumeHaveMinimumServerVersion(ServerVersion.v11);
+
+ try (Connection conn = TestUtil.openPrivilegedDB()) {
+ String sslEnabled = TestUtil.queryForString(conn, "SHOW ssl");
+ assumeTrue("on".equals(sslEnabled), "SSL must be enabled for channel binding tests");
+
+ TestUtil.execute(conn, "SET password_encryption = 'md5'");
+ TestUtil.execute(conn, "DROP ROLE IF EXISTS " + MD5_ROLE_NAME);
+ TestUtil.execute(conn, "CREATE ROLE " + MD5_ROLE_NAME + " WITH LOGIN PASSWORD '" + TEST_PASSWORD + "'");
+
+ TestUtil.execute(conn,"SET password_encryption='scram-sha-256'");
+ TestUtil.execute(conn,"DROP ROLE IF EXISTS " + SASL_ROLE_NAME);
+ TestUtil.execute(conn,"CREATE ROLE " + SASL_ROLE_NAME + " WITH LOGIN PASSWORD '" + TEST_PASSWORD + "'");
+ }
+ }
+
+ @AfterAll
+ static void tearDown() throws Exception {
+ try (Connection conn = TestUtil.openPrivilegedDB()) {
+ TestUtil.execute(conn, "DROP ROLE IF EXISTS " + MD5_ROLE_NAME);
+ TestUtil.execute(conn, "DROP ROLE IF EXISTS " + SASL_ROLE_NAME);
+ }
+ }
+
+ /**
+ * Test that md5 authentication fails when channel binding is required.
+ * Channel binding is only supported with SSL + SCRAM authentication.
+ */
+ @Test
+ void testMD5AuthWithChannelBindingRequiredFails() {
+ Properties props = new Properties();
+ PGProperty.USER.set(props, MD5_ROLE_NAME);
+ PGProperty.PASSWORD.set(props, TEST_PASSWORD);
+ PGProperty.CHANNEL_BINDING.set(props, "require");
+ PGProperty.SSL_MODE.set(props, "require");
+
+ PSQLException ex = assertThrows(PSQLException.class, () -> TestUtil.openDB(props),
+ "Connection with MD5 auth and channel binding required should fail");
+
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), ex.getSQLState());
+ String errorMessage = ex.getMessage().toLowerCase(Locale.ROOT);
+ assertTrue(errorMessage.contains("channel binding") && errorMessage.contains("md5"),
+ "Error message should mention both channel binding requirement and MD5 authentication: " + ex.getMessage());
+ }
+
+
+ /**
+ * Test that SCRAM authentication fails when channel binding is required with no SSL.
+ * Channel binding is only supported with SSL + SCRAM authentication.
+ */
+ @Test
+ void testScramAuthWithNoSSLChannelBindingRequiredFails() {
+ Properties props = new Properties();
+ PGProperty.USER.set(props, SASL_ROLE_NAME);
+ PGProperty.PASSWORD.set(props, TEST_PASSWORD);
+ PGProperty.CHANNEL_BINDING.set(props, "require");
+ PGProperty.SSL_MODE.set(props, "disable");
+
+ PSQLException ex = assertThrows(PSQLException.class, () -> TestUtil.openDB(props),
+ "Connection with SCRAM auth and channel binding required should fail without SSL");
+
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), ex.getSQLState());
+ String errorMessage = ex.getMessage().toLowerCase(Locale.ROOT);
+ assertTrue(errorMessage.contains("channel binding") && errorMessage.contains("ssl"),
+ "Error message should mention both channel binding requirement and ssl: " + ex.getMessage());
+ }
+
+ /**
+ * Test that SASL authentication succeeds when channel binding is required.
+ * This should work as channel binding is supported with SCRAM authentication.
+ */
+ @Test
+ void testSASLAuthWithChannelBindingRequiredSucceeds() throws SQLException {
+ Properties props = new Properties();
+ PGProperty.USER.set(props, SASL_ROLE_NAME);
+ PGProperty.PASSWORD.set(props, TEST_PASSWORD);
+ PGProperty.CHANNEL_BINDING.set(props, "require");
+ PGProperty.SSL_MODE.set(props, "require");
+
+ try (Connection conn = TestUtil.openDB(props);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT current_user")) {
+ assertTrue(rs.next(), "Has result row");
+ assertEquals(SASL_ROLE_NAME, rs.getString(1));
+ }
+ }
+}
=====================================
src/test/java/org/postgresql/test/jdbc2/AutoRollbackTest.java
=====================================
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2004, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.jdbc2;
+
+import org.postgresql.PGConnection;
+import org.postgresql.PGProperty;
+import org.postgresql.core.BaseConnection;
+import org.postgresql.core.ResultHandler;
+import org.postgresql.core.ServerVersion;
+import org.postgresql.core.TransactionState;
+import org.postgresql.jdbc.AutoSave;
+import org.postgresql.jdbc.PgConnection;
+import org.postgresql.jdbc.PreferQueryMode;
+import org.postgresql.test.TestUtil;
+import org.postgresql.util.PSQLState;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+
+ at RunWith(Parameterized.class)
+public class AutoRollbackTest extends BaseTest4 {
+ private static final AtomicInteger counter = new AtomicInteger();
+
+ private enum CleanSavePoint {
+ TRUE,
+ FALSE
+ }
+
+ private enum FailMode {
+ /**
+ * Executes "select 1/0" and causes transaction failure (if autocommit=no).
+ * Mitigation: "autosave=always" or "autocommit=true"
+ */
+ SELECT,
+ /**
+ * Executes "alter table rollbacktest", thus it breaks a prepared select over that table.
+ * Mitigation: "autosave in (always, conservative)"
+ */
+ ALTER,
+ /**
+ * Executes DEALLOCATE ALL.
+ * Mitigation:
+ * 1) QueryExecutor tracks "DEALLOCATE ALL" responses ({@see org.postgresql.core.QueryExecutor#setFlushCacheOnDeallocate(boolean)}
+ * 2) QueryExecutor tracks "prepared statement name is invalid" and unprepared relevant statements ({@link org.postgresql.core.v3.QueryExecutorImpl#processResults(ResultHandler, int)}
+ * 3) "autosave in (always, conservative)"
+ * 4) Non-transactional cases are healed by retry (when no transaction present, just retry is possible)
+ */
+ DEALLOCATE,
+ /**
+ * Executes DISCARD ALL.
+ * Mitigation: the same as for {@link #DEALLOCATE}
+ */
+ DISCARD,
+ /**
+ * Executes "insert ... select 1/0" in a batch statement, thus causing the transaction to fail.
+ */
+ INSERT_BATCH,
+ }
+
+ private enum ReturnColumns {
+ EXACT("a, str"),
+ STAR("*");
+
+ public final String cols;
+
+ ReturnColumns(String cols) {
+ this.cols = cols;
+ }
+ }
+
+ private enum TestStatement {
+ SELECT("select ${cols} from rollbacktest", 0),
+ WITH_INSERT_SELECT(
+ "with x as (insert into rollbacktest(a, str) values(43, 'abc') returning ${cols})"
+ + "select * from x", 1);
+
+ private final String sql;
+ private final int rowsInserted;
+
+ TestStatement(String sql, int rowsInserted) {
+ this.sql = sql;
+ this.rowsInserted = rowsInserted;
+ }
+
+ public String getSql(ReturnColumns cols) {
+ return sql.replace("${cols}", cols.cols);
+ }
+ }
+
+ private static final EnumSet<FailMode> DEALLOCATES =
+ EnumSet.of(FailMode.DEALLOCATE, FailMode.DISCARD);
+
+ private static final EnumSet<FailMode> TRANS_KILLERS =
+ EnumSet.of(FailMode.SELECT, FailMode.INSERT_BATCH);
+
+ private enum ContinueMode {
+ COMMIT,
+ IS_VALID,
+ SELECT,
+ }
+
+ private final AutoSave autoSave;
+ private final CleanSavePoint cleanSavePoint;
+ private final AutoCommit autoCommit;
+ private final FailMode failMode;
+ private final ContinueMode continueMode;
+ private final boolean flushCacheOnDeallocate;
+ private final boolean trans;
+ private final TestStatement testSql;
+ private final ReturnColumns cols;
+
+ public AutoRollbackTest(AutoSave autoSave, CleanSavePoint cleanSavePoint, AutoCommit autoCommit,
+ FailMode failMode, ContinueMode continueMode, boolean flushCacheOnDeallocate,
+ boolean trans, TestStatement testSql, ReturnColumns cols) {
+ this.autoSave = autoSave;
+ this.cleanSavePoint = cleanSavePoint;
+ this.autoCommit = autoCommit;
+ this.failMode = failMode;
+ this.continueMode = continueMode;
+ this.flushCacheOnDeallocate = flushCacheOnDeallocate;
+ this.trans = trans;
+ this.testSql = testSql;
+ this.cols = cols;
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ if (testSql == TestStatement.WITH_INSERT_SELECT) {
+ assumeMinimumServerVersion(ServerVersion.v9_1);
+ }
+
+ TestUtil.createTable(con, "rollbacktest", "a int, str text");
+ con.setAutoCommit(autoCommit == AutoCommit.YES);
+ BaseConnection baseConnection = con.unwrap(BaseConnection.class);
+ baseConnection.setFlushCacheOnDeallocate(flushCacheOnDeallocate);
+ Assume.assumeTrue("DEALLOCATE ALL requires PostgreSQL 8.3+",
+ failMode != FailMode.DEALLOCATE || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3));
+ Assume.assumeTrue("DISCARD ALL requires PostgreSQL 8.3+",
+ failMode != FailMode.DISCARD || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3));
+ Assume.assumeTrue("Plan invalidation on table redefinition requires PostgreSQL 8.3+",
+ failMode != FailMode.ALTER || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3));
+ }
+
+ @Override
+ public void tearDown() throws SQLException {
+ try {
+ con.setAutoCommit(true);
+ TestUtil.dropTable(con, "rollbacktest");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ super.tearDown();
+ }
+
+ @Override
+ protected void updateProperties(Properties props) {
+ super.updateProperties(props);
+ PGProperty.AUTOSAVE.set(props, autoSave.value());
+ PGProperty.CLEANUP_SAVEPOINTS.set(props, cleanSavePoint.toString());
+ PGProperty.PREPARE_THRESHOLD.set(props, 1);
+ }
+
+ @Parameterized.Parameters(name = "{index}: autorollback(autoSave={0}, cleanSavePoint={1}, autoCommit={2}, failMode={3}, continueMode={4}, flushOnDeallocate={5}, hastransaction={6}, sql={7}, columns={8})")
+ public static Iterable<Object[]> data() {
+ Collection<Object[]> ids = new ArrayList<>();
+ boolean[] booleans = new boolean[]{true, false};
+ for (AutoSave autoSave : AutoSave.values()) {
+ for (CleanSavePoint cleanSavePoint:CleanSavePoint.values()) {
+ for (AutoCommit autoCommit : AutoCommit.values()) {
+ for (FailMode failMode : FailMode.values()) {
+ // ERROR: DISCARD ALL cannot run inside a transaction block
+ if (failMode == FailMode.DISCARD && autoCommit == AutoCommit.NO) {
+ continue;
+ }
+ for (ContinueMode continueMode : ContinueMode.values()) {
+ if (failMode == FailMode.ALTER && continueMode != ContinueMode.SELECT) {
+ continue;
+ }
+ for (boolean flushCacheOnDeallocate : booleans) {
+ if (!(flushCacheOnDeallocate || DEALLOCATES.contains(failMode))) {
+ continue;
+ }
+
+ for (boolean trans : new boolean[]{true, false}) {
+ // continueMode would commit, and autoCommit=YES would commit,
+ // so it does not make sense to test trans=true for those cases
+ if (trans && (continueMode == ContinueMode.COMMIT
+ || autoCommit != AutoCommit.NO)) {
+ continue;
+ }
+ for (TestStatement statement : TestStatement.values()) {
+ for (ReturnColumns columns : ReturnColumns.values()) {
+ ids.add(new Object[]{autoSave, cleanSavePoint, autoCommit, failMode, continueMode,
+ flushCacheOnDeallocate, trans, statement, columns});
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return ids;
+ }
+
+ @Test
+ public void run() throws SQLException {
+ if (continueMode == ContinueMode.IS_VALID) {
+ // make "isValid" a server-prepared statement
+ con.isValid(4);
+ } else if (continueMode == ContinueMode.COMMIT) {
+ doCommit();
+ } else if (continueMode == ContinueMode.SELECT) {
+ assertRows("rollbacktest", 0);
+ }
+
+ Statement statement = con.createStatement();
+ statement.executeUpdate("insert into rollbacktest(a, str) values (0, 'test')");
+ int rowsExpected = 1;
+
+ PreparedStatement ps = con.prepareStatement(testSql.getSql(cols));
+ // Server-prepare the testSql
+ ps.executeQuery().close();
+ rowsExpected += testSql.rowsInserted;
+
+ if (trans) {
+ statement.executeUpdate("update rollbacktest set a=a");
+ }
+
+ switch (failMode) {
+ case SELECT:
+ try {
+ statement.execute("select 1/0");
+ Assert.fail("select 1/0 should fail");
+ } catch (SQLException e) {
+ Assert.assertEquals("division by zero expected",
+ PSQLState.DIVISION_BY_ZERO.getState(), e.getSQLState());
+ }
+ break;
+ case DEALLOCATE:
+ statement.executeUpdate("DEALLOCATE ALL");
+ break;
+ case DISCARD:
+ statement.executeUpdate("DISCARD ALL");
+ break;
+ case ALTER:
+ statement.executeUpdate("alter table rollbacktest add q int");
+ break;
+ case INSERT_BATCH:
+ try {
+ statement.addBatch("insert into rollbacktest(a, str) values (1/0, 'test')");
+ statement.executeBatch();
+ Assert.fail("select 1/0 should fail");
+ } catch (SQLException e) {
+ Assert.assertEquals("division by zero expected",
+ PSQLState.DIVISION_BY_ZERO.getState(), e.getSQLState());
+ }
+ break;
+ default:
+ Assert.fail("Fail mode " + failMode + " is not implemented");
+ }
+
+ PgConnection pgConnection = con.unwrap(PgConnection.class);
+ if (autoSave == AutoSave.ALWAYS) {
+ Assert.assertNotEquals("In AutoSave.ALWAYS, transaction should not fail",
+ TransactionState.FAILED, pgConnection.getTransactionState());
+ }
+ if (autoCommit == AutoCommit.NO) {
+ Assert.assertNotEquals("AutoCommit == NO, thus transaction should be active (open or failed)",
+ TransactionState.IDLE, pgConnection.getTransactionState());
+ }
+ statement.close();
+
+ switch (continueMode) {
+ case COMMIT:
+ try {
+ doCommit();
+ // No assert here: commit should always succeed with exception of well known failure cases in catch
+ } catch (SQLException e) {
+ if (!flushCacheOnDeallocate && DEALLOCATES.contains(failMode)
+ && autoSave == AutoSave.NEVER) {
+ Assert.assertEquals(
+ "flushCacheOnDeallocate is disabled, thus " + failMode + " should cause 'prepared statement \"...\" does not exist'"
+ + " error message is " + e.getMessage(),
+ PSQLState.INVALID_SQL_STATEMENT_NAME.getState(), e.getSQLState());
+ return;
+ }
+ throw e;
+ }
+ return;
+ case IS_VALID:
+ Assert.assertTrue("Connection.isValid should return true unless the connection is closed",
+ con.isValid(4));
+ return;
+ default:
+ break;
+ }
+
+ try {
+ // Try execute server-prepared statement again
+ ps.executeQuery().close();
+ rowsExpected += testSql.rowsInserted;
+ executeSqlSuccess();
+ } catch (SQLException e) {
+ if (autoSave != AutoSave.ALWAYS && TRANS_KILLERS.contains(failMode) && autoCommit == AutoCommit.NO) {
+ Assert.assertEquals(
+ "AutoSave==" + autoSave + ", thus statements should fail with 'current transaction is aborted...', "
+ + " error message is " + e.getMessage(),
+ PSQLState.IN_FAILED_SQL_TRANSACTION.getState(), e.getSQLState());
+ return;
+ }
+
+ if (autoSave == AutoSave.NEVER && autoCommit == AutoCommit.NO) {
+ if (DEALLOCATES.contains(failMode) && !flushCacheOnDeallocate) {
+ Assert.assertEquals(
+ "flushCacheOnDeallocate is disabled, thus " + failMode + " should cause 'prepared statement \"...\" does not exist'"
+ + " error message is " + e.getMessage(),
+ PSQLState.INVALID_SQL_STATEMENT_NAME.getState(), e.getSQLState());
+ } else if (failMode == FailMode.ALTER) {
+ Assert.assertEquals(
+ "AutoSave==NEVER, autocommit=NO, thus ALTER TABLE causes SELECT * to fail with "
+ + "'cached plan must not change result type', "
+ + " error message is " + e.getMessage(),
+ PSQLState.NOT_IMPLEMENTED.getState(), e.getSQLState());
+ } else {
+ throw e;
+ }
+ } else {
+ throw e;
+ }
+ }
+
+ try {
+ assertRows("rollbacktest", rowsExpected);
+ executeSqlSuccess();
+ } catch (SQLException e) {
+ if (autoSave == AutoSave.NEVER && autoCommit == AutoCommit.NO) {
+ if (DEALLOCATES.contains(failMode) && !flushCacheOnDeallocate
+ || failMode == FailMode.ALTER) {
+ // The above statement failed with "prepared statement does not exist", thus subsequent one should fail with
+ // transaction aborted.
+ Assert.assertEquals(
+ "AutoSave==NEVER, thus statements should fail with 'current transaction is aborted...', "
+ + " error message is " + e.getMessage(),
+ PSQLState.IN_FAILED_SQL_TRANSACTION.getState(), e.getSQLState());
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ private void executeSqlSuccess() throws SQLException {
+ if (autoCommit == AutoCommit.YES) {
+ // in autocommit everything should just work
+ } else if (TRANS_KILLERS.contains(failMode)) {
+ if (autoSave != AutoSave.ALWAYS) {
+ Assert.fail(
+ "autosave= " + autoSave + " != ALWAYS, thus the transaction should be killed");
+ }
+ } else if (DEALLOCATES.contains(failMode)) {
+ if (autoSave == AutoSave.NEVER && !flushCacheOnDeallocate
+ && con.unwrap(PGConnection.class).getPreferQueryMode() != PreferQueryMode.SIMPLE) {
+ Assert.fail("flushCacheOnDeallocate == false, thus DEALLOCATE ALL should kill the transaction");
+ }
+ } else if (failMode == FailMode.ALTER) {
+ if (autoSave == AutoSave.NEVER
+ && con.unwrap(PGConnection.class).getPreferQueryMode() != PreferQueryMode.SIMPLE
+ && cols == ReturnColumns.STAR) {
+ Assert.fail("autosave=NEVER, thus the transaction should be killed");
+ }
+ } else {
+ Assert.fail("It is not specified why the test should pass, thus marking a failure");
+ }
+ }
+
+ private void assertRows(String tableName, int nrows) throws SQLException {
+ Statement st = con.createStatement();
+ ResultSet rs = st.executeQuery("select count(*) from " + tableName);
+ rs.next();
+ Assert.assertEquals("Table " + tableName, nrows, rs.getInt(1));
+ }
+
+ private void doCommit() throws SQLException {
+ // Such a dance is required since "commit" checks "current transaction state",
+ // so we need some pending changes, so "commit" query would be sent to the database
+ if (con.getAutoCommit()) {
+ con.setAutoCommit(false);
+ Statement st = con.createStatement();
+ st.executeUpdate(
+ "insert into rollbacktest(a, str) values (42, '" + System.currentTimeMillis() + "," + counter.getAndIncrement() + "')");
+ st.close();
+ }
+ con.commit();
+ con.setAutoCommit(autoCommit == AutoCommit.YES);
+ }
+}
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/8417910bf6c09bad5d457391c47b5e94a397325a...e4dff3838f736dd53aaee4c3b040c28524531035
--
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/8417910bf6c09bad5d457391c47b5e94a397325a...e4dff3838f736dd53aaee4c3b040c28524531035
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20250613/50e841d1/attachment.htm>
More information about the pkg-java-commits
mailing list