[Git][java-team/libpostgresql-jdbc-java][master] 3 commits: New upstream version 42.2.24

Christoph Berg (@myon) gitlab at salsa.debian.org
Wed Sep 29 10:55:27 BST 2021



Christoph Berg pushed to branch master at Debian Java Maintainers / libpostgresql-jdbc-java


Commits:
89ca2dae by Christoph Berg at 2021-09-29T11:49:55+02:00
New upstream version 42.2.24
- - - - -
41fe1256 by Christoph Berg at 2021-09-29T11:49:59+02:00
Update upstream source from tag 'upstream/42.2.24'

Update to upstream version '42.2.24'
with Debian dir eaa930fd80e5df620e6c31431ce6506c0affa0e5
- - - - -
bc1ba662 by Christoph Berg at 2021-09-29T11:54:05+02:00
New upstream version 42.2.24.

- - - - -


21 changed files:

- README.md
- debian/changelog
- pom.xml
- src/main/java/org/postgresql/core/BaseConnection.java
- src/main/java/org/postgresql/core/TypeInfo.java
- src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
- src/main/java/org/postgresql/jdbc/BatchResultHandler.java
- src/main/java/org/postgresql/jdbc/PgConnection.java
- src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
- src/main/java/org/postgresql/jdbc/PgResultSet.java
- src/main/java/org/postgresql/jdbc/TypeInfoCache.java
- src/main/java/org/postgresql/util/DriverInfo.java
- src/main/resources/META-INF/MANIFEST.MF
- src/test/java/org/postgresql/jdbc/AbstractArraysTest.java
- + src/test/java/org/postgresql/jdbc/LargeObjectManagerTest.java
- src/test/java/org/postgresql/test/core/LogServerMessagePropertyTest.java
- src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java
- src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java
- src/test/java/org/postgresql/test/jdbc2/StatementTest.java
- src/test/java/org/postgresql/test/jdbc2/UpdateableResultTest.java
- src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java


Changes:

=====================================
README.md
=====================================
@@ -125,6 +125,7 @@ In addition to the standard connection parameters the driver supports a number o
 | receiveBufferSize             | Integer | -1      | Socket read buffer size  |
 | loggerLevel                   | String  | null    | Logger level of the driver using java.util.logging. Allowed values: OFF, DEBUG or TRACE. |
 | loggerFile                    | String  | null    | File name output of the Logger, if set, the Logger will use a FileHandler to write to a specified file. If the parameter is not set or the file can't be created the ConsoleHandler will be used instead. |
+| logServerErrorDetail          | Boolean | true    | Allows server error detail (such as sql statements and values) to be logged and passed on in exceptions.  Setting to false will mask these errors so they won't be exposed to users, or logs. |
 | allowEncodingChanges          | Boolean | false   | Allow for changes in client_encoding |
 | logUnclosedConnections        | Boolean | false   | When connections that are not explicitly closed are garbage collected, log the stacktrace from the opening of the connection to trace the leak source |
 | binaryTransferEnable          | String  | ""      | Comma separated list of types to enable binary transfer. Either OID numbers or names |


=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+libpgjava (42.2.24-1) unstable; urgency=medium
+
+  * New upstream version 42.2.24.
+
+ -- Christoph Berg <myon at debian.org>  Wed, 29 Sep 2021 11:53:54 +0200
+
 libpgjava (42.2.23-1) unstable; urgency=medium
 
   * New upstream version 42.2.23.


=====================================
pom.xml
=====================================
@@ -10,7 +10,7 @@
     <artifactId>postgresql</artifactId>
     <packaging>jar</packaging>
     <name>PostgreSQL JDBC Driver - JDBC 4.2</name>
-    <version>42.2.23</version>
+    <version>42.2.24</version>
     <description>Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database</description>
     <url>https://github.com/pgjdbc/pgjdbc</url>
 


=====================================
src/main/java/org/postgresql/core/BaseConnection.java
=====================================
@@ -228,4 +228,11 @@ public interface BaseConnection extends PGConnection, Connection {
    * @throws SQLException if the class cannot be found or instantiated.
    */
   PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException;
+
+  /**
+   * Indicates if error details from server used in included in logging and exceptions.
+   *
+   * @return true if should be included and passed on to other exceptions
+   */
+  boolean getLogServerErrorDetail();
 }


