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

Christoph Berg (@myon) gitlab at salsa.debian.org
Mon Aug 22 13:25:38 BST 2022



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


Commits:
732b021a by Christoph Berg at 2022-08-22T14:24:01+02:00
New upstream version 42.4.2
- - - - -


18 changed files:

- README.md
- pom.xml
- src/main/java/org/postgresql/PGProperty.java
- src/main/java/org/postgresql/core/TypeInfo.java
- src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
- src/main/java/org/postgresql/ds/common/BaseDataSource.java
- src/main/java/org/postgresql/gss/GssAction.java
- src/main/java/org/postgresql/gss/MakeGSS.java
- src/main/java/org/postgresql/jdbc/PgCallableStatement.java
- src/main/java/org/postgresql/jdbc/PgConnection.java
- src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
- src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
- src/main/java/org/postgresql/jdbc/PgStatement.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/UUIDArrayTest.java
- src/test/java/org/postgresql/test/jdbc2/StatementTest.java


Changes:

=====================================
README.md
=====================================
@@ -118,6 +118,7 @@ In addition to the standard connection parameters the driver supports a number o
 | loginTimeout                  | Integer | 0       | Specify how long to wait for establishment of a database connection.|
 | connectTimeout                | Integer | 10      | The timeout value used for socket connect operations. |
 | socketTimeout                 | Integer | 0       | The timeout value used for socket read operations. |
+| sslResponseTimeout            | Integer | 5000    | Socket timeout waiting for a response from a request for SSL upgrade from the server. |                                                                                                                                                                        
 | tcpKeepAlive                  | Boolean | false   | Enable or disable TCP keep-alive. |
 | tcpNoDelay                    | Boolean | true    | Enable or disable TCP no delay. |
 | ApplicationName               | String  | PostgreSQL JDBC Driver    | The application name (require server version >= 9.0). If assumeMinServerVersion is set to >= 9.0 this will be sent in the startup packets, otherwise after the connection is made |


=====================================
pom.xml
=====================================
@@ -10,7 +10,7 @@
     <artifactId>postgresql</artifactId>
     <packaging>jar</packaging>
     <name>PostgreSQL JDBC Driver - JDBC 4.2</name>
-    <version>42.4.1</version>
+    <version>42.4.2</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/PGProperty.java
=====================================
@@ -672,6 +672,15 @@ public enum PGProperty {
       null,
       "A class, implementing javax.security.auth.callback.CallbackHandler that can handle PassworCallback for the ssl password."),
 
+  /**
+   * <p>After requesting an upgrade to SSL from the server there are reports of the server not responding due to a failover
+   * without a timeout here, the client can wait forever. This timeout will be set before the request and reset after </p>
+   */
+  SSL_RESPONSE_TIMEOUT(
+      "sslResponseTimeout",
+      "5000",
+      "Time in milliseconds we wait for a response from the server after requesting SSL upgrade"),
+
   /**
    * File containing the root certificate when validating server ({@code sslmode} = {@code
    * verify-ca} or {@code verify-full}). Default will be the file {@code root.crt} in {@code


=====================================
src/main/java/org/postgresql/core/TypeInfo.java
=====================================
@@ -36,6 +36,8 @@ public interface TypeInfo {
    */
   int getSQLType(String pgTypeName) throws SQLException;
 
