Repository: pgjdbc/pgjdbc
Pull request #1605 (2019-11-12 17:07:28)

fix: get correct column length for simple domains
fixes issue #1604 

AppVeyorBot: :x: [Build pgjdbc 1.0.500 failed](https://ci.appveyor.com/project/davecramer/pgjdbc/builds/28806223) (commit https://github.com/pgjdbc/pgjdbc/commit/762c19a087 by @davecramer)
AppVeyorBot: :white_check_mark: [Build pgjdbc 1.0.501 completed](https://ci.appveyor.com/project/davecramer/pgjdbc/builds/28807721) (commit https://github.com/pgjdbc/pgjdbc/commit/1c4236ab25 by @davecramer)
codecov-io: # [Codecov](https://codecov.io/gh/pgjdbc/pgjdbc/pull/1605?src=pr&el=h1) Report
> Merging [#1605](https://codecov.io/gh/pgjdbc/pgjdbc/pull/1605?src=pr&el=desc) into [master](https://codecov.io/gh/pgjdbc/pgjdbc/commit/5e48eaa4c9f6fc07904944bd98ad45fbb4aefd10?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `83.33%`.



```diff
@@             Coverage Diff              @@
##             master    #1605      +/-   ##
============================================
- Coverage     68.96%   68.94%   -0.02%     
  Complexity     3998     3998              
============================================
  Files           179      179              
  Lines         16624    16631       +7     
  Branches       2708     2710       +2     
============================================
+ Hits          11465    11467       +2     
- Misses         3904     3907       +3     
- Partials       1255     1257       +2
```


Patch:
diff --git a/docs/documentation/head/connect.md b/docs/documentation/head/connect.md
index c8f167e6..e6df9ab2 100644
--- a/docs/documentation/head/connect.md
+++ b/docs/documentation/head/connect.md
@@ -134,6 +134,12 @@ Connection conn = DriverManager.getConnection(url);
 	
 	`openssl pkcs8 -topk8 -inform PEM -in my.key -outform DER -out my.key.der -v1 PBE-MD5-DES`
 
+    *Note:* The use of -v1 PBE-MD5-DES might be inadequate in environments where high level of security is needed and the key is not protected
+    by other means (e.g. access control of the OS), or the key file is transmitted in untrusted channels.
+    We are depending on the cryptography providers provided by the java runtime. The solution documented here is known to work at
+    the time of writing. If you have stricter security needs, please see https://stackoverflow.com/questions/58488774/configure-tomcat-hibernate-to-have-a-cryptographic-provider-supporting-1-2-840-1
+    for a discussion of the problem and information on choosing a better cipher suite.
+
 * **sslrootcert** = String
 
 	File name of the SSL root certificate. Defaults to defaultdir/root.crt
@@ -467,6 +473,32 @@ Connection conn = DriverManager.getConnection(url);
    `assumeMinServerVersion` with parameter >= 9.4 (backend >= 9.4)</p>
     
     
+<a name="unix sockets"></a>
+## Unix sockets
+
+Aleksander Blomskøld has forked junixsocket and added a [Unix SocketFactory](https://github.com/fiken/junixsocket/blob/master/junixsocket-common/src/main/java/org/newsclub/net/unix/socketfactory/PostgresqlAFUNIXSocketFactory.java) that works with the driver.
+His code can be found at [https://github.com/fiken/junixsocket](https://github.com/fiken/junixsocket).
+
+Dependencies for junixsocket are :
+
+```xml
+<dependency>
+  <groupId>no.fiken.oss.junixsocket</groupId>
+  <artifactId>junixsocket-common</artifactId>
+  <version>1.0.2</version>
+</dependency>
+<dependency>
+  <groupId>no.fiken.oss.junixsocket</groupId>
+  <artifactId>junixsocket-native-common</artifactId>
+  <version>1.0.2</version>
+</dependency>
+```
+Simply add
+`?socketFactory=org.newsclub.net.unix.socketfactory.PostgresqlAFUNIXSocketFactory&socketFactoryArg=[path-to-the-unix-socket]`
+to the connection URL.
+
+For many distros the default path is /var/run/postgresql/.s.PGSQL.5432
+
 <a name="connection-failover"></a>
 ## Connection Fail-over
 
diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
index b342b44c..9e99dc7a 100644
--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java
+++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
@@ -172,6 +172,12 @@ public enum PGProperty {
   LOG_UNCLOSED_CONNECTIONS("logUnclosedConnections", "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"),
 
+  /**
+   * Whether to include full server error detail in exception messages.
+   */
+  LOG_SERVER_ERROR_DETAIL("logServerErrorDetail", "true",
+      "Include full server error detail in exception messages. If disabled then only the error itself will be included."),
+
   /**
    * Enable optimization that disables column name sanitiser.
    */
diff --git a/pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java b/pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java
index ce5459c8..520c645e 100644
--- a/pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java
+++ b/pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java
@@ -101,17 +101,20 @@ public class PGCopyOutputStream extends OutputStream implements CopyIn {
       return;
     }
 
-    try {
-      endCopy();
-    } catch (SQLException se) {
-      IOException ioe = new IOException("Ending write to copy failed.");
-      ioe.initCause(se);
-      throw ioe;
+    if (op.isActive()) {
+      try {
+        endCopy();
+      } catch (SQLException se) {
+        IOException ioe = new IOException("Ending write to copy failed.");
+        ioe.initCause(se);
+        throw ioe;
+      }
     }
     op = null;
   }
 
   public void flush() throws IOException {
+    checkClosed();
     try {
       op.writeToCopy(copyBuffer, 0, at);
       at = 0;
@@ -154,7 +157,7 @@ public class PGCopyOutputStream extends OutputStream implements CopyIn {
   }
 
   public boolean isActive() {
-    return op.isActive();
+    return op != null && op.isActive();
   }
 
   public void flushCopy() throws SQLException {
diff --git a/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java b/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java
index fbe5c498..1a74a7f7 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java
@@ -45,6 +45,7 @@ public abstract class QueryExecutorBase implements QueryExecutor {
   private final PreferQueryMode preferQueryMode;
   private AutoSave autoSave;
   private boolean flushCacheOnDeallocate = true;
+  protected final boolean logServerErrorDetail;
 
   // default value for server versions that don't report standard_conforming_strings
   private boolean standardConformingStrings = false;
@@ -70,6 +71,7 @@ public abstract class QueryExecutorBase implements QueryExecutor {
     String preferMode = PGProperty.PREFER_QUERY_MODE.get(info);
     this.preferQueryMode = PreferQueryMode.of(preferMode);
     this.autoSave = AutoSave.of(PGProperty.AUTOSAVE.get(info));
+    this.logServerErrorDetail = PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info);
     this.cachedQueryCreateAction = new CachedQueryCreateAction(this);
     statementCache = new LruCache<Object, CachedQuery>(
         Math.max(0, PGProperty.PREPARED_STATEMENT_CACHE_QUERIES.getInt(info)),
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
index 662cc263..1efcd527 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
@@ -517,7 +517,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
             ServerErrorMessage errorMsg =
                 new ServerErrorMessage(pgStream.receiveErrorString(elen - 4));
             LOGGER.log(Level.FINEST, " <=BE ErrorMessage({0})", errorMsg);
-            throw new PSQLException(errorMsg);
+            throw new PSQLException(errorMsg, PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info));
 
           case 'R':
             // Authentication request.
@@ -647,7 +647,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
                   org.postgresql.gss.MakeGSS.authenticate(pgStream, host, user, password,
                       PGProperty.JAAS_APPLICATION_NAME.get(info),
                       PGProperty.KERBEROS_SERVER_NAME.get(info), usespnego,
-                      PGProperty.JAAS_LOGIN.getBoolean(info));
+                      PGProperty.JAAS_LOGIN.getBoolean(info),
+                      PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info));
                 }
                 break;
 
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
index 13326c98..48ca673b 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
@@ -71,6 +71,7 @@ import java.util.logging.Logger;
 public class QueryExecutorImpl extends QueryExecutorBase {
 
   private static final Logger LOGGER = Logger.getLogger(QueryExecutorImpl.class.getName());
+  private static final String COPY_ERROR_MESSAGE = "COPY commands are only supported using the CopyManager API.";
 
   /**
    * TimeZone of the current connection (TimeZone backend parameter).
@@ -2352,8 +2353,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           // We'll send a CopyFail message for COPY FROM STDIN so that
           // server does not wait for the data.
 
-          byte[] buf =
-              Utils.encodeUTF8("The JDBC driver currently does not support COPY operations.");
+          byte[] buf = Utils.encodeUTF8(COPY_ERROR_MESSAGE);
           pgStream.sendChar('f');
           pgStream.sendInteger4(buf.length + 4 + 1);
           pgStream.send(buf);
@@ -2370,7 +2370,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           // In case of CopyOutResponse, we cannot abort data transfer,
           // so just throw an error and ignore CopyData messages
           handler.handleError(
-              new PSQLException(GT.tr("The driver currently does not support COPY operations."),
+              new PSQLException(GT.tr(COPY_ERROR_MESSAGE),
                   PSQLState.NOT_IMPLEMENTED));
           break;
 
@@ -2500,7 +2500,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
       LOGGER.log(Level.FINEST, " <=BE ErrorMessage({0})", errorMsg.toString());
     }
 
-    PSQLException error = new PSQLException(errorMsg);
+    PSQLException error = new PSQLException(errorMsg, this.logServerErrorDetail);
     if (transactionFailCause == null) {
       transactionFailCause = error;
     } else {
diff --git a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
index 64e9f04b..f4be8197 100644
--- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
+++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
@@ -928,6 +928,22 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     PGProperty.LOG_UNCLOSED_CONNECTIONS.set(properties, enabled);
   }
 
+  /**
+   * @return true if driver should log include detail in server error messages
+   * @see PGProperty#LOG_SERVER_ERROR_DETAIL
+   */
+  public boolean getLogServerErrorDetail() {
+    return PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(properties);
+  }
+
+  /**
+   * @param enabled true if driver should include detail in server error messages
+   * @see PGProperty#LOG_SERVER_ERROR_DETAIL
+   */
+  public void setLogServerErrorDetail(boolean enabled) {
+    PGProperty.LOG_SERVER_ERROR_DETAIL.set(properties, enabled);
+  }
+
   /**
    * @return assumed minimal server version
    * @see PGProperty#ASSUME_MIN_SERVER_VERSION
diff --git a/pgjdbc/src/main/java/org/postgresql/gss/GssAction.java b/pgjdbc/src/main/java/org/postgresql/gss/GssAction.java
index fa27e30d..f747fd4a 100644
--- a/pgjdbc/src/main/java/org/postgresql/gss/GssAction.java
+++ b/pgjdbc/src/main/java/org/postgresql/gss/GssAction.java
@@ -32,15 +32,17 @@ class GssAction implements PrivilegedAction<Exception> {
   private final String kerberosServerName;
   private final boolean useSpnego;
   private final GSSCredential clientCredentials;
+  private final boolean logServerErrorDetail;
 
   GssAction(PGStream pgStream, GSSCredential clientCredentials, String host, String user,
-      String kerberosServerName, boolean useSpnego) {
+      String kerberosServerName, boolean useSpnego, boolean logServerErrorDetail) {
     this.pgStream = pgStream;
     this.clientCredentials = clientCredentials;
     this.host = host;
     this.user = user;
     this.kerberosServerName = kerberosServerName;
     this.useSpnego = useSpnego;
+    this.logServerErrorDetail = logServerErrorDetail;
   }
 
   private static boolean hasSpnegoSupport(GSSManager manager) throws GSSException {
@@ -111,7 +113,7 @@ class GssAction implements PrivilegedAction<Exception> {
 
               LOGGER.log(Level.FINEST, " <=BE ErrorMessage({0})", errorMsg);
 
-              return new PSQLException(errorMsg);
+              return new PSQLException(errorMsg, logServerErrorDetail);
             case 'R':
               LOGGER.log(Level.FINEST, " <=BE AuthenticationGSSContinue");
               int len = pgStream.receiveInteger4();
diff --git a/pgjdbc/src/main/java/org/postgresql/gss/MakeGSS.java b/pgjdbc/src/main/java/org/postgresql/gss/MakeGSS.java
index 84c2f35f..b3b856b4 100644
--- a/pgjdbc/src/main/java/org/postgresql/gss/MakeGSS.java
+++ b/pgjdbc/src/main/java/org/postgresql/gss/MakeGSS.java
@@ -28,7 +28,8 @@ public class MakeGSS {
   private static final Logger LOGGER = Logger.getLogger(MakeGSS.class.getName());
 
   public static void authenticate(PGStream pgStream, String host, String user, String password,
-      String jaasApplicationName, String kerberosServerName, boolean useSpnego, boolean jaasLogin)
+      String jaasApplicationName, String kerberosServerName, boolean useSpnego, boolean jaasLogin,
+      boolean logServerErrorDetail)
           throws IOException, SQLException {
     LOGGER.log(Level.FINEST, " <=BE AuthenticationReqGSS");
 
@@ -58,7 +59,7 @@ public class MakeGSS {
         sub = lc.getSubject();
       }
       PrivilegedAction<Exception> action = new GssAction(pgStream, gssCredential, host, user,
-          kerberosServerName, useSpnego);
+          kerberosServerName, useSpnego, logServerErrorDetail);
 
       result = Subject.doAs(sub, action);
     } catch (Exception e) {
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
index 56d13506..26440aec 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
@@ -9,11 +9,13 @@ import org.postgresql.core.BaseStatement;
 import org.postgresql.core.Field;
 import org.postgresql.core.Oid;
 import org.postgresql.core.ServerVersion;
+import org.postgresql.util.ByteConverter;
 import org.postgresql.util.GT;
 import org.postgresql.util.JdbcBlackHole;
 import org.postgresql.util.PSQLException;
 import org.postgresql.util.PSQLState;
 
+import java.math.BigInteger;
 import java.sql.Array;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
@@ -24,6 +26,9 @@ import java.sql.Statement;
 import java.sql.Types;
 import java.util.ArrayList;
 import java.util.Arrays;
+
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -1497,7 +1502,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
     }
 
     sql += "SELECT n.nspname,c.relname,a.attname,a.atttypid,a.attnotnull "
-           + "OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull,a.atttypmod,a.attlen,";
+           + "OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull,a.atttypmod,a.attlen,t.typtypmod,";
 
     if (connection.haveMinimumServerVersion(ServerVersion.v8_4)) {
       sql += "row_number() OVER (PARTITION BY a.attrelid ORDER BY a.attnum) AS attnum, ";
@@ -1580,12 +1585,37 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
       }
       String identity = rs.getString("attidentity");
 
-      int decimalDigits = connection.getTypeInfo().getScale(typeOid, typeMod);
-      int columnSize = connection.getTypeInfo().getPrecision(typeOid, typeMod);
-      if (columnSize == 0) {
-        columnSize = connection.getTypeInfo().getDisplaySize(typeOid, typeMod);
-      }
+      int baseTypeOid = (int) rs.getLong("typbasetype");
 
+      int decimalDigits;
+      int columnSize;
+
+      /* this is really a DOMAIN type not sure where DISTINCT came from */
+      if ( sqlType == Types.DISTINCT ) {
+        /*
+        From the docs if typtypmod is -1
+         */
+        int domainLength = rs.getInt("typtypmod");
+        decimalDigits = connection.getTypeInfo().getScale(baseTypeOid, typeMod);
+        /*
+        From the postgres docs:
+        Domains use typtypmod to record the typmod to be applied to their
+        base type (-1 if base type does not use a typmod). -1 if this type is not a domain.
+        if it is -1 then get the precision from the basetype. This doesn't help if the basetype is
+        a domain, but for actual types this will return the correct value.
+         */
+        if ( domainLength == -1 ) {
+          columnSize = connection.getTypeInfo().getPrecision(baseTypeOid, typeMod);
+        } else {
+          columnSize = domainLength;
+        }
+      } else {
+        decimalDigits = connection.getTypeInfo().getScale(typeOid, typeMod);
+        columnSize = connection.getTypeInfo().getPrecision(typeOid, typeMod);
+        if (columnSize == 0) {
+          columnSize = connection.getTypeInfo().getDisplaySize(typeOid, typeMod);
+        }
+      }
       tuple[6] = connection.encodeString(Integer.toString(columnSize));
       tuple[8] = connection.encodeString(Integer.toString(decimalDigits));
 
@@ -1607,8 +1637,6 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
       // Is nullable
       tuple[17] = connection.encodeString(rs.getBoolean("attnotnull") ? "NO" : "YES");
 
-      int baseTypeOid = (int) rs.getLong("typbasetype");
-
       tuple[18] = null; // SCOPE_CATLOG
       tuple[19] = null; // SCOPE_SCHEMA
       tuple[20] = null; // SCOPE_TABLE
@@ -2247,7 +2275,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
               connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.typeSearchable));
 
     while (rs.next()) {
-      byte[][] tuple = new byte[18][];
+      byte[][] tuple = new byte[19][];
       String typname = rs.getString(1);
       int typeOid = (int) rs.getLong(2);
 
@@ -2255,6 +2283,10 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
       int sqlType = connection.getTypeInfo().getSQLType(typname);
       tuple[1] =
           connection.encodeString(Integer.toString(sqlType));
+
+      /* this is just for sorting below, the result set never sees this */
+      tuple[18] = BigInteger.valueOf(sqlType).toByteArray();
+
       tuple[2] = connection
           .encodeString(Integer.toString(connection.getTypeInfo().getMaximumPrecision(typeOid)));
 
@@ -2300,6 +2332,14 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
     rs.close();
     stmt.close();
 
+    Collections.sort(v, new Comparator<byte[][]>() {
+      @Override
+      public int compare(byte[][] o1, byte[][] o2) {
+        int i1 = ByteConverter.bytesToInt(o1[18]);
+        int i2 = ByteConverter.bytesToInt(o2[18]);
+        return (i1 < i2) ? -1 : ((i1 == i2) ? 0 : 1);
+      }
+    });
     return ((BaseStatement) createMetaDataStatement()).createDriverResultSet(f, v);
   }
 
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java
index 3cf1277f..919df065 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java
@@ -175,6 +175,7 @@ public class PgSQLXML implements SQLXML {
   public synchronized Writer setCharacterStream() throws SQLException {
     checkFreed();
     initialize();
+    active = true;
     stringWriter = new StringWriter();
     return stringWriter;
   }
diff --git a/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java b/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
index 5464236c..f1188977 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
@@ -16,6 +16,25 @@ public class ByteConverter {
     // prevent instantiation of static helper class
   }
 
+  /**
+   * Convert a variable length array of bytes to an integer
+   * @param bytes array of bytes that can be decoded as an integer
+   * @return integer
+   */
+  public static int bytesToInt(byte []bytes) {
+    if ( bytes.length == 1 ) {
+      return (int)bytes[0];
+    }
+    if ( bytes.length == 2 ) {
+      return int2(bytes, 0);
+    }
+    if ( bytes.length == 4 ) {
+      return int4(bytes, 0);
+    } else {
+      throw new IllegalArgumentException("Argument bytes is empty");
+    }
+  }
+
   /**
    * Parses a long value from the byte array.
    *
diff --git a/pgjdbc/src/main/java/org/postgresql/util/PSQLException.java b/pgjdbc/src/main/java/org/postgresql/util/PSQLException.java
index 370ce8a9..0e3cd476 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/PSQLException.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/PSQLException.java
@@ -20,7 +20,11 @@ public class PSQLException extends SQLException {
   }
 
   public PSQLException(ServerErrorMessage serverError) {
-    super(serverError.toString(), serverError.getSQLState());
+    this(serverError, true);
+  }
+
+  public PSQLException(ServerErrorMessage serverError, boolean detail) {
+    super(detail ? serverError.toString() : serverError.getNonSensitiveErrorMessage(), serverError.getSQLState());
     this.serverError = serverError;
   }
 
diff --git a/pgjdbc/src/main/java/org/postgresql/util/ServerErrorMessage.java b/pgjdbc/src/main/java/org/postgresql/util/ServerErrorMessage.java
index d44b464d..7b3f62c2 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/ServerErrorMessage.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/ServerErrorMessage.java
@@ -143,6 +143,19 @@ public class ServerErrorMessage implements Serializable {
     return Integer.parseInt(s);
   }
 
+  String getNonSensitiveErrorMessage() {
+    StringBuilder totalMessage = new StringBuilder();
+    String message = mesgParts.get(SEVERITY);
+    if (message != null) {
+      totalMessage.append(message).append(": ");
+    }
+    message = mesgParts.get(MESSAGE);
+    if (message != null) {
+      totalMessage.append(message);
+    }
+    return totalMessage.toString();
+  }
+
   public String toString() {
     // Now construct the message from what the server sent
     // The general format is:


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/core/ParserTest.java b/pgjdbc/src/test/java/org/postgresql/core/ParserTest.java
index 4274fcde..bd5aadc1 100644
--- a/pgjdbc/src/test/java/org/postgresql/core/ParserTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/core/ParserTest.java
@@ -216,4 +216,22 @@ public class ParserTest {
     Assert.assertEquals(34, command.getBatchRewriteValuesBraceOpenPosition());
     Assert.assertEquals(56, command.getBatchRewriteValuesBraceClosePosition());
   }
+
+  @Test
+  public void valuesTableParse() throws SQLException {
+    String query = "insert into values_table (id, name) values (?,?)";
+    List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true);
+    SqlCommand command = qry.get(0).getCommand();
+    Assert.assertEquals(43,command.getBatchRewriteValuesBraceOpenPosition());
+    Assert.assertEquals(49,command.getBatchRewriteValuesBraceClosePosition());
+
+    query = "insert into table_values (id, name) values (?,?)";
+    qry = Parser.parseJdbcSql(query, true, true, true, true);
+    command = qry.get(0).getCommand();
+    Assert.assertEquals(43,command.getBatchRewriteValuesBraceOpenPosition());
+    Assert.assertEquals(49,command.getBatchRewriteValuesBraceClosePosition());
+
+
+
+  }
 }
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java
new file mode 100644
index 00000000..53fd56dc
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.postgresql.test.TestUtil;
+import org.postgresql.test.jdbc2.BaseTest4;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.Writer;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLXML;
+import java.sql.Statement;
+
+public class PgSQLXMLTest extends BaseTest4 {
+
+  @Override
+  @Before
+  public void setUp() throws Exception {
+    super.setUp();
+    TestUtil.createTempTable(con, "xmltab","x xml");
+  }
+
+  @Test
+  public void setCharacterStream() throws  Exception {
+    String exmplar = "<x>value</x>";
+    SQLXML pgSQLXML = con.createSQLXML();
+    Writer writer = pgSQLXML.setCharacterStream();
+    writer.write(exmplar);
+    PreparedStatement preparedStatement = con.prepareStatement("insert into xmltab values (?)");
+    preparedStatement.setSQLXML(1,pgSQLXML);
+    preparedStatement.execute();
+
+    Statement statement = con.createStatement();
+    ResultSet rs = statement.executeQuery("select * from xmltab");
+    assertTrue(rs.next());
+    SQLXML result = rs.getSQLXML(1);
+    assertNotNull(result);
+    assertEquals(exmplar, result.getString());
+  }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/core/LogServerMessagePropertyTest.java b/pgjdbc/src/test/java/org/postgresql/test/core/LogServerMessagePropertyTest.java
new file mode 100644
index 00000000..daab6935
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/test/core/LogServerMessagePropertyTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.core;
+
+import org.postgresql.PGProperty;
+import org.postgresql.core.ServerVersion;
+import org.postgresql.test.TestUtil;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+
+public class LogServerMessagePropertyTest {
+  private static final String PRIMARY_KEY_NAME = "lms_test_pk";
+  private static final String CREATE_TABLE_SQL =
+      "CREATE TABLE pg_temp.lms_test ("
+      + "  id text, "
+      + "  CONSTRAINT " + PRIMARY_KEY_NAME + " PRIMARY KEY (id)"
+      + ")";
+  private static final String SECRET_VALUE = "some_secret_value";
+  private static final String INSERT_SQL =
+      "INSERT INTO pg_temp.lms_test (id) VALUES ('" + SECRET_VALUE + "')";
+  private static final String UNIQUE_VIOLATION_SQL_STATE = "23505";
+
+  /**
+   * Creates a connection with the additional properties, use it to
+   * 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 {
+    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);
+    } catch (SQLException e) {
+      Assert.assertEquals("SQL state must be for a unique violation", UNIQUE_VIOLATION_SQL_STATE, e.getSQLState());
+      return e.getMessage();
+    } finally {
+      conn.close();
+    }
+    // Should never get here:
+    Assert.fail("A duplicate key exception should have occurred");
+    return null;
+  }
+
+  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));
+    }
+  }
+
+  private static void assertMessageDoesNotContain(String message, String text) {
+    if (message.toLowerCase().indexOf(text.toLowerCase()) >= 0) {
+      Assert.fail(String.format("Message must not contain text '%s': %s", text, message));
+    }
+  }
+
+  @Test
+  public void testWithDefaults() throws SQLException {
+    Properties props = new Properties();
+    String message = testViolatePrimaryKey(props);
+    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 testWithExplicitlyEnabled() throws SQLException {
+    Properties props = new Properties();
+    props.setProperty(PGProperty.LOG_SERVER_ERROR_DETAIL.getName(), "true");
+    String message = testViolatePrimaryKey(props);
+    assertMessageContains(message, PRIMARY_KEY_NAME);
+    assertMessageContains(message, "Detail:");
+    assertMessageContains(message, SECRET_VALUE);
+  }
+
+  @Test
+  public void testWithLogServerErrorDetailDisabled() throws SQLException {
+    Properties props = new Properties();
+    props.setProperty(PGProperty.LOG_SERVER_ERROR_DETAIL.getName(), "false");
+    String message = testViolatePrimaryKey(props);
+    assertMessageContains(message, PRIMARY_KEY_NAME);
+    assertMessageDoesNotContain(message, "Detail:");
+    assertMessageDoesNotContain(message, SECRET_VALUE);
+  }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/CopyTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/CopyTest.java
index 798c6d7a..278e8102 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/CopyTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/CopyTest.java
@@ -6,6 +6,7 @@
 package org.postgresql.test.jdbc2;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -134,6 +135,47 @@ public class CopyTest {
     assertEquals(dataRows, rowCount);
   }
 
+  @Test
+  public void testCopyInAsOutputStreamClosesAfterEndCopy() throws SQLException, IOException {
+    String sql = "COPY copytest FROM STDIN";
+    PGCopyOutputStream os = new PGCopyOutputStream((PGConnection) con, sql, 1000);
+    try {
+      for (String anOrigData : origData) {
+        byte[] buf = anOrigData.getBytes();
+        os.write(buf);
+      }
+      os.endCopy();
+    } finally {
+      os.close();
+    }
+    assertFalse(os.isActive());
+    int rowCount = getCount();
+    assertEquals(dataRows, rowCount);
+  }
+
+  @Test
+  public void testCopyInAsOutputStreamFailsOnFlushAfterEndCopy() throws SQLException, IOException {
+    String sql = "COPY copytest FROM STDIN";
+    PGCopyOutputStream os = new PGCopyOutputStream((PGConnection) con, sql, 1000);
+    try {
+      for (String anOrigData : origData) {
+        byte[] buf = anOrigData.getBytes();
+        os.write(buf);
+      }
+      os.endCopy();
+    } finally {
+      os.close();
+    }
+    try {
+      os.flush();
+      fail("should have failed flushing an inactive copy stream.");
+    } catch (IOException e) {
+      if (!e.toString().contains("This copy stream is closed.")) {
+        fail("has failed not due to checkClosed(): " + e);
+      }
+    }
+  }
+
   @Test
   public void testCopyInFromInputStream() throws SQLException, IOException {
     String sql = "COPY copytest FROM STDIN";
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java
index 0e3f6055..e8e03140 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java
@@ -83,7 +83,8 @@ public class DatabaseMetaDataTest {
     }
 
     TestUtil.createDomain(con, "nndom", "int not null");
-    TestUtil.createTable(con, "domaintable", "id nndom");
+    TestUtil.createDomain(con, "varbit2", "varbit(3)");
+    TestUtil.createTable(con, "domaintable", "id nndom, v varbit2");
     stmt.close();
   }
 
@@ -670,9 +671,22 @@ public class DatabaseMetaDataTest {
     assertTrue(rs.next());
     assertEquals("id", rs.getString("COLUMN_NAME"));
     assertEquals("NO", rs.getString("IS_NULLABLE"));
+    assertTrue(rs.next());
     assertTrue(!rs.next());
   }
 
+  @Test
+  public void testDomainColumnSize() throws SQLException {
+    DatabaseMetaData dbmd = con.getMetaData();
+    ResultSet rs = dbmd.getColumns("", "", "domaintable", "");
+    assertTrue(rs.next());
+    assertEquals("id", rs.getString("COLUMN_NAME"));
+    assertEquals(10, rs.getInt("COLUMN_SIZE"));
+    assertTrue(rs.next());
+    assertEquals("v", rs.getString("COLUMN_NAME"));
+    assertEquals(3, rs.getInt("COLUMN_SIZE"));
+  }
+
   @Test
   public void testAscDescIndexInfo() throws SQLException {
     if (!TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3)) {
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
index 01d9fbf9..004f1b61 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
@@ -13,8 +13,10 @@ import org.postgresql.core.ParserTest;
 import org.postgresql.core.ReturningParserTest;
 import org.postgresql.core.v3.V3ParameterListTests;
 import org.postgresql.jdbc.DeepBatchedInsertStatementTest;
+import org.postgresql.jdbc.PgSQLXMLTest;
 import org.postgresql.jdbc.PrimitiveArraySupportTest;
 import org.postgresql.test.core.JavaVersionTest;
+import org.postgresql.test.core.LogServerMessagePropertyTest;
 import org.postgresql.test.core.NativeQueryBindLengthTest;
 import org.postgresql.test.core.OptionsPropertyTest;
 import org.postgresql.test.util.ExpressionPropertiesTest;
@@ -67,6 +69,7 @@ import org.junit.runners.Suite;
     JavaVersionTest.class,
     JBuilderTest.class,
     LoginTimeoutTest.class,
+    LogServerMessagePropertyTest.class,
     LruCacheTest.class,
     MiscTest.class,
     NativeQueryBindLengthTest.class,
@@ -80,6 +83,7 @@ import org.junit.runners.Suite;
     PGPropertyTest.class,
     PGTimestampTest.class,
     PGTimeTest.class,
+    PgSQLXMLTest.class,
     PreparedStatementTest.class,
     PrimitiveArraySupportTest.class,
     QuotationTest.class,
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java
index 813a777f..a22f42ae 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java
@@ -232,4 +232,17 @@ public class DatabaseMetaDataTest {
       stmt.execute("DROP FUNCTION getfunc_f3(int, varchar)");
     }
   }
+
+  @Test
+  public void testSortedDataTypes() throws SQLException {
+    // https://github.com/pgjdbc/pgjdbc/issues/716
+    DatabaseMetaData dbmd = conn.getMetaData();
+    ResultSet rs = dbmd.getTypeInfo();
+    int lastType = Integer.MIN_VALUE;
+    while (rs.next()) {
+      int type = rs.getInt("DATA_TYPE");
+      assertTrue(lastType <= type);
+      lastType = type;
+    }
+  }
 }