=====================================
src/main/java/org/postgresql/core/TypeInfo.java
=====================================
@@ -91,7 +91,7 @@ public interface TypeInfo {
 
   String getJavaClass(int oid) throws SQLException;
 
-  String getTypeForAlias(String alias);
+  /* @Nullable */ String getTypeForAlias(String alias);
 
   int getPrecision(int oid, int typmod);
 
@@ -116,4 +116,30 @@ public interface TypeInfo {
    * @throws SQLException if something goes wrong
    */
   boolean requiresQuotingSqlType(int sqlType) throws SQLException;
+
+  /**
+   * <p>Java Integers are signed 32-bit integers, but oids are unsigned 32-bit integers.
+   * We therefore read them as positive long values and then force them into signed integers
+   * (wrapping around into negative values when required) or we'd be unable to correctly
+   * handle the upper half of the oid space.</p>
+   *
+   * <p>This function handles the mapping of uint32-values in the long to java integers, and
+   * throws for values that are out of range.</p>
+   *
+   * @param oid the oid as a long.
+   * @return the (internal) signed integer representation of the (unsigned) oid.
+   * @throws SQLException if the long has a value outside of the range representable by uint32
+   */
+  int longOidToInt(long oid) throws SQLException;
+
+  /**
+   * Java Integers are signed 32-bit integers, but oids are unsigned 32-bit integers.
+   * We must therefore first map the (internal) integer representation to a positive long
+   * value before sending it to postgresql, or we would be unable to correctly handle the
+   * upper half of the oid space because these negative values are disallowed as OID values.
+   *
+   * @param oid the (signed) integer oid to convert into a long.
+   * @return the non-negative value of this oid, stored as a java long.
+   */
+  long intOidToLong(int oid);
 }


=====================================
src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
=====================================
@@ -847,6 +847,19 @@ public class QueryExecutorImpl extends QueryExecutorBase {
 
           break;
 
+        case 'S': // Parameter Status
+          try {
+            receiveParameterStatus();
+          } catch (SQLException e) {
+            if (error == null) {
+              error = e;
+            } else {
+              error.setNextException(e);
+            }
+            endQuery = true;
+          }
+          break;
+
         default:
           throw new PSQLException(GT.tr("Unknown Response Type {0}.", (char) c),
               PSQLState.CONNECTION_FAILURE);


=====================================
src/main/java/org/postgresql/jdbc/BatchResultHandler.java
=====================================
@@ -151,9 +151,11 @@ public class BatchResultHandler extends ResultHandlerBase {
       }
 
       String queryString = "<unknown>";
-      if (resultIndex < queries.length) {
-        queryString = queries[resultIndex].toString(
-            parameterLists == null ? null : parameterLists[resultIndex]);
+      if (pgStatement.getPGConnection().getLogServerErrorDetail()) {
+        if (resultIndex < queries.length) {
+          queryString = queries[resultIndex].toString(
+             parameterLists == null ? null : parameterLists[resultIndex]);
+        }
       }
 
       BatchUpdateException batchException;


=====================================
src/main/java/org/postgresql/jdbc/PgConnection.java
=====================================
@@ -144,6 +144,8 @@ public class PgConnection implements BaseConnection {
   private boolean readOnly = false;
   // Filter out database objects for which the current user has no privileges granted from the DatabaseMetaData
   private boolean  hideUnprivilegedObjects ;
+  // Whether to include error details in logging and exceptions
+  private final boolean logServerErrorDetail;
   // Bind String to UNSPECIFIED or VARCHAR?
   private final boolean bindStringAsVarchar;
 
@@ -297,6 +299,7 @@ public class PgConnection implements BaseConnection {
     if (PGProperty.LOG_UNCLOSED_CONNECTIONS.getBoolean(info)) {
       openStackTrace = new Throwable("Connection was created at this point:");
     }
+    this.logServerErrorDetail = PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info);
     this.disableColumnSanitiser = PGProperty.DISABLE_COLUMN_SANITISER.getBoolean(info);
 
     if (haveMinimumServerVersion(ServerVersion.v8_3)) {
@@ -1533,6 +1536,10 @@ public class PgConnection implements BaseConnection {
     throw org.postgresql.Driver.notImplemented(this.getClass(), "createQueryObject(Class<T>)");
   }
 
+  public boolean getLogServerErrorDetail() {
+    return logServerErrorDetail;
+  }
+
   @Override
   public boolean isWrapperFor(Class<?> iface) throws SQLException {
     checkClosed();


=====================================
src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
=====================================
@@ -1311,9 +1311,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
              + " '' as TYPE_CAT, '' as TYPE_SCHEM, '' as TYPE_NAME, "
              + "'' AS SELF_REFERENCING_COL_NAME, '' AS REF_GENERATION "
              + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c "
-             + " LEFT JOIN pg_catalog.pg_description d ON (c.oid = d.objoid AND d.objsubid = 0) "
-             + " LEFT JOIN pg_catalog.pg_class dc ON (d.classoid=dc.oid AND dc.relname='pg_class') "
-             + " LEFT JOIN pg_catalog.pg_namespace dn ON (dn.oid=dc.relnamespace AND dn.nspname='pg_catalog') "
+             + " LEFT JOIN pg_catalog.pg_description d ON (c.oid = d.objoid AND d.objsubid = 0  and d.classoid = 'pg_class'::regclass) "
              + " WHERE c.relnamespace = n.oid ";
 
     if (schemaPattern != null && !schemaPattern.isEmpty()) {
@@ -2182,13 +2180,22 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
     sql = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, "
         + "  ct.relname AS TABLE_NAME, a.attname AS COLUMN_NAME, "
         + "  (information_schema._pg_expandarray(i.indkey)).n AS KEY_SEQ, ci.relname AS PK_NAME, "
-        + "  information_schema._pg_expandarray(i.indkey) AS KEYS, a.attnum AS A_ATTNUM "
+        + "  information_schema._pg_expandarray(i.indkey) AS KEYS, a.attnum AS A_ATTNUM, "
+        + "  a.attnotnull AS IS_NOT_NULL "
         + "FROM pg_catalog.pg_class ct "
         + "  JOIN pg_catalog.pg_attribute a ON (ct.oid = a.attrelid) "
         + "  JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid) "
         + "  JOIN pg_catalog.pg_index i ON ( a.attrelid = i.indrelid) "
         + "  JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid) "
-        + "WHERE true ";
+        // primary as well as unique keys can be used to uniquely identify a row to update
+        + "WHERE (i.indisprimary OR ( "
+        + "    i.indisunique "
+        + "    AND i.indisvalid "
+        // partial indexes are not allowed - indpred will not be null if this is a partial index
+        + "    AND i.indpred IS NULL "
+        // indexes with expressions are not allowed
+        + "    AND i.indexprs IS NULL "
+        + "  )) ";
 
     if (schema != null && !schema.isEmpty()) {
       sql += " AND n.nspname = " + escapeQuotes(schema);
@@ -2198,14 +2205,14 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
       sql += " AND ct.relname = " + escapeQuotes(table);
     }
 
-    sql += " AND (i.indisprimary or i.indisunique) ";
     sql = "SELECT "
         + "       result.TABLE_CAT, "
         + "       result.TABLE_SCHEM, "
         + "       result.TABLE_NAME, "
         + "       result.COLUMN_NAME, "
         + "       result.KEY_SEQ, "
-        + "       result.PK_NAME "
+        + "       result.PK_NAME, "
+        + "       result.IS_NOT_NULL "
         + "FROM "
         + "     (" + sql + " ) result"
         + " where "
@@ -2528,14 +2535,14 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
                 + "    trim(both '\"' from pg_catalog.pg_get_indexdef(tmp.CI_OID, tmp.ORDINAL_POSITION, false)) AS COLUMN_NAME, "
                 + (connection.haveMinimumServerVersion(ServerVersion.v9_6)
                         ? "  CASE tmp.AM_NAME "
-                        + "    WHEN 'btree' THEN CASE tmp.I_INDOPTION[tmp.ORDINAL_POSITION - 1] & 1 "
+                        + "    WHEN 'btree' THEN CASE tmp.I_INDOPTION[tmp.ORDINAL_POSITION - 1] & 1::smallint "
                         + "      WHEN 1 THEN 'D' "
                         + "      ELSE 'A' "
                         + "    END "
                         + "    ELSE NULL "
                         + "  END AS ASC_OR_DESC, "
                         : "  CASE tmp.AM_CANORDER "
-                        + "    WHEN true THEN CASE tmp.I_INDOPTION[tmp.ORDINAL_POSITION - 1] & 1 "
+                        + "    WHEN true THEN CASE tmp.I_INDOPTION[tmp.ORDINAL_POSITION - 1] & 1::smallint "
                         + "      WHEN 1 THEN 'D' "
                         + "      ELSE 'A' "
                         + "    END "
@@ -2666,12 +2673,18 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
         + java.sql.Types.DISTINCT
         + " end as data_type, pg_catalog.obj_description(t.oid, 'pg_type')  "
         + "as remarks, CASE WHEN t.typtype = 'd' then  (select CASE";
+    TypeInfo typeInfo = connection.getTypeInfo();
 
     StringBuilder sqlwhen = new StringBuilder();
-    for (Iterator<Integer> i = connection.getTypeInfo().getPGTypeOidsWithSQLTypes(); i.hasNext(); ) {
+    for (Iterator<Integer> i = typeInfo.getPGTypeOidsWithSQLTypes(); i.hasNext(); ) {
       Integer typOid = i.next();
-      int sqlType = connection.getTypeInfo().getSQLType(typOid);
-      sqlwhen.append(" when t.oid = ").append(typOid).append(" then ").append(sqlType);
+      // NB: Java Integers are signed 32-bit integers, but oids are unsigned 32-bit integers.
+      // We must therefore map it to a positive long value before writing it into the query,
+      // or we'll be unable to correctly handle ~ half of the oid space.
+      long longTypOid = typeInfo.intOidToLong(typOid);
+      int sqlType = typeInfo.getSQLType(typOid);
+
+      sqlwhen.append(" when t.oid = ").append(longTypOid).append(" then ").append(sqlType);
     }
     sql += sqlwhen.toString();
 


=====================================
src/main/java/org/postgresql/jdbc/PgResultSet.java
=====================================
@@ -1333,7 +1333,7 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     for (int i = 0; i < numKeys; i++) {
 
       PrimaryKey primaryKey = primaryKeys.get(i);
-      selectSQL.append(primaryKey.name).append("= ?");
+      selectSQL.append(primaryKey.name).append(" = ?");
 
       if (i < numKeys - 1) {
         selectSQL.append(" and ");
@@ -1350,8 +1350,8 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
       selectStatement = connection.prepareStatement(sqlText,
           ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
 
-      for (int j = 0, i = 1; j < numKeys; j++, i++) {
-        selectStatement.setObject(i, primaryKeys.get(j).getValue());
+      for (int i = 0; i < numKeys; i++) {
+        selectStatement.setObject(i + 1, primaryKeys.get(i).getValue());
       }
 
       PgResultSet rs = (PgResultSet) selectStatement.executeQuery();
@@ -1626,20 +1626,42 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     java.sql.ResultSet rs = ((PgDatabaseMetaData)connection.getMetaData()).getPrimaryUniqueKeys("",
         quotelessSchemaName, quotelessTableName);
 
+    String lastConstraintName = null;
+
     while (rs.next()) {
+      String constraintName = castNonNull(rs.getString(6)); // get the constraintName
+      if (lastConstraintName == null || !lastConstraintName.equals(constraintName)) {
+        if (lastConstraintName != null) {
+          if (i == numPKcolumns && numPKcolumns > 0) {
+            break;
+          }
+          connection.getLogger().log(Level.FINE, "no of keys={0} from constraint {1}", new Object[]{i, lastConstraintName});
+        }
+        i = 0;
+        numPKcolumns = 0;
+
+        primaryKeys.clear();
+        lastConstraintName = constraintName;
+      }
       numPKcolumns++;
-      String columnName = castNonNull(rs.getString(4)); // get the columnName
-      int index = findColumnIndex(columnName);
 
-      /* make sure that the user has included the primary key in the resultset */
-      if (index > 0) {
-        i++;
-        primaryKeys.add(new PrimaryKey(index, columnName)); // get the primary key information
+      boolean isNotNull = rs.getBoolean("IS_NOT_NULL");
+
+      /* make sure that only unique keys with all non-null attributes are handled */
+      if (isNotNull) {
+        String columnName = castNonNull(rs.getString(4)); // get the columnName
+        int index = findColumnIndex(columnName);
+
+        /* make sure that the user has included the primary key in the resultset */
+        if (index > 0) {
+          i++;
+          primaryKeys.add(new PrimaryKey(index, columnName)); // get the primary key information
+        }
       }
     }
 
     rs.close();
-    connection.getLogger().log(Level.FINE, "no of keys={0}", i);
+    connection.getLogger().log(Level.FINE, "no of keys={0} from constraint {1}", new Object[]{i, lastConstraintName});
 
     /*
     it is only updatable if the primary keys are available in the resultset
@@ -1665,7 +1687,7 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     }
 
     if (!updateable) {
-      throw new PSQLException(GT.tr("No primary key found for table {0}.", tableName),
+      throw new PSQLException(GT.tr("No eligible primary or unique key found for table {0}.", tableName),
           PSQLState.INVALID_CURSOR_STATE);
     }
 


=====================================
src/main/java/org/postgresql/jdbc/TypeInfoCache.java
=====================================
@@ -268,7 +268,7 @@ public class TypeInfoCache implements TypeInfo {
         pgNameToSQLType.put(typeName, type);
       }
 
-      Integer typeOid = castNonNull(rs.getInt("oid"));
+      Integer typeOid = longOidToInt(castNonNull(rs.getLong("oid")));
       if (!oidToSQLType.containsKey(typeOid)) {
         oidToSQLType.put(typeOid, type);
       }
@@ -286,7 +286,26 @@ public class TypeInfoCache implements TypeInfo {
   }
 
   public synchronized int getSQLType(String pgTypeName) throws SQLException {
-    return getSQLType(castNonNull(getPGType(pgTypeName)));
+    /*
+    Get a few things out of the way such as arrays and known types
+     */
+    if (pgTypeName.endsWith("[]")) {
+      return Types.ARRAY;
+    }
+    Integer i = this.pgNameToSQLType.get(pgTypeName);
+    if (i != null) {
+      return i;
+    }
+
+    /*
+      All else fails then we will query the database.
+      save for future calls
+    */
+    i = getSQLType(castNonNull(getPGType(pgTypeName)));
+
+    pgNameToSQLType.put(pgTypeName, i);
+    return i;
+
   }
 
   public synchronized int getSQLType(int typeOid) throws SQLException {
@@ -299,11 +318,11 @@ public class TypeInfoCache implements TypeInfo {
       return i;
     }
 
-    LOGGER.log(Level.FINEST, "querying SQL typecode for pg type oid '{0}'", typeOid);
+    LOGGER.log(Level.FINEST, "querying SQL typecode for pg type oid '{0}'", intOidToLong(typeOid));
 
     PreparedStatement getTypeInfoStatement = prepareGetTypeInfoStatement();
 
-    getTypeInfoStatement.setInt(1, typeOid);
+    getTypeInfoStatement.setLong(1, intOidToLong(typeOid));
 
     // Go through BaseStatement to avoid transaction start.
     if (!((BaseStatement) getTypeInfoStatement)
@@ -429,7 +448,12 @@ public class TypeInfoCache implements TypeInfo {
     return oidStatementComplex;
   }
 
-  public synchronized int getPGType(String pgTypeName) throws SQLException {
+  public synchronized int getPGType(/* @Nullable */ String pgTypeName) throws SQLException {
+    // there really isn't anything else to return other than UNSPECIFIED here.
+    if ( pgTypeName == null ) {
+      return Oid.UNSPECIFIED;
+    }
+
     Integer oid = pgNameToOid.get(pgTypeName);
     if (oid != null) {
       return oid;
@@ -515,7 +539,7 @@ public class TypeInfoCache implements TypeInfo {
     return getNameStatement;
   }
 
-  public int getPGArrayType(String elementTypeName) throws SQLException {
+  public int getPGArrayType(/* @Nullable */  String elementTypeName) throws SQLException {
     elementTypeName = getTypeForAlias(elementTypeName);
     return getPGType(elementTypeName + "[]");
   }
@@ -667,7 +691,10 @@ public class TypeInfoCache implements TypeInfo {
     return result == null ? "java.lang.String" : result;
   }
 
-  public String getTypeForAlias(String alias) {
+  public /* @Nullable */ String getTypeForAlias(/* @Nullable */ String alias) {
+    if (alias == null ) {
+      return null;
+    }
     String type = typeAliases.get(alias);
     if (type != null) {
       return type;
@@ -962,4 +989,18 @@ public class TypeInfoCache implements TypeInfo {
     }
     return true;
   }
+
+  @Override
+  public int longOidToInt(long oid) throws SQLException {
+    if ((oid & 0xFFFFFFFF00000000L) != 0) {
+      throw new PSQLException(GT.tr("Value is not an OID: {0}", oid), PSQLState.NUMERIC_VALUE_OUT_OF_RANGE);
+    }
+
+    return (int) oid;
+  }
+
+  @Override
+  public long intOidToLong(int oid) {
+    return ((long) oid) & 0xFFFFFFFFL;
+  }
 }


=====================================
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.2.23";
+  public static final String DRIVER_VERSION = "42.2.24";
   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 = 2;
-  public static final int PATCH_VERSION = 23;
+  public static final int PATCH_VERSION = 24;
 
   // JDBC specification
   public static final String JDBC_VERSION = "4.2";


=====================================
src/main/resources/META-INF/MANIFEST.MF
=====================================
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
 Implementation-Title: PostgreSQL JDBC Driver
 Bundle-License: BSD-2-Clause
 Automatic-Module-Name: org.postgresql.jdbc
-Implementation-Version: 42.2.23
+Implementation-Version: 42.2.24
 Specification-Vendor: Oracle Corporation
 Specification-Title: JDBC
 Implementation-Vendor-Id: org.postgresql


=====================================
src/test/java/org/postgresql/jdbc/AbstractArraysTest.java
=====================================
@@ -942,5 +942,13 @@ public abstract class AbstractArraysTest<A> {
     public boolean hintReadOnly() {
       return false;
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean getLogServerErrorDetail() {
+      return false;
+    }
   }
 }


=====================================
src/test/java/org/postgresql/jdbc/LargeObjectManagerTest.java
=====================================
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.postgresql.largeobject.LargeObjectManager;
+import org.postgresql.test.TestUtil;
+import org.postgresql.util.PSQLException;
+import org.postgresql.util.PSQLState;
+
+import org.junit.jupiter.api.Test;
+
+import java.sql.Statement;
+
+class LargeObjectManagerTest {
+
+  /*
+   * It is possible for PostgreSQL to send a ParameterStatus message after an ErrorResponse
+   * Receiving such a message should not lead to an invalid connection state
+   * See https://github.com/pgjdbc/pgjdbc/issues/2237
+   */
+  @Test
+  public void testOpenWithErrorAndSubsequentParameterStatusMessageShouldLeaveConnectionInUsableStateAndUpdateParameterStatus() throws Exception {
+    try (PgConnection con = (PgConnection) TestUtil.openDB()) {
+      con.setAutoCommit(false);
+      String originalApplicationName = con.getParameterStatus("application_name");
+      try (Statement statement = con.createStatement()) {
+        statement.execute("begin;");
+        // Set transaction application_name to trigger ParameterStatus message after error
+        // https://www.postgresql.org/docs/14/protocol-flow.html#PROTOCOL-ASYNC
+        String updatedApplicationName = "LargeObjectManagerTest-application-name";
+        statement.execute("set application_name to '" + updatedApplicationName + "'");
+
+        LargeObjectManager loManager = con.getLargeObjectAPI();
+        try {
+          loManager.open(0, false);
+          fail("Succeeded in opening a non-existent large object");
+        } catch (PSQLException e) {
+          assertEquals(PSQLState.UNDEFINED_OBJECT.getState(), e.getSQLState());
+        }
+
+        // Should be reset to original application name
+        assertEquals(originalApplicationName, con.getParameterStatus("application_name"));
+      }
+    }
+  }
+}


=====================================
src/test/java/org/postgresql/test/core/LogServerMessagePropertyTest.java
=====================================
@@ -15,6 +15,7 @@ import org.junit.Assume;
 import org.junit.Test;
 
 import java.sql.Connection;
+import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.util.Properties;
 
@@ -34,15 +35,22 @@ public class LogServerMessagePropertyTest {
    * create a temp table with a primary key, run two inserts to generate
    * a duplicate key error, and finally return the exception message.
    */
-  private static String testViolatePrimaryKey(Properties props) throws SQLException {
+  private static String testViolatePrimaryKey(Properties props, boolean batch) throws SQLException {
     Connection conn = TestUtil.openDB(props);
     Assume.assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v9_1));
     try {
       TestUtil.execute(CREATE_TABLE_SQL, conn);
-      // First insert should work
-      TestUtil.execute(INSERT_SQL, conn);
-      // Second insert should throw a duplicate key error
-      TestUtil.execute(INSERT_SQL, conn);
+      if (batch) {
+        PreparedStatement stmt = conn.prepareStatement(INSERT_SQL);
+        stmt.addBatch();
+        stmt.addBatch();
+        stmt.executeBatch();
+      } else {
+        // First insert should work
+        TestUtil.execute(INSERT_SQL, conn);
+        // Second insert should throw a duplicate key error
+        TestUtil.execute(INSERT_SQL, conn);
+      }
     } catch (SQLException e) {
       Assert.assertEquals("SQL state must be for a unique violation", PSQLState.UNIQUE_VIOLATION.getState(), e.getSQLState());
       return e.getMessage();
@@ -54,6 +62,10 @@ public class LogServerMessagePropertyTest {
     return null;
   }
 
+  private static String testViolatePrimaryKey(Properties props) throws SQLException {
+    return testViolatePrimaryKey(props, false);
+  }
+
   private static void assertMessageContains(String message, String text) {
     if (message.toLowerCase().indexOf(text.toLowerCase()) < 0) {
       Assert.fail(String.format("Message must contain text '%s': %s", text, message));
@@ -97,4 +109,36 @@ public class LogServerMessagePropertyTest {
     assertMessageDoesNotContain(message, "Detail:");
     assertMessageDoesNotContain(message, SECRET_VALUE);
   }
+
+  @Test
+  public void testBatchWithDefaults() throws SQLException {
+    Properties props = new Properties();
+    String message = testViolatePrimaryKey(props, true);
+    assertMessageContains(message, PRIMARY_KEY_NAME);
+    assertMessageContains(message, "Detail:");
+    assertMessageContains(message, SECRET_VALUE);
+  }
+
+  /**
+   * NOTE: This should be the same as the default case as "true" is the default.
+   */
+  @Test
+  public void testBatchExplicitlyEnabled() throws SQLException {
+    Properties props = new Properties();
+    props.setProperty(PGProperty.LOG_SERVER_ERROR_DETAIL.getName(), "true");
+    String message = testViolatePrimaryKey(props, true);
+    assertMessageContains(message, PRIMARY_KEY_NAME);
+    assertMessageContains(message, "Detail:");
+    assertMessageContains(message, SECRET_VALUE);
+  }
+
+  @Test
+  public void testBatchWithLogServerErrorDetailDisabled() throws SQLException {
+    Properties props = new Properties();
+    props.setProperty(PGProperty.LOG_SERVER_ERROR_DETAIL.getName(), "false");
+    String message = testViolatePrimaryKey(props, true);
+    assertMessageContains(message, PRIMARY_KEY_NAME);
+    assertMessageDoesNotContain(message, "Detail:");
+    assertMessageDoesNotContain(message, SECRET_VALUE);
+  }
 }


=====================================
src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java
=====================================
@@ -6,6 +6,7 @@
 package org.postgresql.test.jdbc2;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import org.postgresql.core.TypeInfo;
 import org.postgresql.jdbc.PgConnection;
@@ -81,4 +82,14 @@ public class DatabaseMetaDataCacheTest {
     List<LogRecord> typeQueries = log.getRecordsMatching(SQL_TYPE_QUERY_LOG_FILTER);
     assertEquals("PgDatabaseMetadata.getTypeInfo() resulted in individual queries for SQL typecodes", 0, typeQueries.size());
   }
+
+  @Test
+  public void testTypeForAlias() {
+    TypeInfo ti = con.getTypeInfo();
+    assertEquals("bool", ti.getTypeForAlias("boolean"));
+    assertEquals("bool", ti.getTypeForAlias("Boolean"));
+    assertEquals("Bool", ti.getTypeForAlias("Bool"));
+    assertEquals("bogus", ti.getTypeForAlias("bogus"));
+    assertNull(ti.getTypeForAlias(null));
+  }
 }


=====================================
src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java
=====================================
@@ -87,6 +87,15 @@ public class DatabaseMetaDataTest {
     TestUtil.createCompositeType(con, "custom", "i int", false);
     TestUtil.createCompositeType(con, "_custom", "f float", false);
 
+    // create a table and multiple comments on it
+    TestUtil.createTable(con, "duplicate", "x text");
+    TestUtil.execute("comment on table duplicate is 'duplicate table'", con);
+    TestUtil.execute("create or replace function bar() returns integer language sql as $$ select 1 $$", con);
+    TestUtil.execute("comment on function bar() is 'bar function'", con);
+    try (Connection conPriv = TestUtil.openPrivilegedDB()) {
+      TestUtil.execute("update pg_description set objoid = 'duplicate'::regclass where objoid = 'bar'::regproc", conPriv);
+    }
+
     // 8.2 does not support arrays of composite types
     TestUtil.createTable(con, "customtable", "c1 custom, c2 _custom"
         + (TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3) ? ", c3 custom[], c4 _custom[]" : ""));
@@ -111,6 +120,12 @@ public class DatabaseMetaDataTest {
           "CREATE OR REPLACE FUNCTION f5() RETURNS TABLE (i int) LANGUAGE sql AS 'SELECT 1'");
     }
 
+    // create a custom `&` operator, which caused failure with `&` usage in getIndexInfo()
+    stmt.execute(
+        "CREATE OR REPLACE FUNCTION f6(numeric, integer) returns integer as 'BEGIN return $1::integer & $2;END;' language plpgsql immutable;");
+    stmt.execute("DROP OPERATOR IF EXISTS & (numeric, integer)");
+    stmt.execute("CREATE OPERATOR & (LEFTARG = numeric, RIGHTARG = integer, PROCEDURE = f6)");
+
     TestUtil.createDomain(con, "nndom", "int not null");
     TestUtil.createDomain(con, "varbit2", "varbit(3)");
     TestUtil.createDomain(con, "float83", "numeric(8,3)");
@@ -125,6 +140,8 @@ public class DatabaseMetaDataTest {
     // metadatatest table's type
     Statement stmt = con.createStatement();
     stmt.execute("DROP FUNCTION f4(int)");
+    TestUtil.execute("drop function bar()", con);
+    TestUtil.dropTable(con, "duplicate");
 
     TestUtil.dropView(con, "viewtest");
     TestUtil.dropTable(con, "metadatatest");
@@ -143,6 +160,8 @@ public class DatabaseMetaDataTest {
     stmt.execute("DROP FUNCTION f1(int, varchar)");
     stmt.execute("DROP FUNCTION f2(int, varchar)");
     stmt.execute("DROP FUNCTION f3(int, varchar)");
+    stmt.execute("DROP OPERATOR IF EXISTS & (numeric, integer)");
+    stmt.execute("DROP FUNCTION f6(numeric, integer)");
     TestUtil.dropTable(con, "domaintable");
     TestUtil.dropDomain(con, "nndom");
     TestUtil.dropDomain(con, "varbit2");


=====================================
src/test/java/org/postgresql/test/jdbc2/StatementTest.java
=====================================
@@ -474,57 +474,58 @@ public class StatementTest {
   @Test
   public void testWarningsAreAvailableAsap()
       throws Exception {
-    final Connection outerLockCon = TestUtil.openDB();
-    outerLockCon.setAutoCommit(false);
-    //Acquire an exclusive lock so we can block the notice generating statement
-    outerLockCon.createStatement().execute("LOCK TABLE test_lock IN ACCESS EXCLUSIVE MODE;");
-    con.createStatement()
-            .execute("CREATE OR REPLACE FUNCTION notify_then_sleep() RETURNS VOID AS "
-                + "$BODY$ "
-                + "BEGIN "
-                + "RAISE NOTICE 'Test 1'; "
-                + "RAISE NOTICE 'Test 2'; "
-                + "LOCK TABLE test_lock IN ACCESS EXCLUSIVE MODE; "
-                + "END "
-                + "$BODY$ "
-                + "LANGUAGE plpgsql;");
-    con.createStatement().execute("SET SESSION client_min_messages = 'NOTICE'");
-    //If we never receive the two warnings the statement will just hang, so set a low timeout
-    con.createStatement().execute("SET SESSION statement_timeout = 1000");
-    final PreparedStatement preparedStatement = con.prepareStatement("SELECT notify_then_sleep()");
-    final Callable<Void> warningReader = new Callable<Void>() {
-      @Override
-      public Void call() throws SQLException, InterruptedException {
-        while (true) {
-          SQLWarning warning = preparedStatement.getWarnings();
-          if (warning != null) {
-            assertEquals("First warning received not first notice raised",
-                "Test 1", warning.getMessage());
-            SQLWarning next = warning.getNextWarning();
-            if (next != null) {
-              assertEquals("Second warning received not second notice raised",
-                  "Test 2", next.getMessage());
-              //Release the lock so that the notice generating statement can end.
-              outerLockCon.commit();
-              return null;
+    try (Connection outerLockCon = TestUtil.openDB()) {
+      outerLockCon.setAutoCommit(false);
+      //Acquire an exclusive lock so we can block the notice generating statement
+      outerLockCon.createStatement().execute("LOCK TABLE test_lock IN ACCESS EXCLUSIVE MODE;");
+      con.createStatement()
+              .execute("CREATE OR REPLACE FUNCTION notify_then_sleep() RETURNS VOID AS "
+                  + "$BODY$ "
+                  + "BEGIN "
+                  + "RAISE NOTICE 'Test 1'; "
+                  + "RAISE NOTICE 'Test 2'; "
+                  + "LOCK TABLE test_lock IN ACCESS EXCLUSIVE MODE; "
+                  + "END "
+                  + "$BODY$ "
+                  + "LANGUAGE plpgsql;");
+      con.createStatement().execute("SET SESSION client_min_messages = 'NOTICE'");
+      //If we never receive the two warnings the statement will just hang, so set a low timeout
+      con.createStatement().execute("SET SESSION statement_timeout = 1000");
+      final PreparedStatement preparedStatement = con.prepareStatement("SELECT notify_then_sleep()");
+      final Callable<Void> warningReader = new Callable<Void>() {
+        @Override
+        public Void call() throws SQLException, InterruptedException {
+          while (true) {
+            SQLWarning warning = preparedStatement.getWarnings();
+            if (warning != null) {
+              assertEquals("First warning received not first notice raised",
+                  "Test 1", warning.getMessage());
+              SQLWarning next = warning.getNextWarning();
+              if (next != null) {
+                assertEquals("Second warning received not second notice raised",
+                    "Test 2", next.getMessage());
+                //Release the lock so that the notice generating statement can end.
+                outerLockCon.commit();
+                return null;
+              }
             }
+            //Break the loop on InterruptedException
+            Thread.sleep(0);
           }
-          //Break the loop on InterruptedException
-          Thread.sleep(0);
         }
-      }
-    };
-    ExecutorService executorService = Executors.newSingleThreadExecutor();
-    try {
-      Future<Void> future = executorService.submit(warningReader);
-      //Statement should only finish executing once we have
-      //received the two notices and released the outer lock.
-      preparedStatement.execute();
+      };
+      ExecutorService executorService = Executors.newSingleThreadExecutor();
+      try {
+        Future<Void> future = executorService.submit(warningReader);
+        //Statement should only finish executing once we have
+        //received the two notices and released the outer lock.
+        preparedStatement.execute();
 
-      //If test takes longer than 2 seconds its a failure.
-      future.get(2, TimeUnit.SECONDS);
-    } finally {
-      executorService.shutdownNow();
+        //If test takes longer than 2 seconds its a failure.
+        future.get(2, TimeUnit.SECONDS);
+      } finally {
+        executorService.shutdownNow();
+      }
     }
   }
 


=====================================
src/test/java/org/postgresql/test/jdbc2/UpdateableResultTest.java
=====================================
@@ -24,6 +24,8 @@ import java.io.ByteArrayInputStream;
 import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
 import java.sql.Array;
+import java.sql.Connection;
+import java.sql.Date;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -39,7 +41,13 @@ public class UpdateableResultTest extends BaseTest4 {
     super.setUp();
     TestUtil.createTable(con, "updateable",
         "id int primary key, name text, notselected text, ts timestamp with time zone, intarr int[]");
+    TestUtil.createTable(con, "hasdate", "id int primary key, dt date unique, name text");
+    TestUtil.createTable(con, "unique_null_constraint", "u1 int unique, name1 text");
+    TestUtil.createTable(con, "uniquekeys", "id int unique not null, id2 int unique, dt date");
+    TestUtil.createTable(con, "partialunique", "subject text, target text, success boolean");
+    TestUtil.execute("CREATE UNIQUE INDEX tests_success_constraint ON partialunique (subject, target) WHERE success", con);
     TestUtil.createTable(con, "second", "id1 int primary key, name1 text");
+    TestUtil.createTable(con, "primaryunique", "id int primary key, name text unique not null, dt date");
     TestUtil.createTable(con, "serialtable", "gen_id serial primary key, name text");
     TestUtil.createTable(con, "compositepktable", "gen_id serial, name text, dec_id serial");
     TestUtil.execute( "alter sequence compositepktable_dec_id_seq increment by 10; alter sequence compositepktable_dec_id_seq restart with 10", con);
@@ -48,7 +56,8 @@ public class UpdateableResultTest extends BaseTest4 {
     TestUtil.createTable(con, "multicol", "id1 int not null, id2 int not null, val text");
     TestUtil.createTable(con, "nopkmulticol", "id1 int not null, id2 int not null, val text");
     TestUtil.createTable(con, "booltable", "id int not null primary key, b boolean default false");
-    TestUtil.execute( "insert into booltable (id) values (1)", con);
+    TestUtil.execute("insert into booltable (id) values (1)", con);
+    TestUtil.execute("insert into uniquekeys(id, id2, dt) values (1, 2, now())", con);
 
     Statement st2 = con.createStatement();
     // create pk for multicol table
@@ -56,8 +65,9 @@ public class UpdateableResultTest extends BaseTest4 {
     // put some dummy data into second
     st2.execute("insert into second values (1,'anyvalue' )");
     st2.close();
-    TestUtil.createTable(con, "uniqueconstraint", "u1 int unique, name1 text");
-    TestUtil.execute("insert into uniqueconstraint values (1, 'dave')", con);
+    TestUtil.execute("insert into unique_null_constraint values (1, 'dave')", con);
+    TestUtil.execute("insert into unique_null_constraint values (null, 'unknown')", con);
+    TestUtil.execute("insert into primaryunique values (1, 'dave', now())", con);
 
   }
 
@@ -70,7 +80,11 @@ public class UpdateableResultTest extends BaseTest4 {
     TestUtil.dropTable(con, "stream");
     TestUtil.dropTable(con, "nopkmulticol");
     TestUtil.dropTable(con, "booltable");
-    TestUtil.dropTable(con, "uniqueconstraint");
+    TestUtil.dropTable(con, "unique_null_constraint");
+    TestUtil.dropTable(con, "hasdate");
+    TestUtil.dropTable(con, "uniquekeys");
+    TestUtil.dropTable(con, "partialunique");
+    TestUtil.dropTable(con, "primaryunique");
     super.tearDown();
   }
 
@@ -447,6 +461,26 @@ public class UpdateableResultTest extends BaseTest4 {
     st.close();
   }
 
+  @Test
+  public void testUpdateDate() throws Exception {
+    Date testDate = Date.valueOf("2021-01-01");
+    TestUtil.execute("insert into hasdate values (1,'2021-01-01'::date)", con);
+    con.setAutoCommit(false);
+    String sql = "SELECT * FROM hasdate where id=1";
+    ResultSet rs = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+        ResultSet.CONCUR_UPDATABLE).executeQuery(sql);
+    assertTrue(rs.next());
+    assertEquals(testDate, rs.getDate("dt"));
+    rs.updateDate("dt", Date.valueOf("2020-01-01"));
+    rs.updateRow();
+    assertEquals(Date.valueOf("2020-01-01"), rs.getDate("dt"));
+    con.commit();
+    rs = con.createStatement().executeQuery("select dt from hasdate where id=1");
+    assertTrue(rs.next());
+    assertEquals(Date.valueOf("2020-01-01"), rs.getDate("dt"));
+    rs.close();
+  }
+
   @Test
   public void test2193() throws Exception {
     Statement st =
@@ -717,24 +751,152 @@ public class UpdateableResultTest extends BaseTest4 {
 
   @Test
   public void testOidUpdatable() throws Exception {
+    Connection privilegedCon = TestUtil.openPrivilegedDB();
+    try {
+      Statement st = privilegedCon.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+          ResultSet.CONCUR_UPDATABLE);
+      ResultSet rs = st.executeQuery("SELECT oid,* FROM pg_class WHERE relname = 'pg_class'");
+      assertTrue(rs.next());
+      assertTrue(rs.first());
+      rs.updateString("relname", "pg_class");
+      rs.updateRow();
+      rs.close();
+      st.close();
+    } finally {
+      privilegedCon.close();
+    }
+  }
+
+  @Test
+  public void testUniqueWithNullableColumnsNotUpdatable() throws Exception {
+    Statement st = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+        ResultSet.CONCUR_UPDATABLE);
+    ResultSet rs = st.executeQuery("SELECT u1, name1 from unique_null_constraint");
+    assertTrue(rs.next());
+    assertTrue(rs.first());
+    try {
+      rs.updateString("name1", "bob");
+      fail("Should have failed since unique column u1 is nullable");
+    } catch (SQLException ex) {
+      assertEquals("No eligible primary or unique key found for table unique_null_constraint.",
+          ex.getMessage());
+    }
+    rs.close();
+    st.close();
+  }
+
+  @Test
+  public void testPrimaryAndUniqueUpdateableByPrimary() throws Exception {
     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
         ResultSet.CONCUR_UPDATABLE);
-    ResultSet rs = st.executeQuery("SELECT oid,* FROM pg_class WHERE relname = 'pg_class'");
+    ResultSet rs = st.executeQuery("SELECT id, dt from primaryunique");
     assertTrue(rs.next());
     assertTrue(rs.first());
-    rs.updateString("relname", "pg_class");
+    int id = rs.getInt("id");
+    rs.updateDate("dt", Date.valueOf("1999-01-01"));
+    rs.updateRow();
+    assertFalse(rs.next());
+    rs.close();
+    rs = st.executeQuery("select dt from primaryunique where id = " + id);
+    assertTrue(rs.next());
+    assertEquals(Date.valueOf("1999-01-01"), rs.getDate("dt"));
+    rs.close();
+    st.close();
+  }
+
+  @Test
+  public void testPrimaryAndUniqueUpdateableByUnique() throws Exception {
+    Statement st = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+        ResultSet.CONCUR_UPDATABLE);
+    ResultSet rs = st.executeQuery("SELECT name, dt from primaryunique");
+    assertTrue(rs.next());
+    assertTrue(rs.first());
+    String name = rs.getString("name");
+    rs.updateDate("dt", Date.valueOf("1999-01-01"));
+    rs.updateRow();
+    assertFalse(rs.next());
+    rs.close();
+    rs = st.executeQuery("select dt from primaryunique where name = '" + name + "'");
+    assertTrue(rs.next());
+    assertEquals(Date.valueOf("1999-01-01"), rs.getDate("dt"));
     rs.close();
     st.close();
   }
 
   @Test
-  public void testUniqueUpdatable() throws Exception {
+  public void testUniqueWithNullAndNotNullableColumnUpdateable() throws Exception {
     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
         ResultSet.CONCUR_UPDATABLE);
-    ResultSet rs = st.executeQuery("SELECT * from uniqueconstraint");
+    int id = 0;
+    int id2 = 0;
+    ResultSet rs = st.executeQuery("SELECT id, id2, dt from uniquekeys");
     assertTrue(rs.next());
     assertTrue(rs.first());
-    rs.updateString("name1", "bob");
+    id = rs.getInt(("id"));
+    id2 = rs.getInt(("id2"));
+    rs.updateDate("dt", Date.valueOf("1999-01-01"));
+    rs.updateRow();
+    rs.close();
+    rs = st.executeQuery("select dt from uniquekeys where id = " + id + " and id2 = " + id2);
+    assertNotNull(rs);
+    assertTrue(rs.next());
+    assertEquals(Date.valueOf("1999-01-01"), rs.getDate("dt"));
+    rs.close();
+    st.close();
+  }
+
+  @Test
+  public void testUniqueWithNotNullableColumnUpdateable() throws Exception {
+    Statement st = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+        ResultSet.CONCUR_UPDATABLE);
+    int id = 0;
+    ResultSet rs = st.executeQuery("SELECT id, dt from uniquekeys");
+    assertTrue(rs.next());
+    assertTrue(rs.first());
+    id = rs.getInt(("id"));
+    rs.updateDate("dt", Date.valueOf("1999-01-01"));
+    rs.updateRow();
+    rs.close();
+    rs = st.executeQuery("select id, dt from uniquekeys where id = " + id);
+    assertNotNull(rs);
+    assertTrue(rs.next());
+    assertEquals(Date.valueOf("1999-01-01"), rs.getDate("dt"));
+    rs.close();
+    st.close();
+  }
+
+  @Test
+  public void testUniqueWithNullableColumnNotUpdateable() throws Exception {
+    Statement st = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+        ResultSet.CONCUR_UPDATABLE);
+    ResultSet rs = st.executeQuery("SELECT id2, dt from uniquekeys");
+    assertTrue(rs.next());
+    assertTrue(rs.first());
+    try {
+      rs.updateDate("dt", Date.valueOf("1999-01-01"));
+      fail("Should have failed since id2 is nullable column");
+    } catch (SQLException ex) {
+      assertEquals("No eligible primary or unique key found for table uniquekeys.",
+          ex.getMessage());
+    }
+    rs.close();
+    st.close();
+  }
+
+  @Test
+  public void testNoUniqueNotUpdateable() throws SQLException {
+    Statement st = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+        ResultSet.CONCUR_UPDATABLE);
+    ResultSet rs = st.executeQuery("SELECT dt from uniquekeys");
+    assertTrue(rs.next());
+    assertTrue(rs.first());
+    try {
+      rs.updateDate("dt", Date.valueOf("1999-01-01"));
+      fail("Should have failed since no UK/PK are in the select statement");
+    } catch (SQLException ex) {
+      assertEquals("No eligible primary or unique key found for table uniquekeys.",
+          ex.getMessage());
+    }
     rs.close();
     st.close();
   }


=====================================
src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java
=====================================
@@ -9,7 +9,11 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import org.postgresql.core.TypeInfo;
+import org.postgresql.jdbc.PgConnection;
 import org.postgresql.test.TestUtil;
+import org.postgresql.util.PSQLException;
+import org.postgresql.util.PSQLState;
 
 import org.junit.After;
 import org.junit.Before;
@@ -18,6 +22,7 @@ import org.junit.Test;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.sql.Types;
 
 public class DatabaseMetaDataTest {
@@ -102,4 +107,51 @@ public class DatabaseMetaDataTest {
 
     assertFalse(rs.next());
   }
+
+  @Test
+  public void testLargeOidIsHandledCorrectly() throws SQLException {
+    TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo();
+
+    try {
+      ti.getSQLType((int) 4294967295L); // (presumably) unused OID 4294967295, which is 2**32 - 1
+    } catch (PSQLException ex) {
+      assertEquals(ex.getSQLState(), PSQLState.NO_DATA.getState());
+    }
+  }
+
+  @Test
+  public void testOidConversion() throws SQLException {
+    TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo();
+    int oid = 0;
+    long loid = 0;
+    assertEquals(oid, ti.longOidToInt(loid));
+    assertEquals(loid, ti.intOidToLong(oid));
+
+    oid = Integer.MAX_VALUE;
+    loid = Integer.MAX_VALUE;
+    assertEquals(oid, ti.longOidToInt(loid));
+    assertEquals(loid, ti.intOidToLong(oid));
+
+    oid = Integer.MIN_VALUE;
+    loid = 1L << 31;
+    assertEquals(oid, ti.longOidToInt(loid));
+    assertEquals(loid, ti.intOidToLong(oid));
+
+    oid = -1;
+    loid = 0xFFFFFFFFL;
+    assertEquals(oid, ti.longOidToInt(loid));
+    assertEquals(loid, ti.intOidToLong(oid));
+  }
+
+  @Test(expected = PSQLException.class)
+  public void testOidConversionThrowsForNegativeLongValues() throws SQLException {
+    TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo();
+    ti.longOidToInt(-1);
+  }
+
+  @Test(expected = PSQLException.class)
+  public void testOidConversionThrowsForTooLargeLongValues() throws SQLException {
+    TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo();
+    ti.longOidToInt(1L << 32);
+  }
 }



View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/f6e7654856e5623db01ff953e04c20d03902e10c...bc1ba6625de4d75592f79f075f9898aefeb9f9c0

-- 
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/f6e7654856e5623db01ff953e04c20d03902e10c...bc1ba6625de4d75592f79f075f9898aefeb9f9c0
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/20210929/6a4cd722/attachment.htm>


More information about the pkg-java-commits mailing list