+  int getJavaArrayType(String className) throws SQLException;
+
   /**
    * Look up the oid for a given postgresql type name. This is the inverse of
    * {@link #getPGType(int)}.


=====================================
src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
=====================================
@@ -532,6 +532,17 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
 
     LOGGER.log(Level.FINEST, " FE=> SSLRequest");
 
+    int sslTimeout = PGProperty.SSL_RESPONSE_TIMEOUT.getInt(info);
+    int currentTimeout = pgStream.getSocket().getSoTimeout();
+
+    // if the current timeout is less than sslTimeout then
+    // use the smaller timeout. We could do something tricky
+    // here to not set it in that case but this is pretty readable
+    if (currentTimeout > 0 && currentTimeout < sslTimeout) {
+      sslTimeout = currentTimeout;
+    }
+
+    pgStream.getSocket().setSoTimeout(sslTimeout);
     // Send SSL request packet
     pgStream.sendInteger4(8);
     pgStream.sendInteger2(1234);
@@ -540,6 +551,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
 
     // Now get the response from the backend, one of N, E, S.
     int beresp = pgStream.receiveChar();
+    pgStream.getSocket().setSoTimeout(currentTimeout);
+
     switch (beresp) {
       case 'E':
         LOGGER.log(Level.FINEST, " <=BE SSLError");


=====================================
src/main/java/org/postgresql/ds/common/BaseDataSource.java
=====================================
@@ -354,6 +354,24 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     PGProperty.CONNECT_TIMEOUT.set(properties, connectTimeout);
   }
 
+  /**
+   *
+   * @return SSL ResponseTimeout
+   * @see PGProperty#SSL_RESPONSE_TIMEOUT
+   */
+  public int getSslResponseTimeout() {
+    return PGProperty.SSL_RESPONSE_TIMEOUT.getIntNoCheck(properties);
+  }
+
+  /**
+   *
+   * @param sslResponseTimeout ssl response timeout
+   * @see PGProperty#SSL_RESPONSE_TIMEOUT
+   */
+  public void setSslResponseTimeout(int sslResponseTimeout) {
+    PGProperty.SSL_RESPONSE_TIMEOUT.set(properties,sslResponseTimeout);
+  }
+
   /**
    * @return protocol version
    * @see PGProperty#PROTOCOL_VERSION


=====================================
src/main/java/org/postgresql/gss/GssAction.java
=====================================
@@ -24,12 +24,13 @@ import java.security.Principal;
 import java.security.PrivilegedAction;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.security.auth.Subject;
 
-class GssAction implements PrivilegedAction</* @Nullable */ Exception> {
+class GssAction implements PrivilegedAction</* @Nullable */ Exception>, Callable</* @Nullable */ Exception> {
 
   private static final Logger LOGGER = Logger.getLogger(GssAction.class.getName());
   private final PGStream pgStream;
@@ -168,4 +169,9 @@ class GssAction implements PrivilegedAction</* @Nullable */ Exception> {
     }
     return null;
   }
+
+  @Override
+  public /* @Nullable */ Exception call() throws Exception {
+    return run();
+  }
 }


