[Git][java-team/libpostgresql-jdbc-java][upstream] New upstream version 42.7.7

Christoph Berg (@myon) gitlab at salsa.debian.org
Fri Jun 13 14:31:49 BST 2025



Christoph Berg pushed to branch upstream 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
- - - - -


7 changed files:

- 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:

=====================================
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/-/commit/4ac37a140a26360eb677b5a0ee3bdf115ae72a51

-- 
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/commit/4ac37a140a26360eb677b5a0ee3bdf115ae72a51
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/814c77c5/attachment.htm>


More information about the pkg-java-commits mailing list