Repository: pgjdbc/pgjdbc
Pull request #1636 (2019-12-04 07:07:03)

Add Binary Support for Oid.NUMERIC and Oid.NUMERIC_ARRAY
### All Submissions:

* [x] Have you followed the guidelines in our [Contributing](https://github.com/pgjdbc/pgjdbc/blob/master/CONTRIBUTING.md) document?
* [x] Have you checked to ensure there aren't other open [Pull Requests](../../pulls) for the same update/change?

<!-- You can erase any parts of this template not applicable to your Pull Request. -->

### New Feature Submissions:

1. [x] Does your submission pass tests?
2. [x] Does mvn checkstyle:check pass ?
3. [x] Have you added your new test classes to an existing test suite in alphabetical order?

### Changes to Existing Features:

* [ ] Does this break existing behaviour? If so please explain.
* [ ] Have you added an explanation of what your changes do and why you'd like us to include them?
* [ ] Have you written new tests for your core changes, as applicable?
* [ ] Have you successfully run tests with your changes locally?


davecramer: Awesome! Thanks,
Can you add a test for it and it looks like some tests may need to account for it .
Again, thanks
davecramer: @mahmoudbahaa actually all that needs to be done is to patch it via 
```
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
index b8b256a1..f7cedcb3 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
@@ -243,6 +243,9 @@ public class PgArray implements java.sql.Array {
           case Oid.FLOAT8:
             arr[i] = ByteConverter.float8(fieldBytes, pos);
             break;
+          case Oid.NUMERIC:
+            arr[i] = ByteConverter.numeric(fieldBytes,pos);
+            break;
           case Oid.TEXT:
           case Oid.VARCHAR:
             Encoding encoding = connection.getEncoding();
@@ -389,6 +392,8 @@ public class PgArray implements java.sql.Array {
         return Float.class;
       case Oid.FLOAT8:
         return Double.class;
+      case Oid.NUMERIC:
+        return BigDecimal.class;
       case Oid.TEXT:
       case Oid.VARCHAR:
         return String.class;
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
index 4ef826fa..c11a0140 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
@@ -2345,7 +2345,7 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
         }
         return toBigDecimal(trimMoney(String.valueOf(obj)), scale);
       } else {
-        Number num = ByteConverter.numeric(thisRow[columnIndex - 1]);
+        Number num = ByteConverter.numeric(thisRow[columnIndex - 1], 0);
         if (allowNaN && Double.isNaN(num.doubleValue())) {
           return Double.NaN;
         }
@@ -3023,7 +3023,7 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
       case Oid.FLOAT8:
         return ByteConverter.float8(bytes, 0);
       case Oid.NUMERIC:
-        return ByteConverter.numeric(bytes).doubleValue();
+        return ByteConverter.numeric(bytes, 0).doubleValue();
     }
     throw new PSQLException(GT.tr("Cannot convert the column of type {0} to requested type {1}.",
         Oid.toString(oid), targetType), PSQLState.DATA_TYPE_MISMATCH);
@@ -3069,7 +3069,7 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
         val = (long) ByteConverter.float8(bytes, 0);
         break;
       case Oid.NUMERIC:
-        Number num = ByteConverter.numeric(bytes);
+        Number num = ByteConverter.numeric(bytes, 0);
         if (num instanceof  BigDecimal) {
           val = ((BigDecimal) num).setScale(0 , RoundingMode.DOWN).longValueExact();
         } else {
diff --git a/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java b/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
index 75ed5a34..80a77dd7 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
@@ -141,15 +141,15 @@ public class ByteConverter {
    * @param bytes array of bytes that can be decoded as an integer
    * @return integer
    */
-  public static Number numeric(byte []bytes) {
+  public static Number numeric(byte []bytes, int pos) {
     if (bytes.length < 8) {
       throw new IllegalArgumentException("number of bytes should be at-least 8");
     }
 
-    short len = ByteConverter.int2(bytes, 0);
-    short weight = ByteConverter.int2(bytes, 2);
-    short sign = ByteConverter.int2(bytes, 4);
-    short scale = ByteConverter.int2(bytes, 6);
+    short len = ByteConverter.int2(bytes, pos);
+    short weight = ByteConverter.int2(bytes, pos + 2);
+    short sign = ByteConverter.int2(bytes, pos + 4);
+    short scale = ByteConverter.int2(bytes, pos + 6);
 
     if (!(sign == NUMERIC_POS
         || sign == NUMERIC_NEG
@@ -168,7 +168,7 @@ public class ByteConverter {
     short[] digits = new short[len];
     int idx = 8;
     for (int i = 0; i < len; i++) {
-      short d = ByteConverter.int2(bytes, idx);
+      short d = ByteConverter.int2(bytes, pos + idx);
       idx += 2;
 
       if (d < 0 || d >= NBASE) {
```

davecramer: @mahmoudbahaa any chance you can get to this today? I want to release this week and I'd like to have this in the release.
mahmoudbahaa: @davecramer I was already doing these changes but was caught in work please check now
davecramer: @mahmoudbahaa sorry to be pushy, but I did want to get this in. Thanks!
mahmoudbahaa: np, tests are green however I have 2 question:
1) `StatementTest.testFastCloses:937` always fails with timeout on my machine is that normal ? (I on Windows not Linux if that any indicator) i will try to linux later but wanted to confirm if this is a known issue or not.
2) it seems some configurations in `build.local.properties` break tests for example if you set `preparethreshold=-1` many test fails 