=====================================
src/main/java/org/postgresql/gss/MakeGSS.java
=====================================
@@ -22,7 +22,6 @@ import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.security.PrivilegedAction;
-import java.security.PrivilegedExceptionAction;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.logging.Level;
@@ -74,14 +73,14 @@ public class MakeGSS {
     MethodHandle subjectDoAs = null;
     try {
       subjectDoAs = MethodHandles.lookup().findStatic(Subject.class, "doAs",
-          MethodType.methodType(Object.class, Subject.class, PrivilegedExceptionAction.class));
+          MethodType.methodType(Object.class, Subject.class, PrivilegedAction.class));
     } catch (NoSuchMethodException | IllegalAccessException ignore) {
     }
     SUBJECT_DO_AS = subjectDoAs;
 
     MethodHandle subjectCallAs = null;
     try {
-      subjectDoAs = MethodHandles.lookup().findStatic(Subject.class, "callAs",
+      subjectCallAs = MethodHandles.lookup().findStatic(Subject.class, "callAs",
           MethodType.methodType(Object.class, Subject.class, Callable.class));
     } catch (NoSuchMethodException | IllegalAccessException ignore) {
     }
@@ -93,6 +92,7 @@ public class MakeGSS {
    * {@code Subject.getSubject(AccessController.getContext())} in Java before 18.
    * @return current Subject or null
    */
+  @SuppressWarnings("deprecation")
   private static /* @Nullable */ Subject getCurrentSubject() {
     try {
       if (SUBJECT_CURRENT != null) {
@@ -102,7 +102,7 @@ public class MakeGSS {
         return null;
       }
       return (Subject) SUBJECT_GET_SUBJECT.invoke(
-          ACCESS_CONTROLLER_GET_CONTEXT.invokeExact()
+          ACCESS_CONTROLLER_GET_CONTEXT.invoke()
       );
     } catch (Throwable e) {
       if (e instanceof RuntimeException) {
@@ -163,7 +163,7 @@ public class MakeGSS {
         result = (Exception) SUBJECT_DO_AS.invoke(subject, action);
       } else if (SUBJECT_CALL_AS != null) {
         //noinspection ConstantConditions,unchecked
-        result = (Exception) SUBJECT_CALL_AS.invoke(subject, (Callable<Exception>) action);
+        result = (Exception) SUBJECT_CALL_AS.invoke(subject, action);
       } else {
         throw new PSQLException(
             GT.tr("Neither Subject.doAs (Java before 18) nor Subject.callAs (Java 18+) method found"),


=====================================
src/main/java/org/postgresql/jdbc/PgCallableStatement.java
=====================================
@@ -80,79 +80,79 @@ class PgCallableStatement extends PgPreparedStatement implements CallableStateme
 
   @Override
   public boolean executeWithFlags(int flags) throws SQLException {
-    boolean hasResultSet = super.executeWithFlags(flags);
-    int[] functionReturnType = this.functionReturnType;
-    if (!isFunction || !returnTypeSet || functionReturnType == null) {
-      return hasResultSet;
-    }
-
-    // If we are executing and there are out parameters
-    // callable statement function set the return data
-    if (!hasResultSet) {
-      throw new PSQLException(GT.tr("A CallableStatement was executed with nothing returned."),
-          PSQLState.NO_DATA);
-    }
+    synchronized (this) {
+      boolean hasResultSet = super.executeWithFlags(flags);
+      int[] functionReturnType = this.functionReturnType;
+      if (!isFunction || !returnTypeSet || functionReturnType == null) {
+        return hasResultSet;
+      }
 
-    ResultSet rs = castNonNull(getResultSet());
-    if (!rs.next()) {
-      throw new PSQLException(GT.tr("A CallableStatement was executed with nothing returned."),
-          PSQLState.NO_DATA);
-    }
+      // If we are executing and there are out parameters
+      // callable statement function set the return data
+      if (!hasResultSet) {
+        throw new PSQLException(GT.tr("A CallableStatement was executed with nothing returned."),
+            PSQLState.NO_DATA);
+      }
 
-    // figure out how many columns
-    int cols = rs.getMetaData().getColumnCount();
+      ResultSet rs = castNonNull(getResultSet());
+      if (!rs.next()) {
+        throw new PSQLException(GT.tr("A CallableStatement was executed with nothing returned."),
+            PSQLState.NO_DATA);
+      }
 
-    int outParameterCount = preparedParameters.getOutParameterCount();
+      // figure out how many columns
+      int cols = rs.getMetaData().getColumnCount();
 
-    if (cols != outParameterCount) {
-      throw new PSQLException(
-          GT.tr("A CallableStatement was executed with an invalid number of parameters"),
-          PSQLState.SYNTAX_ERROR);
-    }
+      int outParameterCount = preparedParameters.getOutParameterCount();
 
-    // reset last result fetched (for wasNull)
-    lastIndex = 0;
-
-    // allocate enough space for all possible parameters without regard to in/out
-    /* @Nullable */ Object[] callResult = new Object[preparedParameters.getParameterCount() + 1];
-    this.callResult = callResult;
-
-    // move them into the result set
-    for (int i = 0, j = 0; i < cols; i++, j++) {
-      // find the next out parameter, the assumption is that the functionReturnType
-      // array will be initialized with 0 and only out parameters will have values
-      // other than 0. 0 is the value for java.sql.Types.NULL, which should not
-      // conflict
-      while (j < functionReturnType.length && functionReturnType[j] == 0) {
-        j++;
+      if (cols != outParameterCount) {
+        throw new PSQLException(
+            GT.tr("A CallableStatement was executed with an invalid number of parameters"),
+            PSQLState.SYNTAX_ERROR);
       }
 
-      callResult[j] = rs.getObject(i + 1);
-      int columnType = rs.getMetaData().getColumnType(i + 1);
+      // reset last result fetched (for wasNull)
+      lastIndex = 0;
+
+      // allocate enough space for all possible parameters without regard to in/out
+      /* @Nullable */ Object[] callResult = new Object[preparedParameters.getParameterCount() + 1];
+      this.callResult = callResult;
+
+      // move them into the result set
+      for (int i = 0, j = 0; i < cols; i++, j++) {
+        // find the next out parameter, the assumption is that the functionReturnType
+        // array will be initialized with 0 and only out parameters will have values
+        // other than 0. 0 is the value for java.sql.Types.NULL, which should not
+        // conflict
+        while (j < functionReturnType.length && functionReturnType[j] == 0) {
+          j++;
+        }
 
-      if (columnType != functionReturnType[j]) {
-        // this is here for the sole purpose of passing the cts
-        if (columnType == Types.DOUBLE && functionReturnType[j] == Types.REAL) {
-          // return it as a float
-          Object result = callResult[j];
-          if (result != null) {
-            callResult[j] = ((Double) result).floatValue();
+        callResult[j] = rs.getObject(i + 1);
+        int columnType = rs.getMetaData().getColumnType(i + 1);
+
+        if (columnType != functionReturnType[j]) {
+          // this is here for the sole purpose of passing the cts
+          if (columnType == Types.DOUBLE && functionReturnType[j] == Types.REAL) {
+            // return it as a float
+            Object result = callResult[j];
+            if (result != null) {
+              callResult[j] = ((Double) result).floatValue();
+            }
+          } else if (columnType == Types.REF_CURSOR && functionReturnType[j] == Types.OTHER) {
+            // For backwards compatibility reasons we support that ref cursors can be
+            // registered with both Types.OTHER and Types.REF_CURSOR so we allow
+            // this specific mismatch
+          } else {
+            throw new PSQLException(GT.tr(
+                "A CallableStatement function was executed and the out parameter {0} was of type {1} however type {2} was registered.",
+                i + 1, "java.sql.Types=" + columnType, "java.sql.Types=" + functionReturnType[j]),
+                PSQLState.DATA_TYPE_MISMATCH);
           }
-        } else if (columnType == Types.REF_CURSOR && functionReturnType[j] == Types.OTHER) {
-          // For backwards compatibility reasons we support that ref cursors can be
-          // registered with both Types.OTHER and Types.REF_CURSOR so we allow
-          // this specific mismatch
-        } else {
-          throw new PSQLException(GT.tr(
-              "A CallableStatement function was executed and the out parameter {0} was of type {1} however type {2} was registered.",
-              i + 1, "java.sql.Types=" + columnType, "java.sql.Types=" + functionReturnType[j]),
-              PSQLState.DATA_TYPE_MISMATCH);
         }
-      }
 
-    }
-    rs.close();
-    synchronized (this) {
+      }
+      rs.close();
       result = null;
     }
     return false;


=====================================
src/main/java/org/postgresql/jdbc/PgConnection.java
=====================================
@@ -1454,8 +1454,13 @@ public class PgConnection implements BaseConnection {
           statement.execute("IDENTIFY_SYSTEM");
           statement.close();
         } else {
-          if (checkConnectionQuery == null) {
-            checkConnectionQuery = prepareStatement("");
+          PreparedStatement checkConnectionQuery;
+          synchronized (this) {
+            checkConnectionQuery = this.checkConnectionQuery;
+            if (checkConnectionQuery == null) {
+              checkConnectionQuery = prepareStatement("");
+              this.checkConnectionQuery = checkConnectionQuery;
+            }
           }
           checkConnectionQuery.executeUpdate();
         }


=====================================
src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
=====================================
@@ -2696,11 +2696,11 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
       long longTypOid = typeInfo.intOidToLong(typOid);
       int sqlType = typeInfo.getSQLType(typOid);
 
-      sqlwhen.append(" when oid = ").append(longTypOid).append(" then ").append(sqlType);
+      sqlwhen.append(" when base_type.oid = ").append(longTypOid).append(" then ").append(sqlType);
     }
     sql += sqlwhen.toString();
 
-    sql += " else " + java.sql.Types.OTHER + " end from pg_type where oid=t.typbasetype) "
+    sql += " else " + java.sql.Types.OTHER + " end from pg_type base_type where base_type.oid=t.typbasetype) "
         + "else null end as base_type "
         + "from pg_catalog.pg_type t, pg_catalog.pg_namespace n where t.typnamespace = n.oid and n.nspname != 'pg_catalog' and n.nspname != 'pg_toast'";
 


=====================================
src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
=====================================
@@ -130,11 +130,13 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
    */
   @Override
   public ResultSet executeQuery() throws SQLException {
-    if (!executeWithFlags(0)) {
-      throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA);
-    }
+    synchronized (this) {
+      if (!executeWithFlags(0)) {
+        throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA);
+      }
 
-    return getSingleResultSet();
+      return getSingleResultSet();
+    }
   }
 
   @Override
@@ -146,16 +148,20 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
 
   @Override
   public int executeUpdate() throws SQLException {
-    executeWithFlags(QueryExecutor.QUERY_NO_RESULTS);
-    checkNoResultUpdate();
-    return getUpdateCount();
+    synchronized (this) {
+      executeWithFlags(QueryExecutor.QUERY_NO_RESULTS);
+      checkNoResultUpdate();
+      return getUpdateCount();
+    }
   }
 
   @Override
   public long executeLargeUpdate() throws SQLException {
-    executeWithFlags(QueryExecutor.QUERY_NO_RESULTS);
-    checkNoResultUpdate();
-    return getLargeUpdateCount();
+    synchronized (this) {
+      executeWithFlags(QueryExecutor.QUERY_NO_RESULTS);
+      checkNoResultUpdate();
+      return getLargeUpdateCount();
+    }
   }
 
   @Override
@@ -167,20 +173,22 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
 
   @Override
   public boolean execute() throws SQLException {
-    return executeWithFlags(0);
+    synchronized (this) {
+      return executeWithFlags(0);
+    }
   }
 
   public boolean executeWithFlags(int flags) throws SQLException {
     try {
-      checkClosed();
+      synchronized (this) {
+        checkClosed();
 
-      if (connection.getPreferQueryMode() == PreferQueryMode.SIMPLE) {
-        flags |= QueryExecutor.QUERY_EXECUTE_AS_SIMPLE;
-      }
+        if (connection.getPreferQueryMode() == PreferQueryMode.SIMPLE) {
+          flags |= QueryExecutor.QUERY_EXECUTE_AS_SIMPLE;
+        }
 
-      execute(preparedQuery, preparedParameters, flags);
+        execute(preparedQuery, preparedParameters, flags);
 
-      synchronized (this) {
         checkClosed();
         return (result != null && result.getResultSet() != null);
       }
@@ -731,18 +739,31 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
     }
   }
 
+  private Class<?> getArrayType(Class<?> type) {
+    Class<?> subType = type.getComponentType();
+    while (subType != null) {
+      type = subType;
+      subType = type.getComponentType();
+    }
+    return type;
+  }
+
   private <A extends /* @NonNull */ Object> void setObjectArray(int parameterIndex, A in) throws SQLException {
     final ArrayEncoding.ArrayEncoder<A> arraySupport = ArrayEncoding.getArrayEncoder(in);
 
     final TypeInfo typeInfo = connection.getTypeInfo();
 
-    final int oid = arraySupport.getDefaultArrayTypeOid();
+    int oid = arraySupport.getDefaultArrayTypeOid();
 
     if (arraySupport.supportBinaryRepresentation(oid) && connection.getPreferQueryMode() != PreferQueryMode.SIMPLE) {
       bindBytes(parameterIndex, arraySupport.toBinaryRepresentation(connection, in, oid), oid);
     } else {
       if (oid == Oid.UNSPECIFIED) {
-        throw new SQLFeatureNotSupportedException();
+        Class<?> arrayType = getArrayType(in.getClass());
+        oid = typeInfo.getJavaArrayType(arrayType.getName());
+        if (oid == Oid.UNSPECIFIED) {
+          throw new SQLFeatureNotSupportedException();
+        }
       }
       final int baseOid = typeInfo.getPGArrayElement(oid);
       final String baseType = castNonNull(typeInfo.getPGType(baseOid));


=====================================
src/main/java/org/postgresql/jdbc/PgStatement.java
=====================================
@@ -240,11 +240,13 @@ public class PgStatement implements Statement, BaseStatement {
 
   @Override
   public ResultSet executeQuery(String sql) throws SQLException {
-    if (!executeWithFlags(sql, 0)) {
-      throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA);
-    }
+    synchronized (this) {
+      if (!executeWithFlags(sql, 0)) {
+        throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA);
+      }
 
-    return getSingleResultSet();
+      return getSingleResultSet();
+    }
   }
 
   protected ResultSet getSingleResultSet() throws SQLException {
@@ -262,9 +264,11 @@ public class PgStatement implements Statement, BaseStatement {
 
   @Override
   public int executeUpdate(String sql) throws SQLException {
-    executeWithFlags(sql, QueryExecutor.QUERY_NO_RESULTS);
-    checkNoResultUpdate();
-    return getUpdateCount();
+    synchronized (this) {
+      executeWithFlags(sql, QueryExecutor.QUERY_NO_RESULTS);
+      checkNoResultUpdate();
+      return getUpdateCount();
+    }
   }
 
   protected final void checkNoResultUpdate() throws SQLException {
@@ -404,17 +408,19 @@ public class PgStatement implements Statement, BaseStatement {
   protected final void execute(CachedQuery cachedQuery,
       /* @Nullable */ ParameterList queryParameters, int flags)
       throws SQLException {
-    try {
-      executeInternal(cachedQuery, queryParameters, flags);
-    } catch (SQLException e) {
-      // Don't retry composite queries as it might get partially executed
-      if (cachedQuery.query.getSubqueries() != null
-          || !connection.getQueryExecutor().willHealOnRetry(e)) {
-        throw e;
+    synchronized (this) {
+      try {
+        executeInternal(cachedQuery, queryParameters, flags);
+      } catch (SQLException e) {
+        // Don't retry composite queries as it might get partially executed
+        if (cachedQuery.query.getSubqueries() != null
+            || !connection.getQueryExecutor().willHealOnRetry(e)) {
+          throw e;
+        }
+        cachedQuery.query.close();
+        // Execute the query one more time
+        executeInternal(cachedQuery, queryParameters, flags);
       }
-      cachedQuery.query.close();
-      // Execute the query one more time
-      executeInternal(cachedQuery, queryParameters, flags);
     }
   }
 
@@ -514,7 +520,10 @@ public class PgStatement implements Statement, BaseStatement {
     // No-op.
   }
 
-  private volatile boolean isClosed = false;
+  private volatile int isClosed = 0;
+  private static final AtomicIntegerFieldUpdater<PgStatement> IS_CLOSED_UPDATER =
+      AtomicIntegerFieldUpdater.newUpdater(
+          PgStatement.class, "isClosed");
 
   @Override
   public int getUpdateCount() throws SQLException {
@@ -667,11 +676,8 @@ public class PgStatement implements Statement, BaseStatement {
    */
   public final void close() throws SQLException {
     // closing an already closed Statement is a no-op.
-    synchronized (this) {
-      if (isClosed) {
-        return;
-      }
-      isClosed = true;
+    if (!IS_CLOSED_UPDATER.compareAndSet(this, 0, 1)) {
+      return;
     }
 
     cancel();
@@ -1090,9 +1096,11 @@ public class PgStatement implements Statement, BaseStatement {
 
   @Override
   public long executeLargeUpdate(String sql) throws SQLException {
-    executeWithFlags(sql, QueryExecutor.QUERY_NO_RESULTS);
-    checkNoResultUpdate();
-    return getLargeUpdateCount();
+    synchronized (this) {
+      executeWithFlags(sql, QueryExecutor.QUERY_NO_RESULTS);
+      checkNoResultUpdate();
+      return getLargeUpdateCount();
+    }
   }
 
   @Override
@@ -1116,19 +1124,21 @@ public class PgStatement implements Statement, BaseStatement {
 
   @Override
   public long executeLargeUpdate(String sql, String /* @Nullable */ [] columnNames) throws SQLException {
-    if (columnNames != null && columnNames.length == 0) {
-      return executeLargeUpdate(sql);
-    }
+    synchronized (this) {
+      if (columnNames != null && columnNames.length == 0) {
+        return executeLargeUpdate(sql);
+      }
 
-    wantsGeneratedKeysOnce = true;
-    if (!executeCachedSql(sql, 0, columnNames)) {
-      // no resultset returned. What's a pity!
+      wantsGeneratedKeysOnce = true;
+      if (!executeCachedSql(sql, 0, columnNames)) {
+        // no resultset returned. What's a pity!
+      }
+      return getLargeUpdateCount();
     }
-    return getLargeUpdateCount();
   }
 
   public boolean isClosed() throws SQLException {
-    return isClosed;
+    return isClosed == 1;
   }
 
   public void setPoolable(boolean poolable) throws SQLException {
@@ -1240,15 +1250,17 @@ public class PgStatement implements Statement, BaseStatement {
   }
 
   public int executeUpdate(String sql, String /* @Nullable */ [] columnNames) throws SQLException {
-    if (columnNames != null && columnNames.length == 0) {
-      return executeUpdate(sql);
-    }
+    synchronized (this) {
+      if (columnNames != null && columnNames.length == 0) {
+        return executeUpdate(sql);
+      }
 
-    wantsGeneratedKeysOnce = true;
-    if (!executeCachedSql(sql, 0, columnNames)) {
-      // no resultset returned. What's a pity!
+      wantsGeneratedKeysOnce = true;
+      if (!executeCachedSql(sql, 0, columnNames)) {
+        // no resultset returned. What's a pity!
+      }
+      return getUpdateCount();
     }
-    return getUpdateCount();
   }
 
   public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
@@ -1268,12 +1280,14 @@ public class PgStatement implements Statement, BaseStatement {
   }
 
   public boolean execute(String sql, String /* @Nullable */ [] columnNames) throws SQLException {
-    if (columnNames != null && columnNames.length == 0) {
-      return execute(sql);
-    }
+    synchronized (this) {
+      if (columnNames != null && columnNames.length == 0) {
+        return execute(sql);
+      }
 
-    wantsGeneratedKeysOnce = true;
-    return executeCachedSql(sql, 0, columnNames);
+      wantsGeneratedKeysOnce = true;
+      return executeCachedSql(sql, 0, columnNames);
+    }
   }
 
   public int getResultSetHoldability() throws SQLException {


=====================================
src/main/java/org/postgresql/jdbc/TypeInfoCache.java
=====================================
@@ -52,6 +52,8 @@ public class TypeInfoCache implements TypeInfo {
   // pgname (String) -> oid (Integer)
   private Map<String, Integer> pgNameToOid;
 
+  private Map<String, Integer> javaArrayTypeToOid;
+
   // pgname (String) -> extension pgobject (Class)
   private Map<String, Class<? extends PGobject>> pgNameToPgObject;
 
@@ -140,6 +142,7 @@ public class TypeInfoCache implements TypeInfo {
     this.unknownLength = unknownLength;
     oidToPgName = new HashMap<Integer, String>((int) Math.round(types.length * 1.5));
     pgNameToOid = new HashMap<String, Integer>((int) Math.round(types.length * 1.5));
+    javaArrayTypeToOid = new HashMap<String, Integer>((int) Math.round(types.length * 1.5));
     pgNameToJavaClass = new HashMap<String, String>((int) Math.round(types.length * 1.5));
     pgNameToPgObject = new HashMap<String, Class<? extends PGobject>>((int) Math.round(types.length * 1.5));
     pgArrayToPgType = new HashMap<Integer, Integer>((int) Math.round(types.length * 1.5));
@@ -168,6 +171,7 @@ public class TypeInfoCache implements TypeInfo {
     pgNameToJavaClass.put(pgTypeName, javaClass);
     pgNameToOid.put(pgTypeName, oid);
     oidToPgName.put(oid, pgTypeName);
+    javaArrayTypeToOid.put(javaClass, arrayOid);
     pgArrayToPgType.put(arrayOid, oid);
     pgNameToSQLType.put(pgTypeName, sqlType);
     oidToSQLType.put(oid, sqlType);
@@ -319,6 +323,15 @@ public class TypeInfoCache implements TypeInfo {
     return i;
   }
 
+  @Override
+  public synchronized int getJavaArrayType(String className) throws SQLException {
+    Integer oid = javaArrayTypeToOid.get(className);
+    if (oid == null) {
+      return Oid.UNSPECIFIED;
+    }
+    return oid;
+  }
+
   public synchronized int getSQLType(int typeOid) throws SQLException {
     if (typeOid == Oid.UNSPECIFIED) {
       return Types.OTHER;


=====================================
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.4.1";
+  public static final String DRIVER_VERSION = "42.4.2";
   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 = 4;
-  public static final int PATCH_VERSION = 1;
+  public static final int PATCH_VERSION = 2;
 
   // 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.4.1
+Implementation-Version: 42.4.2
 Specification-Vendor: Oracle Corporation
 Specification-Title: JDBC
 Implementation-Vendor-Id: org.postgresql


=====================================
src/test/java/org/postgresql/jdbc/UUIDArrayTest.java
=====================================
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2022, 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.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import org.postgresql.core.ServerVersion;
+import org.postgresql.test.TestUtil;
+
+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.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.UUID;
+
+class UUIDArrayTest {
+
+  private static Connection con;
+  private static final String TABLE_NAME = "uuid_table";
+  private static final String INSERT1 = "INSERT INTO " + TABLE_NAME
+      + " (id, data1) VALUES (?, ?)";
+  private static final String INSERT2 = "INSERT INTO " + TABLE_NAME
+      + " (id, data2) VALUES (?, ?)";
+  private static final String SELECT1 = "SELECT data1 FROM " + TABLE_NAME
+      + " WHERE id = ?";
+  private static final String SELECT2 = "SELECT data2 FROM " + TABLE_NAME
+      + " WHERE id = ?";
+  private static final UUID[] uids1 = new UUID[]{UUID.randomUUID(), UUID.randomUUID()};
+  private static final UUID[][] uids2 = new UUID[][]{uids1};
+
+  @BeforeAll
+  public static void setUp() throws Exception {
+    con = TestUtil.openDB();
+    assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_6));
+    try (Statement stmt = con.createStatement()) {
+      stmt.execute("CREATE TABLE " + TABLE_NAME
+          + " (id int PRIMARY KEY, data1 UUID[], data2 UUID[][])");
+    }
+  }
+
+  @AfterAll
+  public static void tearDown() throws Exception {
+    try (Statement stmt = con.createStatement()) {
+      stmt.execute("DROP TABLE IF EXISTS " + TABLE_NAME);
+    }
+    TestUtil.closeDB(con);
+  }
+
+  @Test
+  void test1DWithCreateArrayOf() throws SQLException {
+    try (Connection c = assertDoesNotThrow(() -> TestUtil.openDB());
+        PreparedStatement stmt1 = c.prepareStatement(INSERT1);
+        PreparedStatement stmt2 = c.prepareStatement(SELECT1)) {
+      stmt1.setInt(1, 100);
+      stmt1.setArray(2, c.createArrayOf("uuid", uids1));
+      stmt1.execute();
+
+      stmt2.setInt(1, 100);
+      stmt2.execute();
+      try (ResultSet rs = stmt2.getResultSet()) {
+        assertTrue(rs.next());
+        UUID[] array = (UUID[])rs.getArray(1).getArray();
+        assertEquals(uids1[0], array[0]);
+        assertEquals(uids1[1], array[1]);
+      }
+    }
+  }
+
+  @Test
+  void test1DWithSetObject() throws SQLException {
+    try (Connection c = assertDoesNotThrow(() -> TestUtil.openDB());
+         PreparedStatement stmt1 = c.prepareStatement(INSERT1);
+         PreparedStatement stmt2 = c.prepareStatement(SELECT1)) {
+      stmt1.setInt(1, 101);
+      stmt1.setObject(2, uids1);
+      stmt1.execute();
+
+      stmt2.setInt(1, 101);
+      stmt2.execute();
+      try (ResultSet rs = stmt2.getResultSet()) {
+        assertTrue(rs.next());
+        UUID[] array = (UUID[])rs.getArray(1).getArray();
+        assertEquals(uids1[0], array[0]);
+        assertEquals(uids1[1], array[1]);
+      }
+    }
+  }
+
+  @Test
+  void test2DWithCreateArrayOf() throws SQLException {
+    try (Connection c = assertDoesNotThrow(() -> TestUtil.openDB());
+         PreparedStatement stmt1 = c.prepareStatement(INSERT2);
+         PreparedStatement stmt2 = c.prepareStatement(SELECT2)) {
+      stmt1.setInt(1, 200);
+      stmt1.setArray(2, c.createArrayOf("uuid", uids2));
+      stmt1.execute();
+
+      stmt2.setInt(1, 200);
+      stmt2.execute();
+      try (ResultSet rs = stmt2.getResultSet()) {
+        assertTrue(rs.next());
+        UUID[][] array = (UUID[][])rs.getArray(1).getArray();
+        assertEquals(uids2[0][0], array[0][0]);
+        assertEquals(uids2[0][1], array[0][1]);
+      }
+    }
+  }
+
+  @Test
+  void test2DWithSetObject() throws SQLException {
+    try (Connection c = assertDoesNotThrow(() -> TestUtil.openDB());
+         PreparedStatement stmt1 = c.prepareStatement(INSERT2);
+         PreparedStatement stmt2 = c.prepareStatement(SELECT2)) {
+      stmt1.setInt(1, 201);
+      stmt1.setObject(2, uids2);
+      stmt1.execute();
+
+      stmt2.setInt(1, 201);
+      stmt2.execute();
+      try (ResultSet rs = stmt2.getResultSet()) {
+        assertTrue(rs.next());
+        UUID[][] array = (UUID[][])rs.getArray(1).getArray();
+        assertEquals(uids2[0][0], array[0][0]);
+        assertEquals(uids2[0][1], array[0][1]);
+      }
+    }
+  }
+}


=====================================
src/test/java/org/postgresql/test/jdbc2/StatementTest.java
=====================================
@@ -32,7 +32,9 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLWarning;
 import java.sql.Statement;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Random;
@@ -861,7 +863,7 @@ public class StatementTest {
   public void testCancelQueryWithBrokenNetwork() throws SQLException, IOException, InterruptedException {
     // check that stmt.cancel() doesn't hang forever if the network is broken
 
-    ExecutorService executor = Executors.newSingleThreadExecutor();
+    ExecutorService executor = Executors.newCachedThreadPool();
 
     try (StrangeProxyServer proxyServer = new StrangeProxyServer(TestUtil.getServer(), TestUtil.getPort())) {
       Properties props = new Properties();
@@ -875,6 +877,9 @@ public class StatementTest {
         proxyServer.stopForwardingAllClients();
 
         stmt.cancel();
+        // Note: network is still inaccessible, so the statement execution is still in progress.
+        // So we abort the connection to allow implicit conn.close()
+        conn.abort(executor);
       }
     }
 
@@ -939,6 +944,51 @@ public class StatementTest {
     }
   }
 
+  @Test(timeout = 10000)
+  public void testConcurrentIsValid() throws Throwable {
+    ExecutorService executor = Executors.newCachedThreadPool();
+    try {
+      List<Future<?>> results = new ArrayList<>();
+      Random rnd = new Random();
+      for (int i = 0; i < 10; i++) {
+        Future<?> future = executor.submit(() -> {
+          try {
+            for (int j = 0; j < 50; j++) {
+              con.isValid(1);
+              try (PreparedStatement ps =
+                       con.prepareStatement("select * from generate_series(1,?) as x(id)")) {
+                int limit = rnd.nextInt(10);
+                ps.setInt(1, limit);
+                try (ResultSet r = ps.executeQuery()) {
+                  int cnt = 0;
+                  String callName = "generate_series(1, " + limit + ") in thread "
+                      + Thread.currentThread().getName();
+                  while (r.next()) {
+                    cnt++;
+                    assertEquals(callName + ", row " + cnt, cnt, r.getInt(1));
+                  }
+                  assertEquals(callName + " number of rows", limit, cnt);
+                }
+              }
+            }
+          } catch (SQLException e) {
+            throw new RuntimeException(e);
+          }
+        });
+        results.add(future);
+      }
+      for (Future<?> result : results) {
+        // Propagate exception if any
+        result.get();
+      }
+    } catch (ExecutionException e) {
+      throw e.getCause();
+    } finally {
+      executor.shutdown();
+      executor.awaitTermination(10, TimeUnit.SECONDS);
+    }
+  }
+
   @Test(timeout = 20000)
   public void testFastCloses() throws SQLException {
     ExecutorService executor = Executors.newSingleThreadExecutor();



View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/commit/732b021aa0607b46b32a6502be189e3b57ea831d

-- 
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/commit/732b021aa0607b46b32a6502be189e3b57ea831d
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/20220822/03905e4e/attachment.htm>


More information about the pkg-java-commits mailing list