Patch:
diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
index 42729c95..be805c47 100644
--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java
+++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
@@ -425,6 +425,13 @@ public enum PGProperty {
   REWRITE_BATCHED_INSERTS("reWriteBatchedInserts", "false",
       "Enable optimization to rewrite and collapse compatible INSERT statements that are batched."),
 
+  /**
+   * Enable mode to filter out the names of database objects for which the current user has no privileges
+   * granted from appearing in the DatabaseMetaData returned by the driver.
+   */
+  HIDE_UNPRIVILEGED_OBJECTS("hideUnprivilegedObjects", "false",
+    "Enable hiding of database objects for which the current user has no privileges granted from the DatabaseMetaData"),
+
   /**
    * <p>Connection parameter passed in the startup message. This parameter accepts two values; "true"
    * and "database". Passing "true" tells the backend to go into walsender mode, wherein a small set
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 e1a6df35..e72aac5e 100644
--- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
+++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
@@ -1495,6 +1495,22 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     PGProperty.REWRITE_BATCHED_INSERTS.set(properties, reWrite);
   }
 
+  /**
+   * @return boolean indicating property is enabled or not.
+   * @see PGProperty#HIDE_UNPRIVILEGED_OBJECTS
+   */
+  public boolean getHideUnprivilegedObjects() {
+    return PGProperty.HIDE_UNPRIVILEGED_OBJECTS.getBoolean(properties);
+  }
+
+  /**
+   * @param hideUnprivileged boolean value to set the property in the properties collection
+   * @see PGProperty#HIDE_UNPRIVILEGED_OBJECTS
+   */
+  public void setHideUnprivilegedObjects(boolean hideUnprivileged) {
+    PGProperty.HIDE_UNPRIVILEGED_OBJECTS.set(properties, hideUnprivileged);
+  }
+
   //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1"
   public java.util.logging.Logger getParentLogger() {
     return Logger.getLogger("org.postgresql");
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
index b8b256a1..9635918d 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
@@ -243,6 +243,9 @@ public class PgArray implements java.sql.Array {
           case Oid.FLOAT8:
             arr[i] = ByteConverter.float8(fieldBytes, pos);
             break;
+          case Oid.NUMERIC:
+            arr[i] = ByteConverter.numeric(fieldBytes, pos, len);
+            break;
           case Oid.TEXT:
           case Oid.VARCHAR:
             Encoding encoding = connection.getEncoding();
@@ -389,6 +392,8 @@ public class PgArray implements java.sql.Array {
         return Float.class;
       case Oid.FLOAT8:
         return Double.class;
+      case Oid.NUMERIC:
+        return BigDecimal.class;
       case Oid.TEXT:
       case Oid.VARCHAR:
         return String.class;
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
index 2e7b9f2c..8779c56b 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
@@ -134,7 +134,8 @@ public class PgConnection implements BaseConnection {
   private boolean autoCommit = true;
   // Connection's readonly state.
   private boolean readOnly = false;
-
+  // Filter out database objects for which the current user has no privileges granted from the DatabaseMetaData
+  private boolean  hideUnprivilegedObjects ;
   // Bind String to UNSPECIFIED or VARCHAR?
   private final boolean bindStringAsVarchar;
 
@@ -221,6 +222,8 @@ public class PgConnection implements BaseConnection {
       setReadOnly(true);
     }
 
+    this.hideUnprivilegedObjects = PGProperty.HIDE_UNPRIVILEGED_OBJECTS.getBoolean(info);
+
     Set<Integer> binaryOids = getBinaryOids(info);
 
     // split for receive and send for better control
@@ -332,6 +335,7 @@ public class PgConnection implements BaseConnection {
       binaryOids.add(Oid.INT8);
       binaryOids.add(Oid.FLOAT4);
       binaryOids.add(Oid.FLOAT8);
+      binaryOids.add(Oid.NUMERIC);
       binaryOids.add(Oid.TIME);
       binaryOids.add(Oid.DATE);
       binaryOids.add(Oid.TIMETZ);
@@ -342,6 +346,7 @@ public class PgConnection implements BaseConnection {
       binaryOids.add(Oid.INT8_ARRAY);
       binaryOids.add(Oid.FLOAT4_ARRAY);
       binaryOids.add(Oid.FLOAT8_ARRAY);
+      binaryOids.add(Oid.NUMERIC_ARRAY);
       binaryOids.add(Oid.VARCHAR_ARRAY);
       binaryOids.add(Oid.TEXT_ARRAY);
       binaryOids.add(Oid.POINT);
@@ -948,6 +953,10 @@ public class PgConnection implements BaseConnection {
     return queryExecutor.getDatabase();
   }
 
+  public boolean getHideUnprivilegedObjects() {
+    return hideUnprivilegedObjects;
+  }
+
   /**
    * <p>Overrides finalize(). If called, it closes the connection.</p>
    *
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
index 08823012..80201a89 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
@@ -1045,6 +1045,9 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
     if (procedureNamePattern != null && !procedureNamePattern.isEmpty()) {
       sql += " AND p.proname LIKE " + escapeQuotes(procedureNamePattern);
     }
+    if (connection.getHideUnprivilegedObjects()) {
+      sql += " AND has_function_privilege(p.oid,'EXECUTE')";
+    }
     sql += " ORDER BY PROCEDURE_SCHEM, PROCEDURE_NAME, p.oid::text ";
 
     return createMetaDataStatement().executeQuery(sql);
@@ -1303,6 +1306,10 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
     if (schemaPattern != null && !schemaPattern.isEmpty()) {
       select += " AND n.nspname LIKE " + escapeQuotes(schemaPattern);
     }
+    if (connection.getHideUnprivilegedObjects()) {
+      select += " AND has_table_privilege(c.oid, "
+        + " 'SELECT, INSERT, UPDATE, DELETE, RULE, REFERENCES, TRIGGER')";
+    }
     orderby = " ORDER BY TABLE_TYPE,TABLE_SCHEM,TABLE_NAME ";
 
     if (tableNamePattern != null && !tableNamePattern.isEmpty()) {
@@ -1419,6 +1426,9 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
     if (schemaPattern != null && !schemaPattern.isEmpty()) {
       sql += " AND nspname LIKE " + escapeQuotes(schemaPattern);
     }
+    if (connection.getHideUnprivilegedObjects()) {
+      sql += " AND has_schema_privilege(nspname, 'USAGE, CREATE')";
+    }
     sql += " ORDER BY TABLE_SCHEM";
 
     return createMetaDataStatement().executeQuery(sql);
@@ -2266,6 +2276,10 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
           + " AND "
           + " (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))";
 
+    if (connection.getHideUnprivilegedObjects() && connection.haveMinimumServerVersion(ServerVersion.v9_2)) {
+      sql += " AND has_type_privilege(t.oid, 'USAGE')";
+    }
+
     Statement stmt = connection.createStatement();
     ResultSet rs = stmt.executeQuery(sql);
     // cache some results, this will keep memory usage down, and speed
@@ -2584,6 +2598,12 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
       toAdd.append(" and n.nspname like ").append(escapeQuotes(schemaPattern));
     }
     sql += toAdd.toString();
+
+    if (connection.getHideUnprivilegedObjects()
+        && connection.haveMinimumServerVersion(ServerVersion.v9_2)) {
+      sql += " AND has_type_privilege(t.oid, 'USAGE')";
+    }
+
     sql += " order by data_type, type_schem, type_name";
     return createMetaDataStatement().executeQuery(sql);
   }
@@ -2695,6 +2715,9 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
     if (functionNamePattern != null && !functionNamePattern.isEmpty()) {
       sql += " AND p.proname LIKE " + escapeQuotes(functionNamePattern);
     }
+    if (connection.getHideUnprivilegedObjects()) {
+      sql += " AND has_function_privilege(p.oid,'EXECUTE')";
+    }
     sql += " ORDER BY FUNCTION_SCHEM, FUNCTION_NAME, p.oid::text ";
 
     return createMetaDataStatement().executeQuery(sql);
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
index a8b76f1d..4ef826fa 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
@@ -35,6 +35,7 @@ import java.io.Reader;
 import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.math.RoundingMode;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.sql.Array;
@@ -2343,6 +2344,13 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
           return res;
         }
         return toBigDecimal(trimMoney(String.valueOf(obj)), scale);
+      } else {
+        Number num = ByteConverter.numeric(thisRow[columnIndex - 1]);
+        if (allowNaN && Double.isNaN(num.doubleValue())) {
+          return Double.NaN;
+        }
+
+        return num;
       }
     }
 
@@ -3014,6 +3022,8 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
         return ByteConverter.float4(bytes, 0);
       case Oid.FLOAT8:
         return ByteConverter.float8(bytes, 0);
+      case Oid.NUMERIC:
+        return ByteConverter.numeric(bytes).doubleValue();
     }
     throw new PSQLException(GT.tr("Cannot convert the column of type {0} to requested type {1}.",
         Oid.toString(oid), targetType), PSQLState.DATA_TYPE_MISMATCH);
@@ -3058,6 +3068,14 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
       case Oid.FLOAT8:
         val = (long) ByteConverter.float8(bytes, 0);
         break;
+      case Oid.NUMERIC:
+        Number num = ByteConverter.numeric(bytes);
+        if (num instanceof  BigDecimal) {
+          val = ((BigDecimal) num).setScale(0 , RoundingMode.DOWN).longValueExact();
+        } else {
+          val = num.longValue();
+        }
+        break;
       default:
         throw new PSQLException(
             GT.tr("Cannot convert the column of type {0} to requested type {1}.",
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSetMetaData.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSetMetaData.java
index 428c31cb..1d9f5bd0 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSetMetaData.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSetMetaData.java
@@ -113,7 +113,8 @@ public class PgResultSetMetaData implements ResultSetMetaData, PGResultSetMetaDa
   public int isNullable(int column) throws SQLException {
     fetchFieldMetaData();
     Field field = getField(column);
-    return field.getMetadata().nullable;
+    FieldMetadata metadata = field.getMetadata();
+    return metadata == null ? ResultSetMetaData.columnNullable : metadata.nullable;
   }
 
   /**
@@ -151,7 +152,8 @@ public class PgResultSetMetaData implements ResultSetMetaData, PGResultSetMetaDa
       return "";
     }
     fetchFieldMetaData();
-    return field.getMetadata().columnName;
+    FieldMetadata metadata = field.getMetadata();
+    return metadata == null ? "" : metadata.columnName;
   }
 
   public String getSchemaName(int column) throws SQLException {
@@ -268,7 +270,8 @@ public class PgResultSetMetaData implements ResultSetMetaData, PGResultSetMetaDa
   public String getBaseSchemaName(int column) throws SQLException {
     fetchFieldMetaData();
     Field field = getField(column);
-    return field.getMetadata().schemaName;
+    FieldMetadata metadata = field.getMetadata();
+    return metadata == null ? "" : metadata.schemaName;
   }
 
   public int getPrecision(int column) throws SQLException {
@@ -288,7 +291,8 @@ public class PgResultSetMetaData implements ResultSetMetaData, PGResultSetMetaDa
   public String getBaseTableName(int column) throws SQLException {
     fetchFieldMetaData();
     Field field = getField(column);
-    return field.getMetadata().tableName;
+    FieldMetadata metadata = field.getMetadata();
+    return metadata == null ? "" : metadata.tableName;
   }
 
   /**
diff --git a/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java b/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
index f1188977..dd7eb737 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
@@ -5,6 +5,9 @@
 
 package org.postgresql.util;
 
+import java.math.BigDecimal;
+import java.nio.CharBuffer;
+
 /**
  * Helper methods to parse java base types from byte arrays.
  *
@@ -12,6 +15,16 @@ package org.postgresql.util;
  */
 public class ByteConverter {
 
+  private static final int NBASE = 10000;
+  private static final int NUMERIC_DSCALE_MASK = 0x00003FFF;
+  private static final short NUMERIC_POS = 0x0000;
+  private static final short NUMERIC_NEG = 0x4000;
+  private static final short NUMERIC_NAN = (short) 0xC000;
+  private static final int DEC_DIGITS = 4;
+  private static final int[] round_powers = {0, 1000, 100, 10};
+  private static final int SHORT_BYTES = 2;
+  private static final int LONG_BYTES = 4;
+
   private ByteConverter() {
     // prevent instantiation of static helper class
   }
@@ -25,16 +38,168 @@ public class ByteConverter {
     if ( bytes.length == 1 ) {
       return (int)bytes[0];
     }
-    if ( bytes.length == 2 ) {
+    if ( bytes.length == SHORT_BYTES ) {
       return int2(bytes, 0);
     }
-    if ( bytes.length == 4 ) {
+    if ( bytes.length == LONG_BYTES ) {
       return int4(bytes, 0);
     } else {
       throw new IllegalArgumentException("Argument bytes is empty");
     }
   }
 
+  /**
+   * Convert a number from binary representation to text representation.
+   * @param idx index of the digit to be converted in the digits array
+   * @param digits array of shorts that can be decoded as the number String
+   * @param buffer the character buffer to put the text representation in
+   * @param alwaysPutIt a flag that indicate whether or not to put the digit char even if it is zero
+   * @return String the number as String
+   */
+  private static void digitToString(int idx, short[] digits, CharBuffer buffer, boolean alwaysPutIt) {
+    short dig = (idx >= 0 && idx < digits.length) ? digits[idx] : 0;
+    for (int p = 1; p < round_powers.length; p++) {
+      int pow = round_powers[p];
+      short d1 = (short)(dig / pow);
+      dig -= d1 * pow;
+      boolean putit = (d1 > 0);
+      if (putit || alwaysPutIt) {
+        buffer.put((char)(d1 + '0'));
+      }
+    }
+
+    buffer.put((char)(dig + '0'));
+  }
+
+  /**
+   * Convert a number from binary representation to text representation.
+   * @param digits array of shorts that can be decoded as the number String
+   * @param scale the scale of the number binary representation
+   * @param weight the weight of the number binary representation
+   * @param sign the sign of the number
+   * @return String the number as String
+   */
+  private static String numberBytesToString(short[] digits, int scale, int weight, int sign) {
+    CharBuffer buffer;
+    int i;
+    int d;
+
+    /*
+     * Allocate space for the result.
+     *
+     * i is set to the # of decimal digits before decimal point. dscale is the
+     * # of decimal digits we will print after decimal point. We may generate
+     * as many as DEC_DIGITS-1 excess digits at the end, and in addition we
+     * need room for sign, decimal point, null terminator.
+     */
+    i = (weight + 1) * DEC_DIGITS;
+    if (i <= 0) {
+      i = 1;
+    }
+
+    buffer = CharBuffer.allocate((i + scale + DEC_DIGITS + 2));
+
+    /*
+     * Output a dash for negative values
+     */
+    if (sign == NUMERIC_NEG) {
+      buffer.put('-');
+    }
+
+    /*
+     * Output all digits before the decimal point
+     */
+    if (weight < 0) {
+      d = weight + 1;
+      buffer.put('0');
+    } else {
+      for (d = 0; d <= weight; d++) {
+        /* In the first digit, suppress extra leading decimal zeroes */
+        digitToString(d, digits, buffer, d != 0);
+      }
+    }
+
+    /*
+     * If requested, output a decimal point and all the digits that follow it.
+     * We initially put out a multiple of DEC_DIGITS digits, then truncate if
+     * needed.
+     */
+    if (scale > 0) {
+      buffer.put('.');
+      for (i = 0; i < scale; d++, i += DEC_DIGITS) {
+        digitToString(d, digits, buffer, true);
+      }
+    }
+
+    /*
+     * terminate the string and return it
+     */
+    int extra = (i - scale) % DEC_DIGITS;
+    return new String(buffer.array(), 0, buffer.position() - extra);
+  }
+
+  /**
+   * 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 Number numeric(byte [] bytes) {
+    return numeric(bytes, 0, bytes.length);
+  }
+
+  /**
+   * Convert a variable length array of bytes to an integer
+   * @param bytes array of bytes that can be decoded as an integer
+   * @param pos index of the start position of the bytes array for number
+   * @param numBytes number of bytes to use, length is already encoded
+   *                in the binary format but this is used for double checking
+   * @return integer
+   */
+  public static Number numeric(byte [] bytes, int pos, int numBytes) {
+    if (numBytes < 8) {
+      throw new IllegalArgumentException("number of bytes should be at-least 8");
+    }
+
+    short len = ByteConverter.int2(bytes, pos);
+    short weight = ByteConverter.int2(bytes, pos + 2);
+    short sign = ByteConverter.int2(bytes, pos + 4);
+    short scale = ByteConverter.int2(bytes, pos + 6);
+
+    if (numBytes != (len * SHORT_BYTES + 8)) {
+      throw new IllegalArgumentException("invalid length of bytes \"numeric\" value");
+    }
+
+    if (!(sign == NUMERIC_POS
+        || sign == NUMERIC_NEG
+        || sign == NUMERIC_NAN)) {
+      throw new IllegalArgumentException("invalid sign in \"numeric\" value");
+    }
+
+    if (sign == NUMERIC_NAN) {
+      return Double.NaN;
+    }
+
+    if ((scale & NUMERIC_DSCALE_MASK) != scale) {
+      throw new IllegalArgumentException("invalid scale in \"numeric\" value");
+    }
+
+    short[] digits = new short[len];
+    int idx = pos + 8;
+    for (int i = 0; i < len; i++) {
+      short d = ByteConverter.int2(bytes, idx);
+      idx += 2;
+
+      if (d < 0 || d >= NBASE) {
+        throw new IllegalArgumentException("invalid digit in \"numeric\" value");
+      }
+
+      digits[i] = d;
+    }
+
+    String numString = numberBytesToString(digits, scale, weight, sign);
+    return new BigDecimal(numString);
+  }
+
   /**
    * Parses a long value from the byte array.
    *


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/NoColumnMetadataIssue1613.java b/pgjdbc/src/test/java/org/postgresql/jdbc/NoColumnMetadataIssue1613.java
new file mode 100644
index 00000000..750623ac
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/NoColumnMetadataIssue1613.java
@@ -0,0 +1,41 @@
+/*
+ * 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.assertTrue;
+
+import org.postgresql.test.TestUtil;
+import org.postgresql.test.jdbc2.BaseTest4;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+/**
+ * If the SQL query has no column metadata, the driver shouldn't break by a null pointer exception.
+ * It should return the result correctly.
+ *
+ * @author Ivy (ivyyiyideng@gmail.com)
+ *
+ */
+public class NoColumnMetadataIssue1613 extends BaseTest4 {
+  @Override
+  @Before
+  public void setUp() throws Exception {
+    super.setUp();
+    TestUtil.createTempTable(con, "test_no_column_metadata","id int");
+  }
+
+  @Test
+  public void shouldBeNoNPE() throws Exception {
+    Statement statement = con.createStatement();
+    statement.executeQuery("INSERT INTO test_no_column_metadata values (1)");
+    ResultSet rs = statement.executeQuery("SELECT x FROM test_no_column_metadata x");
+    assertTrue(rs.next());
+  }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java
index a46abef5..2ac51e7c 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java
@@ -51,6 +51,9 @@ import java.util.logging.Logger;
 @RunWith(Parameterized.class)
 public class PreparedStatementTest extends BaseTest4 {
 
+  private static final int NUMERIC_MAX_PRECISION = 1000;
+  private static final int NUMERIC_MAX_DISPLAY_SCALE = NUMERIC_MAX_PRECISION;
+
   public PreparedStatementTest(BinaryMode binaryMode) {
     setBinaryMode(binaryMode);
   }
@@ -513,6 +516,53 @@ public class PreparedStatementTest extends BaseTest4 {
     st.close();
   }
 
+  @Test
+  public void testNumeric() throws SQLException {
+    PreparedStatement pstmt = con.prepareStatement(
+        "CREATE TEMP TABLE numeric_tab (max_numeric_positive numeric, min_numeric_positive numeric, max_numeric_negative numeric, min_numeric_negative numeric, null_value numeric)");
+    pstmt.executeUpdate();
+    pstmt.close();
+
+    char[] wholeDigits = new char[NUMERIC_MAX_DISPLAY_SCALE];
+    for (int i = 0; i < NUMERIC_MAX_DISPLAY_SCALE; i++) {
+      wholeDigits[i] = '9';
+    }
+
+    char[] fractionDigits = new char[NUMERIC_MAX_PRECISION];
+    for (int i = 0; i < NUMERIC_MAX_PRECISION; i++) {
+      fractionDigits[i] = '9';
+    }
+
+    String maxValueString = new String(wholeDigits);
+    String minValueString = new String(fractionDigits);
+    BigDecimal[] values = new BigDecimal[4];
+    values[0] = new BigDecimal(maxValueString);
+    values[1] = new BigDecimal("-" + maxValueString);
+    values[2] = new BigDecimal(minValueString);
+    values[3] = new BigDecimal("-" + minValueString);
+
+    pstmt = con.prepareStatement("insert into numeric_tab values (?,?,?,?,?)");
+    for (int i = 1; i < 5 ; i++) {
+      pstmt.setBigDecimal(i, values[i - 1]);
+    }
+
+    pstmt.setNull(5, Types.NUMERIC);
+    pstmt.executeUpdate();
+    pstmt.close();
+
+    pstmt = con.prepareStatement("select * from numeric_tab");
+    ResultSet rs = pstmt.executeQuery();
+    assertTrue(rs.next());
+    for (int i = 1; i < 5 ; i++) {
+      assertTrue(rs.getBigDecimal(i).compareTo(values[i - 1]) == 0);
+    }
+    rs.getDouble(5);
+    assertTrue(rs.wasNull());
+    rs.close();
+    pstmt.close();
+
+  }
+
   @Test
   public void testDouble() throws SQLException {
     PreparedStatement pstmt = con.prepareStatement(
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataHideUnprivilegedObjectsTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataHideUnprivilegedObjectsTest.java
new file mode 100644
index 00000000..b021787e
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataHideUnprivilegedObjectsTest.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.jdbc4;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import org.postgresql.PGProperty;
+import org.postgresql.core.ServerVersion;
+import org.postgresql.jdbc.PgConnection;
+import org.postgresql.test.TestUtil;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+;
+
+/**
+ * Tests that database objects for which the current user has no privileges are filtered out from
+ * the DatabaseMetaData depending on whether the connection parameter hideUnprivilegedObjects is
+ * set to true.
+ */
+public class DatabaseMetaDataHideUnprivilegedObjectsTest {
+  public static final String COLUMNS = "digit int4, name text";
+  private static Connection hidingCon;
+  private static Connection nonhidingCon;
+  private static Connection privilegedCon;
+  private static PgConnection pgConnection;
+  private static DatabaseMetaData hidingDatabaseMetaData;
+  private static DatabaseMetaData nonhidingDatabaseMetaData;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    Properties props = new Properties();
+    privilegedCon = TestUtil.openPrivilegedDB();
+    pgConnection = privilegedCon.unwrap(PgConnection.class);
+    Statement stmt = privilegedCon.createStatement();
+
+    createTestDataObjectsWithRangeOfPrivilegesInSchema("high_privileges_schema");
+    // Grant Test User ALL privileges on schema.
+    stmt.executeUpdate("GRANT ALL ON SCHEMA high_privileges_schema TO " + TestUtil.getUser());
+    stmt.executeUpdate("REVOKE ALL ON SCHEMA high_privileges_schema FROM public");
+
+    createTestDataObjectsWithRangeOfPrivilegesInSchema("low_privileges_schema");
+    // Grant Test User USAGE privileges on schema.
+    stmt.executeUpdate("GRANT USAGE ON SCHEMA low_privileges_schema TO " + TestUtil.getUser());
+    stmt.executeUpdate("REVOKE ALL ON SCHEMA low_privileges_schema FROM public");
+
+    createTestDataObjectsWithRangeOfPrivilegesInSchema("no_privileges_schema");
+    // Revoke ALL privileges from Test User USAGE on schema.
+    stmt.executeUpdate("REVOKE ALL ON SCHEMA no_privileges_schema FROM " + TestUtil.getUser());
+    stmt.executeUpdate("REVOKE ALL ON SCHEMA no_privileges_schema FROM public");
+
+    stmt.close();
+
+    nonhidingDatabaseMetaData = getNonHidingDatabaseMetaData(props);
+    hidingDatabaseMetaData = getHidingDatabaseMetaData(props);
+  }
+
+  private static DatabaseMetaData getHidingDatabaseMetaData(Properties props) throws Exception {
+    PGProperty.HIDE_UNPRIVILEGED_OBJECTS.set(props, true);
+    hidingCon = TestUtil.openDB(props);
+    if (isSuperUser(hidingCon)) {
+      fail("Test for hiding database objects will not work while:" + TestUtil.getUser()
+          + " has a SUPERUSER role.");
+    }
+    return hidingCon.getMetaData();
+  }
+
+  private static DatabaseMetaData getNonHidingDatabaseMetaData(Properties props) throws Exception {
+    nonhidingCon = TestUtil.openDB(props);
+    return nonhidingCon.getMetaData();
+  }
+
+  private static void createTestDataObjectsWithRangeOfPrivilegesInSchema(String schema)
+      throws SQLException {
+    TestUtil.createSchema(privilegedCon, schema);
+    createSimpleTablesInSchema(schema,
+        new String[]{"owned_table", "all_grants_table", "insert_granted_table",
+          "select_granted_table", "no_grants_table"});
+
+    Statement stmt = privilegedCon.createStatement();
+    stmt.executeUpdate(
+        "CREATE FUNCTION " + schema + "."
+          + "execute_granted_add_function(integer, integer) RETURNS integer AS 'select $1 + $2;' LANGUAGE SQL IMMUTABLE  RETURNS NULL ON NULL INPUT");
+    stmt.executeUpdate(
+        "CREATE FUNCTION " + schema + "."
+          + "no_grants_add_function(integer, integer) RETURNS integer AS 'select $1 + $2;' LANGUAGE SQL IMMUTABLE  RETURNS NULL ON NULL INPUT");
+
+    if (pgConnection.haveMinimumServerVersion(ServerVersion.v11)) {
+      stmt.executeUpdate(
+          "CREATE PROCEDURE " + schema + "."
+          + "execute_granted_insert_procedure( a integer, b integer) LANGUAGE SQL AS 'select $1 + $2;'");
+      stmt.executeUpdate(
+          "CREATE PROCEDURE " + schema + "."
+          + "no_grants_insert_procedure( a integer, b integer) LANGUAGE SQL AS 'select $1 + $2;'");
+
+    }
+    stmt.executeUpdate(
+        "CREATE OR REPLACE VIEW " + schema + "." + "select_granted_view AS SELECT name FROM "
+          + schema + "." + "select_granted_table");
+    stmt.executeUpdate(
+        "CREATE OR REPLACE VIEW " + schema + "." + "no_grants_view AS SELECT name FROM " + schema
+          + "." + "owned_table");
+    stmt.executeUpdate(
+        "CREATE TYPE " + schema + "." + "usage_granted_composite_type AS (f1 int, f2 text)");
+    stmt.executeUpdate(
+        "CREATE TYPE " + schema + "." + "no_grants_composite_type AS (f1 int, f2 text)");
+    stmt.executeUpdate(
+        "CREATE DOMAIN " + schema + "." + "usage_granted_us_postal_code_domain CHAR(5) NOT NULL");
+    stmt.executeUpdate(
+        "CREATE DOMAIN " + schema + "." + "no_grants_us_postal_code_domain AS CHAR(5) NOT NULL");
+
+    if (pgConnection.haveMinimumServerVersion(ServerVersion.v9_2)) {
+      stmt.executeUpdate(
+          "REVOKE ALL ON TYPE " + schema + "."
+          + "usage_granted_composite_type FROM public RESTRICT");
+      stmt.executeUpdate(
+          "REVOKE ALL ON TYPE " + schema + "." + "no_grants_composite_type FROM public RESTRICT");
+      stmt.executeUpdate("GRANT USAGE on TYPE " + schema + "." + "usage_granted_composite_type TO "
+          + TestUtil.getUser());
+      stmt.executeUpdate(
+          "REVOKE ALL ON TYPE " + schema + "."
+            + "usage_granted_us_postal_code_domain FROM public RESTRICT");
+      stmt.executeUpdate(
+          "REVOKE ALL ON TYPE " + schema + "."
+            + "no_grants_us_postal_code_domain FROM public RESTRICT");
+      stmt.executeUpdate(
+          "GRANT USAGE on TYPE " + schema + "." + "usage_granted_us_postal_code_domain TO "
+            + TestUtil.getUser());
+    }
+    revokeAllOnFunctions(schema, new String[]{"execute_granted_add_function(integer, integer)",
+        "no_grants_add_function(integer, integer)"});
+
+    revokeAllOnTables(schema,
+        new String[]{"owned_table", "all_grants_table", "insert_granted_table",
+          "select_granted_table", "no_grants_table", "select_granted_view", "no_grants_view"});
+
+    stmt.executeUpdate(
+        "GRANT ALL ON FUNCTION " + schema + "."
+          + "execute_granted_add_function(integer, integer) TO "
+          + TestUtil.getUser());
+
+    if (pgConnection.haveMinimumServerVersion(ServerVersion.v11)) {
+      revokeAllOnProcedures(schema, new String[]{"execute_granted_insert_procedure(integer, integer)",
+          "no_grants_insert_procedure(integer, integer)"});
+      stmt.executeUpdate(
+          "GRANT ALL ON PROCEDURE " + schema + "."
+            + "execute_granted_insert_procedure(integer, integer) TO "
+            + TestUtil.getUser());
+
+    }
+    stmt.executeUpdate(
+          "ALTER TABLE " + schema + "." + "owned_table OWNER TO " + TestUtil.getUser());
+    stmt.executeUpdate(
+        "GRANT ALL ON TABLE " + schema + "." + "all_grants_table TO " + TestUtil.getUser());
+    stmt.executeUpdate("GRANT INSERT ON TABLE " + schema + "." + "insert_granted_table TO "
+        + TestUtil.getUser());
+    stmt.executeUpdate("GRANT SELECT ON TABLE " + schema + "." + "select_granted_table TO "
+        + TestUtil.getUser());
+    stmt.executeUpdate("GRANT SELECT ON TABLE " + schema + "." + "select_granted_view TO "
+        + TestUtil.getUser());
+    stmt.close();
+  }
+
+  private static void revokeAllOnProcedures(String schema, String[] procedures
+  ) throws SQLException {
+    Statement stmt = privilegedCon.createStatement();
+    for (String procedure : procedures) {
+      stmt.executeUpdate(
+          "REVOKE ALL ON PROCEDURE " + schema + "." + procedure + " FROM public RESTRICT");
+      stmt.executeUpdate(
+          "REVOKE ALL ON PROCEDURE  " + schema + "." + procedure + " FROM " + TestUtil.getUser()
+            + " RESTRICT");
+    }
+    stmt.close();
+  }
+
+  private static void revokeAllOnFunctions(String schema, String[] functions
+  ) throws SQLException {
+    Statement stmt = privilegedCon.createStatement();
+    for (String function : functions) {
+      stmt.executeUpdate(
+          "REVOKE ALL ON FUNCTION " + schema + "." + function + " FROM public RESTRICT");
+      stmt.executeUpdate("REVOKE ALL ON FUNCTION  " + schema + "."
+            + function + " FROM " + TestUtil.getUser()
+            + " RESTRICT");
+    }
+    stmt.close();
+  }
+
+  private static void revokeAllOnTables(String schema, String[] tables
+  ) throws SQLException {
+    Statement stmt = privilegedCon.createStatement();
+    for (String table : tables) {
+      stmt.executeUpdate("REVOKE ALL ON TABLE " + schema + "." + table + " FROM public RESTRICT");
+      stmt.executeUpdate(
+          "REVOKE ALL ON TABLE  " + schema + "." + table + " FROM " + TestUtil.getUser()
+          +   " RESTRICT");
+    }
+    stmt.close();
+  }
+
+  private static void createSimpleTablesInSchema(String schema, String[] tables
+  ) throws SQLException {
+    for (String tableName : tables) {
+      TestUtil.createTable(privilegedCon, schema + "." + tableName, COLUMNS);
+    }
+  }
+
+  @AfterClass
+  public static void tearDown() throws SQLException {
+    TestUtil.closeDB(hidingCon);
+    TestUtil.closeDB(nonhidingCon);
+    TestUtil.dropSchema(privilegedCon, "high_privileges_schema");
+    TestUtil.dropSchema(privilegedCon, "low_privileges_schema");
+    TestUtil.dropSchema(privilegedCon, "no_privileges_schema");
+    TestUtil.closeDB(privilegedCon);
+  }
+
+  private static boolean isSuperUser(Connection connection) throws SQLException {
+    // Check if we're operating as a superuser.
+    Statement st = connection.createStatement();
+    st.executeQuery("SHOW is_superuser;");
+    ResultSet rs = st.getResultSet();
+    rs.next(); // One row is guaranteed
+    boolean connIsSuper = rs.getString(1).equalsIgnoreCase("on");
+    st.close();
+    return connIsSuper;
+  }
+
+  @Test
+  public void testGetSchemas() throws SQLException {
+    List<String> schemasWithHiding = getSchemaNames(hidingDatabaseMetaData);
+    assertThat(schemasWithHiding,
+        hasItems("pg_catalog", "information_schema",
+        "high_privileges_schema", "low_privileges_schema"));
+    assertThat(schemasWithHiding,
+        not(hasItem("no_privileges_schema")));
+
+    List<String> schemasWithNoHiding = getSchemaNames(nonhidingDatabaseMetaData);
+    assertThat(schemasWithNoHiding,
+        hasItems("pg_catalog", "information_schema",
+        "high_privileges_schema", "low_privileges_schema", "no_privileges_schema"));
+  }
+
+  List<String> getSchemaNames(DatabaseMetaData databaseMetaData) throws SQLException {
+    List<String> schemaNames = new ArrayList<String>();
+    ResultSet rs = databaseMetaData.getSchemas();
+    while (rs.next()) {
+      schemaNames.add(rs.getString("TABLE_SCHEM"));
+    }
+    return schemaNames;
+  }
+
+  @Test
+  public void testGetTables() throws SQLException {
+    List<String> tablesWithHiding = getTableNames(hidingDatabaseMetaData, "high_privileges_schema");
+
+    assertThat(tablesWithHiding,
+        hasItems(
+        "owned_table",
+        "all_grants_table",
+        "insert_granted_table",
+        "select_granted_table"));
+    assertThat(tablesWithHiding,
+        not(hasItem("no_grants_table")));
+
+    List<String> tablesWithNoHiding =
+        getTableNames(nonhidingDatabaseMetaData, "high_privileges_schema");
+    assertThat(tablesWithNoHiding,
+        hasItems(
+        "owned_table",
+        "all_grants_table",
+        "insert_granted_table",
+        "select_granted_table",
+        "no_grants_table"));
+
+    tablesWithHiding = getTableNames(hidingDatabaseMetaData, "low_privileges_schema");
+
+    assertThat(tablesWithHiding,
+        hasItems(
+        "owned_table",
+        "all_grants_table",
+        "insert_granted_table",
+        "select_granted_table"));
+    assertThat(tablesWithHiding,
+        not(hasItem("no_grants_table")));
+
+    tablesWithNoHiding =
+        getTableNames(nonhidingDatabaseMetaData, "low_privileges_schema");
+    assertThat(tablesWithNoHiding,
+        hasItems(
+        "owned_table",
+        "all_grants_table",
+        "insert_granted_table",
+        "select_granted_table",
+        "no_grants_table"));
+
+    // Or should the the tables names not be returned because the schema is not visible?
+    tablesWithHiding = getTableNames(hidingDatabaseMetaData, "no_privileges_schema");
+
+    assertThat(tablesWithHiding,
+        hasItems(
+        "owned_table",
+        "all_grants_table",
+        "insert_granted_table",
+        "select_granted_table"));
+    assertThat(tablesWithHiding,
+        not(hasItem("no_grants_table")));
+
+    tablesWithNoHiding =
+        getTableNames(nonhidingDatabaseMetaData, "no_privileges_schema");
+    assertThat(tablesWithNoHiding,
+        hasItems(
+        "owned_table",
+        "all_grants_table",
+        "insert_granted_table",
+        "select_granted_table",
+        "no_grants_table"));
+
+  }
+
+  List<String> getTableNames(DatabaseMetaData databaseMetaData, String schemaPattern)
+      throws SQLException {
+    List<String> tableNames = new ArrayList<String>();
+    ResultSet rs = databaseMetaData.getTables(null, schemaPattern, null, new String[]{"TABLE"});
+    while (rs.next()) {
+      tableNames.add(rs.getString("TABLE_NAME"));
+    }
+    return tableNames;
+  }
+
+  @Test
+  public void testGetViews() throws SQLException {
+    List<String> viewsWithHiding = getViewNames(hidingDatabaseMetaData, "high_privileges_schema");
+
+    assertThat(viewsWithHiding,
+        hasItems(
+        "select_granted_view"));
+    assertThat(viewsWithHiding,
+        not(hasItem("no_grants_view")));
+
+    List<String> viewsWithNoHiding =
+        getViewNames(nonhidingDatabaseMetaData, "high_privileges_schema");
+    assertThat(viewsWithNoHiding,
+        hasItems(
+        "select_granted_view",
+        "no_grants_view"));
+
+    viewsWithHiding = getViewNames(hidingDatabaseMetaData, "low_privileges_schema");
+
+    assertThat(viewsWithHiding,
+        hasItems(
+        "select_granted_view"));
+    assertThat(viewsWithHiding,
+        not(hasItem("no_grants_view")));
+
+    viewsWithNoHiding =
+        getViewNames(nonhidingDatabaseMetaData, "low_privileges_schema");
+    assertThat(viewsWithNoHiding,
+        hasItems(
+        "select_granted_view",
+        "no_grants_view"));
+
+    // Or should the the view names not be returned because the schema is not visible?
+    viewsWithHiding = getViewNames(hidingDatabaseMetaData, "no_privileges_schema");
+
+    assertThat(viewsWithHiding,
+        hasItems(
+        "select_granted_view"));
+    assertThat(viewsWithHiding,
+        not(hasItem("no_grants_view")));
+
+    viewsWithNoHiding =
+        getViewNames(nonhidingDatabaseMetaData, "no_privileges_schema");
+    assertThat(viewsWithNoHiding,
+        hasItems(
+        "select_granted_view",
+        "no_grants_view"));
+
+  }
+
+  List<String> getViewNames(DatabaseMetaData databaseMetaData, String schemaPattern)
+      throws SQLException {
+    List<String> viewNames = new ArrayList<String>();
+    ResultSet rs = databaseMetaData.getTables(null, schemaPattern, null, new String[]{"VIEW"});
+    while (rs.next()) {
+      viewNames.add(rs.getString("TABLE_NAME"));
+    }
+    return viewNames;
+  }
+
+  @Test
+  public void testGetFunctions() throws SQLException {
+    List<String> functionsWithHiding =
+        getFunctionNames(hidingDatabaseMetaData, "high_privileges_schema");
+    assertThat(functionsWithHiding,
+        hasItem("execute_granted_add_function"));
+    assertThat(functionsWithHiding,
+        not(hasItem("no_grants_add_function")));
+
+    List<String> functionsWithNoHiding =
+        getFunctionNames(nonhidingDatabaseMetaData, "high_privileges_schema");
+    assertThat(functionsWithNoHiding,
+        hasItems("execute_granted_add_function", "no_grants_add_function"));
+
+    functionsWithHiding =
+        getFunctionNames(hidingDatabaseMetaData, "low_privileges_schema");
+    assertThat(functionsWithHiding,
+        hasItem("execute_granted_add_function"));
+    assertThat(functionsWithHiding,
+        not(hasItem("no_grants_add_function")));
+
+    functionsWithNoHiding =
+        getFunctionNames(nonhidingDatabaseMetaData, "low_privileges_schema");
+    assertThat(functionsWithNoHiding,
+        hasItems("execute_granted_add_function", "no_grants_add_function"));
+
+    // Or should the the function names not be returned because the schema is not visible?
+    functionsWithHiding =
+        getFunctionNames(hidingDatabaseMetaData, "no_privileges_schema");
+    assertThat(functionsWithHiding,
+        hasItem("execute_granted_add_function"));
+    assertThat(functionsWithHiding,
+        not(hasItem("no_grants_add_function")));
+
+    functionsWithNoHiding =
+        getFunctionNames(nonhidingDatabaseMetaData, "no_privileges_schema");
+    assertThat(functionsWithNoHiding,
+        hasItems("execute_granted_add_function", "no_grants_add_function"));
+  }
+
+  List<String> getFunctionNames(DatabaseMetaData databaseMetaData, String schemaPattern)
+      throws SQLException {
+    List<String> functionNames = new ArrayList<String>();
+    ResultSet rs = databaseMetaData.getFunctions(null, schemaPattern, null);
+    while (rs.next()) {
+      functionNames.add(rs.getString("FUNCTION_NAME"));
+    }
+    return functionNames;
+  }
+
+  @Test
+  public void testGetProcedures() throws SQLException {
+    List<String> proceduresWithHiding =
+        getProcedureNames(hidingDatabaseMetaData, "high_privileges_schema");
+    assertThat(proceduresWithHiding,
+        hasItem("execute_granted_add_function"));
+    assertThat(proceduresWithHiding,
+        not(hasItem("no_grants_add_function")));
+
+    List<String> proceduresWithNoHiding =
+        getProcedureNames(nonhidingDatabaseMetaData, "high_privileges_schema");
+    assertThat(proceduresWithNoHiding,
+        hasItems("execute_granted_add_function", "no_grants_add_function"));
+
+    proceduresWithHiding =
+        getProcedureNames(hidingDatabaseMetaData, "low_privileges_schema");
+    assertThat(proceduresWithHiding,
+        hasItem("execute_granted_add_function"));
+    assertThat(proceduresWithHiding,
+        not(hasItem("no_grants_add_function")));
+
+    proceduresWithNoHiding =
+        getProcedureNames(nonhidingDatabaseMetaData, "low_privileges_schema");
+    assertThat(proceduresWithNoHiding,
+        hasItems("execute_granted_add_function", "no_grants_add_function"));
+
+    // Or should the the function names not be returned because the schema is not visible?
+    proceduresWithHiding =
+        getProcedureNames(hidingDatabaseMetaData, "no_privileges_schema");
+    assertThat(proceduresWithHiding,
+        hasItem("execute_granted_add_function"));
+    assertThat(proceduresWithHiding,
+        not(hasItem("no_grants_add_function")));
+
+    proceduresWithNoHiding =
+        getProcedureNames(nonhidingDatabaseMetaData, "no_privileges_schema");
+    assertThat(proceduresWithNoHiding,
+        hasItems("execute_granted_add_function", "no_grants_add_function"));
+
+  }
+
+  List<String> getProcedureNames(DatabaseMetaData databaseMetaData, String schemaPattern)
+      throws SQLException {
+    List<String> procedureNames = new ArrayList<String>();
+    ResultSet rs = databaseMetaData.getProcedures(null, schemaPattern, null);
+    while (rs.next()) {
+      procedureNames.add(rs.getString("PROCEDURE_NAME"));
+    }
+    return procedureNames;
+  }
+
+  @Test
+  /*
+   *  According to the JDBC JavaDoc, the applicable UDTs are: JAVA_OBJECT, STRUCT, or DISTINCT.
+   */
+  public void testGetUDTs() throws SQLException {
+    if (pgConnection.haveMinimumServerVersion(ServerVersion.v9_2)) {
+      List<String> typesWithHiding = getTypeNames(hidingDatabaseMetaData, "high_privileges_schema");
+      assertThat(typesWithHiding,
+          hasItems("usage_granted_composite_type", "usage_granted_us_postal_code_domain"));
+      assertThat(typesWithHiding,
+          not(hasItems("no_grants_composite_type", "no_grants_us_postal_code_domain")));
+
+      typesWithHiding = getTypeNames(hidingDatabaseMetaData, "low_privileges_schema");
+      assertThat(typesWithHiding,
+          hasItems("usage_granted_composite_type", "usage_granted_us_postal_code_domain"));
+      assertThat(typesWithHiding,
+          not(hasItems("no_grants_composite_type", "no_grants_us_postal_code_domain")));
+
+      // Or should the the types names not be returned because the schema is not visible?
+      typesWithHiding = getTypeNames(hidingDatabaseMetaData, "no_privileges_schema");
+      assertThat(typesWithHiding,
+          hasItems("usage_granted_composite_type", "usage_granted_us_postal_code_domain"));
+      assertThat(typesWithHiding,
+          not(hasItems("no_grants_composite_type", "no_grants_us_postal_code_domain")));
+    }
+
+    List<String> typesWithNoHiding =
+        getTypeNames(nonhidingDatabaseMetaData, "high_privileges_schema");
+    assertThat(typesWithNoHiding,
+        hasItems("usage_granted_composite_type", "no_grants_composite_type",
+          "usage_granted_us_postal_code_domain", "no_grants_us_postal_code_domain"));
+
+    typesWithNoHiding =
+        getTypeNames(nonhidingDatabaseMetaData, "low_privileges_schema");
+    assertThat(typesWithNoHiding,
+        hasItems("usage_granted_composite_type", "no_grants_composite_type",
+          "usage_granted_us_postal_code_domain", "no_grants_us_postal_code_domain"));
+
+    typesWithNoHiding =
+        getTypeNames(nonhidingDatabaseMetaData, "no_privileges_schema");
+    assertThat(typesWithNoHiding,
+        hasItems("usage_granted_composite_type", "no_grants_composite_type",
+          "usage_granted_us_postal_code_domain", "no_grants_us_postal_code_domain"));
+  }
+
+  /*
+  From the Postgres JDBC driver source code, we are mapping the types:
+      java.sql.Types.DISTINCT to the Postgres type:  TYPTYPE_COMPOSITE  'c'   # composite (e.g., table's rowtype)
+      java.sql.Types.STRUCT   to the Postgres type:  TYPTYPE_DOMAIN     'd'   # domain over another type
+   */
+  List<String> getTypeNames(DatabaseMetaData databaseMetaData, String schemaPattern) throws SQLException {
+    List<String> typeNames = new ArrayList<String>();
+    ResultSet rs = databaseMetaData.getUDTs(null, schemaPattern, null, null);
+    while (rs.next()) {
+      typeNames.add(rs.getString("TYPE_NAME"));
+    }
+    return typeNames;
+  }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/Jdbc4TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/Jdbc4TestSuite.java
index 29292445..7a7ac592 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/Jdbc4TestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/Jdbc4TestSuite.java
@@ -19,6 +19,7 @@ import org.junit.runners.Suite;
     BlobTest.class,
     CharacterStreamTest.class,
     ClientInfoTest.class,
+    DatabaseMetaDataHideUnprivilegedObjectsTest.class,
     DatabaseMetaDataTest.class,
     IsValidTest.class,
     JsonbTest.class,