Repository: pgjdbc/pgjdbc
Pull request #887 (2017-07-27 21:23:10)

feat: primitive arrays
Add support for primitive arrays.
This is a replacement for PR 872.

codecov-io: # [Codecov](https://codecov.io/gh/pgjdbc/pgjdbc/pull/887?src=pr&el=h1) Report
> Merging [#887](https://codecov.io/gh/pgjdbc/pgjdbc/pull/887?src=pr&el=desc) into [master](https://codecov.io/gh/pgjdbc/pgjdbc/commit/5c12da16d3aa17d299e942c4a2b3647674422920?src=pr&el=desc) will **decrease** coverage by `0.09%`.
> The diff coverage is `57.2%`.



```diff
@@             Coverage Diff             @@
##             master     #887     +/-   ##
===========================================
- Coverage     65.75%   65.65%   -0.1%     
- Complexity     3547     3564     +17     
===========================================
  Files           166      167      +1     
  Lines         15256    15481    +225     
  Branches       2477     2509     +32     
===========================================
+ Hits          10031    10164    +133     
- Misses         4047     4128     +81     
- Partials       1178     1189     +11
```

bokken: It still appears that the code coverage report is not including the new unit test class i added. How is it identifying what tests to run?
AlexElin: @bokken add your test class to the corresponding test suite
bokken: @davecramer, @vlsi, @panchenko, is there anything else needed, or can this get merged in? 

Thanks.
bokken: @vlsi and @davecramer any update on when this could get merged in?
I would like to be able to start using this without having to manage a fork.
davecramer: @bokken LGTM. @vlsi  is on vacation. He should be back in a week
bokken: Thanks @davecramer 
bokken: @vlsi, any update/eta on when this could get merged in?
bokken: @davecramer, @vlsi, any update/eta on when this could get merged in?
davecramer: @bokken Sorry, I'll commit to reviewing this on Wed. Thanks for your patience
bokken: @davecramer, @vlsi, any update/eta on when this could get merged in?
davecramer: @bokken there are some subtle semantic changes here. Specifically setObject(1, String[], Types.Array) used to fail, now it doesn't was this intentional?
bokken: @davecramer, yes that was intentional and is called out in the documentation:

https://github.com/pgjdbc/pgjdbc/pull/887/files?diff=unified#diff-b8b4949d263ba9fd44d157ae61de7584R19

The listed array types can go through setObject
davecramer: @bokken can you fix the conflict and I'll commit this. Thanks
bokken: @davecramer, @vlsi,
It might be helpful to have an `@since` javadoc tag on the new `createArrayOf` method on `PGConnection` interface. What release would this be targeted for? 42.2?

I have fixed the conflict.

Let me know if you would like me to add the `@since` doc and/or make any changes to doc or implementation around handling of `null` elements.

bokken: Which I think is effectively what current code does, right? It is simply
wrapped in an Array object.
For comparison, this seems to match ojdbc’s behavior.

On Sat, Dec 16, 2017 at 4:27 AM Dave Cramer <notifications@github.com>
wrote:

> *@davecramer* commented on this pull request.
> ------------------------------
>
> In pgjdbc/src/main/java/org/postgresql/PGConnection.java
> <https://github.com/pgjdbc/pgjdbc/pull/887#discussion_r157339142>:
>
> > @@ -21,6 +22,22 @@
>   * returned by the PostgreSQL driver implement PGConnection.
>   */
>  public interface PGConnection {
> +
> +  /**
> +   * Creates an {@link Array} wrapping <i>elements</i>. This is similar to
> +   * java.sql.Connection#
>
> createArrayOf(String, Object[]), but also provides
> +   * support for primitive arrays.
> +   *
> +   * @param typeName
> +   *          The SQL name of the type to map the <i>elements</i> to.
> +   * @param elements
> +   *          The array of objects to map.
> +   * @return An {@link Array} wrapping <i>elements</i>.
> +   * @throws SQLException If for some reason the array cannot be created.
> +   * @see java.sql.Connection#createArrayOf(String, Object[])
> +   */
> +  Array createArrayOf(String typeName, Object elements) throws SQLException;
>
> So it is possible to create a null array in postgres so seems reasonable
> to allow it
>
> select null::int[];
>  int4
> ------
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/pgjdbc/pgjdbc/pull/887#discussion_r157339142>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AC26bUiehoA_MmNpgDx0xr--J9atfHecks5tA5sPgaJpZM4Ol20_>
> .
>


Patch:
diff --git a/.travis.yml b/.travis.yml
index 5de16385..b0ee930a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,7 @@ language: java
 dist: trusty
 
 before_script:
+  - test $(grep "after_n_builds" codecov.yml | tr -d '[:space:]' | cut -d":" -f2) -eq $(grep -e "COVERAGE=Y$" .travis.yml | wc -l) || exit 1
   - export PG_DATADIR="/etc/postgresql/${PG_VERSION}/main"
   -  ./.travis/travis_install_postgres.sh
   - test "x$XA" == 'x' || ./.travis/travis_configure_xa.sh
@@ -67,7 +68,7 @@ matrix:
     - env: # REMOVE FAILURE extendedCacheEverything
         - PG_VERSION=9.4
         - QUERY_MODE=extendedCacheEverything
-        - COVERAGE=Y
+        - COVERAGE=N
   include:
     - jdk: oraclejdk8
       env: RUN_CHECKSTYLE=true
@@ -121,7 +122,7 @@ matrix:
       env:
         - PG_VERSION=9.4
         - QUERY_MODE=extendedCacheEverything
-        - COVERAGE=Y
+        - COVERAGE=N
     - jdk: openjdk7
       sudo: required
       addons:
@@ -130,6 +131,7 @@ matrix:
         - PG_VERSION=9.2
         - ZULU_JDK=7
         - MCENTRAL=Y
+        - COVERAGE=Y
     - jdk: openjdk6
       sudo: required
       addons:
diff --git a/docs/_config.yml b/docs/_config.yml
index fdb49ed6..58b50f7c 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,5 +1,8 @@
 name: PostgreSQL JDBC Driver website
 markdown: redcarpet
+redcarpet:
+  extensions:
+  - tables
 highlighter: pygments
 excerpt_separator: <!--more-->
 exclude:
diff --git a/docs/documentation/head/arrays.md b/docs/documentation/head/arrays.md
new file mode 100644
index 00000000..b74356a1
--- /dev/null
+++ b/docs/documentation/head/arrays.md
@@ -0,0 +1,29 @@
+---
+layout: default_docs
+title: Arrays
+header: Chapter 9. PostgreSQL™ Extensions to the JDBC API
+resource: media
+previoustitle: Physical and Logical replication API
+previous: replication.html
+nexttitle: Chapter 10. Using the Driver in a Multithreaded or a Servlet Environment
+next: thread.html
+---
+
+PostgreSQL™ provides robust support for array data types as column types, function arguments
+and criteria in where clauses. There are several ways to create arrays with pgjdbc.
+
+The [java.sql.Connection.createArrayOf(String, Object\[\])](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#createArrayOf-java.lang.String-java.lang.Object:A-) can be used to create an [java.sql.Array](https://docs.oracle.com/javase/8/docs/api/java/sql/Array.html) from `Object[]` instances (Note: this includes both primitive and object multi-dimensional arrays).
+A similar method `org.postgresql.PGConnection.createArrayOf(String, Object)` provides support for primitive array types.
+The `java.sql.Array` object returned from these methods can be used in other methods, such as [PreparedStatement.setArray(int, Array)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setArray-int-java.sql.Array-).
+
+Additionally, the following types of arrays can be used in `PreparedStatement.setObject` methods and will use the defined type mapping:
+
+Java Type | Default PostgreSQL™  Type
+--- | ---
+`short[]` | `int2[]`
+`int[]` | `int4[]`
+`long[]` | `int8[]`
+`float[]` | `float4[]`
+`double[]` | `float8[]`
+`boolean[]` | `bool[]`
+`String[]` | `varchar[]`
diff --git a/docs/documentation/head/ext.md b/docs/documentation/head/ext.md
index 753a1ee4..ea7cee9b 100644
--- a/docs/documentation/head/ext.md
+++ b/docs/documentation/head/ext.md
@@ -17,6 +17,7 @@ next: geometric.html
 * [Listen / Notify](listennotify.html)
 * [Server Prepared Statements](server-prepare.html)
 * [Physical and Logical replication API](replication.html)
+* [Arrays](arrays.html)
 
 PostgreSQL™ is an extensible database system. You can add your own functions to
 the server, which can then be called from queries, or even add your own data types.
diff --git a/docs/documentation/head/index.html b/docs/documentation/head/index.html
index ec9162b0..70ef62fd 100644
--- a/docs/documentation/head/index.html
+++ b/docs/documentation/head/index.html
@@ -120,6 +120,7 @@
 									<dt><a href="listennotify.html">Listen / Notify</a></dt>
 									<dt><a href="server-prepare.html">Server Prepared Statements</a></dt>
 									<dt><a href="replication.html">Physical and Logical replication API</a></dt>
+									<dt><a href="arrays.html">Arrays</a></dt>
 								</dl>
 							</dd>
 
diff --git a/docs/documentation/head/replication.md b/docs/documentation/head/replication.md
index c90caeb1..3494b24a 100644
--- a/docs/documentation/head/replication.md
+++ b/docs/documentation/head/replication.md
@@ -5,8 +5,8 @@ header: Chapter 9. PostgreSQL™ Extensions to the JDBC API
 resource: media
 previoustitle: Server Prepared Statements
 previous: server-prepare.html
-nexttitle: Chapter 10. Using the Driver in a Multithreaded or a Servlet Environment
-next: thread.html
+nexttitle: Arrays
+next: arrays.html
 ---
 
 **Table of Contents**
diff --git a/pgjdbc/src/main/java/org/postgresql/Driver.java b/pgjdbc/src/main/java/org/postgresql/Driver.java
index aad94d78..637af62c 100644
--- a/pgjdbc/src/main/java/org/postgresql/Driver.java
+++ b/pgjdbc/src/main/java/org/postgresql/Driver.java
@@ -106,7 +106,7 @@ public class Driver implements java.sql.Driver {
 
     try {
       PGProperty.USER.set(merged, System.getProperty("user.name"));
-    } catch (java.lang.SecurityException se) {
+    } catch (SecurityException se) {
       // We're just trying to set a default, so if we can't
       // it's not a big deal.
     }
@@ -200,7 +200,8 @@ public class Driver implements java.sql.Driver {
    * @throws SQLException if a database access error occurs
    * @see java.sql.Driver#connect
    */
-  public java.sql.Connection connect(String url, Properties info) throws SQLException {
+  @Override
+  public Connection connect(String url, Properties info) throws SQLException {
     // get defaults
     Properties defaults;
 
@@ -460,6 +461,7 @@ public class Driver implements java.sql.Driver {
    * @return true if this driver accepts the given URL
    * @see java.sql.Driver#acceptsURL
    */
+  @Override
   public boolean acceptsURL(String url) {
     return parseURL(url, null) != null;
   }
@@ -478,6 +480,7 @@ public class Driver implements java.sql.Driver {
    *         be an empty array if no properties are required
    * @see java.sql.Driver#getPropertyInfo
    */
+  @Override
   public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
     Properties copy = new Properties(info);
     Properties parse = parseURL(url, copy);
@@ -523,6 +526,7 @@ public class Driver implements java.sql.Driver {
    * <p>
    * For PostgreSQL, this is not yet possible, as we are not SQL92 compliant (yet).
    */
+  @Override
   public boolean jdbcCompliant() {
     return false;
   }
@@ -562,9 +566,7 @@ public class Driver implements java.sql.Driver {
       String[] addresses = l_urlServer.substring(0, slash).split(",");
       StringBuilder hosts = new StringBuilder();
       StringBuilder ports = new StringBuilder();
-      for (int addr = 0; addr < addresses.length; ++addr) {
-        String address = addresses[addr];
-
+      for (String address : addresses) {
         int portIdx = address.lastIndexOf(':');
         if (portIdx != -1 && address.lastIndexOf(']') < portIdx) {
           String portStr = address.substring(portIdx + 1);
diff --git a/pgjdbc/src/main/java/org/postgresql/PGConnection.java b/pgjdbc/src/main/java/org/postgresql/PGConnection.java
index 24560981..ec849568 100644
--- a/pgjdbc/src/main/java/org/postgresql/PGConnection.java
+++ b/pgjdbc/src/main/java/org/postgresql/PGConnection.java
@@ -13,6 +13,7 @@ import org.postgresql.largeobject.LargeObjectManager;
 import org.postgresql.replication.PGReplicationConnection;
 import org.postgresql.util.PGobject;
 
+import java.sql.Array;
 import java.sql.SQLException;
 import java.sql.Statement;
 
@@ -21,6 +22,25 @@ import java.sql.Statement;
  * returned by the PostgreSQL driver implement PGConnection.
  */
 public interface PGConnection {
+
+  /**
+   * Creates an {@link Array} wrapping <i>elements</i>. This is similar to
+   * {@link java.sql.Connection#createArrayOf(String, Object[])}, but also
+   * provides support for primitive arrays.
+   *
+   * @param typeName
+   *          The SQL name of the type to map the <i>elements</i> to.
+   *          Must not be {@code null}.
+   * @param elements
+   *          The array of objects to map. A {@code null} value will result in
+   *          an {@link Array} representing {@code null}.
+   * @return An {@link Array} wrapping <i>elements</i>.
+   * @throws SQLException
+   *           If for some reason the array cannot be created.
+   * @see java.sql.Connection#createArrayOf(String, Object[])
+   */
+  Array createArrayOf(String typeName, Object elements) throws SQLException;
+
   /**
    * This method returns any notifications that have been received since the last call to this
    * method. Returns null if there have been no notifications.
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 ccd78220..9dcc42b8 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
@@ -73,9 +73,9 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
       String spnServiceClass,
       boolean enableNegotiate) {
     try {
-      Class c = Class.forName("org.postgresql.sspi.SSPIClient");
-      Class[] cArg = new Class[]{PGStream.class, String.class, boolean.class};
-      return (ISSPIClient) c.getDeclaredConstructor(cArg)
+      @SuppressWarnings("unchecked")
+      Class<ISSPIClient> c = (Class<ISSPIClient>) Class.forName("org.postgresql.sspi.SSPIClient");
+      return c.getDeclaredConstructor(PGStream.class, String.class, boolean.class)
           .newInstance(pgStream, spnServiceClass, enableNegotiate);
     } catch (Exception e) {
       // This catched quite a lot exceptions, but until Java 7 there is no ReflectiveOperationException
@@ -84,6 +84,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
     }
   }
 
+  @Override
   public QueryExecutor openConnectionImpl(HostSpec[] hostSpecs, String user, String database,
       Properties info) throws SQLException {
     // Extract interesting values from the info properties:
@@ -106,19 +107,10 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
       }
     }
 
-    // - the TCP keep alive setting
     boolean requireTCPKeepAlive = PGProperty.TCP_KEEP_ALIVE.getBoolean(info);
 
-    // NOTE: To simplify this code, it is assumed that if we are
-    // using the V3 protocol, then the database is at least 7.4. That
-    // eliminates the need to check database versions and maintain
-    // backward-compatible code here.
-    //
-    // Change by Chris Smith <cdsmith@twu.net>
-
     int connectTimeout = PGProperty.CONNECT_TIMEOUT.getInt(info) * 1000;
 
-    // - the targetServerType setting
     HostRequirement targetServerType;
     String targetServerTypeStr = PGProperty.TARGET_SERVER_TYPE.get(info);
     try {
@@ -189,36 +181,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
         LOGGER.log(Level.FINE, "Receive Buffer Size is {0}", newStream.getSocket().getReceiveBufferSize());
         LOGGER.log(Level.FINE, "Send Buffer Size is {0}", newStream.getSocket().getSendBufferSize());
 
-        List<String[]> paramList = new ArrayList<String[]>();
-        paramList.add(new String[]{"user", user});
-        paramList.add(new String[]{"database", database});
-        paramList.add(new String[]{"client_encoding", "UTF8"});
-        paramList.add(new String[]{"DateStyle", "ISO"});
-        paramList.add(new String[]{"TimeZone", createPostgresTimeZone()});
-
-        Version assumeVersion = ServerVersion.from(PGProperty.ASSUME_MIN_SERVER_VERSION.get(info));
-
-        if (assumeVersion.getVersionNum() >= ServerVersion.v9_0.getVersionNum()) {
-          // User is explicitly telling us this is a 9.0+ server so set properties here:
-          paramList.add(new String[]{"extra_float_digits", "3"});
-          String appName = PGProperty.APPLICATION_NAME.get(info);
-          if (appName != null) {
-            paramList.add(new String[]{"application_name", appName});
-          }
-        } else {
-          // User has not explicitly told us that this is a 9.0+ server so stick to old default:
-          paramList.add(new String[]{"extra_float_digits", "2"});
-        }
-
-        String replication = PGProperty.REPLICATION.get(info);
-        if (replication != null && assumeVersion.getVersionNum() >= ServerVersion.v9_4.getVersionNum()) {
-          paramList.add(new String[]{"replication", replication});
-        }
-
-        String currentSchema = PGProperty.CURRENT_SCHEMA.get(info);
-        if (currentSchema != null) {
-          paramList.add(new String[]{"search_path", currentSchema});
-        }
+        List<String[]> paramList = getParametersForStartup(user, database, info);
         sendStartupPacket(newStream, paramList);
 
         // Do authentication (until AuthenticationOk).
@@ -290,6 +253,40 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
         PSQLState.CONNECTION_UNABLE_TO_CONNECT);
   }
 
+  private List<String[]> getParametersForStartup(String user, String database, Properties info) {
+    List<String[]> paramList = new ArrayList<String[]>();
+    paramList.add(new String[]{"user", user});
+    paramList.add(new String[]{"database", database});
+    paramList.add(new String[]{"client_encoding", "UTF8"});
+    paramList.add(new String[]{"DateStyle", "ISO"});
+    paramList.add(new String[]{"TimeZone", createPostgresTimeZone()});
+
+    Version assumeVersion = ServerVersion.from(PGProperty.ASSUME_MIN_SERVER_VERSION.get(info));
+
+    if (assumeVersion.getVersionNum() >= ServerVersion.v9_0.getVersionNum()) {
+      // User is explicitly telling us this is a 9.0+ server so set properties here:
+      paramList.add(new String[]{"extra_float_digits", "3"});
+      String appName = PGProperty.APPLICATION_NAME.get(info);
+      if (appName != null) {
+        paramList.add(new String[]{"application_name", appName});
+      }
+    } else {
+      // User has not explicitly told us that this is a 9.0+ server so stick to old default:
+      paramList.add(new String[]{"extra_float_digits", "2"});
+    }
+
+    String replication = PGProperty.REPLICATION.get(info);
+    if (replication != null && assumeVersion.getVersionNum() >= ServerVersion.v9_4.getVersionNum()) {
+      paramList.add(new String[]{"replication", replication});
+    }
+
+    String currentSchema = PGProperty.CURRENT_SCHEMA.get(info);
+    if (currentSchema != null) {
+      paramList.add(new String[]{"search_path", currentSchema});
+    }
+    return paramList;
+  }
+
   /**
    * Convert Java time zone to postgres time zone. All others stay the same except that GMT+nn
    * changes to GMT-nn and vise versa.
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
index 269ab2c7..3810081f 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
@@ -250,6 +250,9 @@ public class PgArray implements java.sql.Array {
             Encoding encoding = connection.getEncoding();
             arr[i] = encoding.decode(fieldBytes, pos, len);
             break;
+          case Oid.BOOL:
+            arr[i] = ByteConverter.bool(fieldBytes, pos);
+            break;
           default:
             ArrayAssistant arrAssistant = ArrayAssistantRegistry.getAssistant(elementOid);
             if (arrAssistant != null) {
@@ -392,6 +395,8 @@ public class PgArray implements java.sql.Array {
       case Oid.TEXT:
       case Oid.VARCHAR:
         return String.class;
+      case Oid.BOOL:
+        return Boolean.class;
       default:
         ArrayAssistant arrElemBuilder = ArrayAssistantRegistry.getAssistant(oid);
         if (arrElemBuilder != null) {
@@ -900,11 +905,17 @@ public class PgArray implements java.sql.Array {
   public String toString() {
     if (fieldString == null && fieldBytes != null) {
       try {
-        Object array = readBinaryArray(1,0);
-        java.sql.Array tmpArray = connection.createArrayOf(getBaseTypeName(), (Object[]) array);
-        fieldString = tmpArray.toString();
+        Object array = readBinaryArray(1, 0);
+
+        final PrimitiveArraySupport arraySupport = PrimitiveArraySupport.getArraySupport(array);
+        if (arraySupport != null) {
+          fieldString = arraySupport.toArrayString(connection.getTypeInfo().getArrayDelimiter(oid), array);
+        } else {
+          java.sql.Array tmpArray = connection.createArrayOf(getBaseTypeName(), (Object[]) array);
+          fieldString = tmpArray.toString();
+        }
       } catch (SQLException e) {
-        fieldString = "NULL"; //punt
+        fieldString = "NULL"; // punt
       }
     }
     return fieldString;
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
index a0911908..9a0c508e 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
@@ -1156,7 +1156,12 @@ public class PgConnection implements BaseConnection {
       if (o == null) {
         sb.append("NULL");
       } else if (o.getClass().isArray()) {
-        appendArray(sb, o, delim);
+        final PrimitiveArraySupport arraySupport = PrimitiveArraySupport.getArraySupport(o);
+        if (arraySupport != null) {
+          arraySupport.appendArray(sb, delim, o);
+        } else {
+          appendArray(sb, o, delim);
+        }
       } else {
         String s = o.toString();
         PgArray.escapeArrayElement(sb, s);
@@ -1271,6 +1276,49 @@ public class PgConnection implements BaseConnection {
     throw org.postgresql.Driver.notImplemented(this.getClass(), "createStruct(String, Object[])");
   }
 
+  @Override
+  public Array createArrayOf(String typeName, Object elements) throws SQLException {
+    checkClosed();
+
+    final TypeInfo typeInfo = getTypeInfo();
+
+    final int oid = typeInfo.getPGArrayType(typeName);
+    final char delim = typeInfo.getArrayDelimiter(oid);
+
+    if (oid == Oid.UNSPECIFIED) {
+      throw new PSQLException(GT.tr("Unable to find server array type for provided name {0}.", typeName),
+          PSQLState.INVALID_NAME);
+    }
+
+    if (elements == null) {
+      return makeArray(oid, null);
+    }
+
+    final String arrayString;
+
+    final PrimitiveArraySupport arraySupport = PrimitiveArraySupport.getArraySupport(elements);
+
+    if (arraySupport != null) {
+      // if the oid for the given type matches the default type, we might be
+      // able to go straight to binary representation
+      if (oid == arraySupport.getDefaultArrayTypeOid(typeInfo) && arraySupport.supportBinaryRepresentation()
+          && getPreferQueryMode() != PreferQueryMode.SIMPLE) {
+        return new PgArray(this, oid, arraySupport.toBinaryRepresentation(this, elements));
+      }
+      arrayString = arraySupport.toArrayString(delim, elements);
+    } else {
+      final Class<?> clazz = elements.getClass();
+      if (!clazz.isArray()) {
+        throw new PSQLException(GT.tr("Invalid elements {0}", elements), PSQLState.INVALID_PARAMETER_TYPE);
+      }
+      StringBuilder sb = new StringBuilder();
+      appendArray(sb, elements, delim);
+      arrayString = sb.toString();
+    }
+
+    return makeArray(oid, arrayString);
+  }
+
   @Override
   public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
     checkClosed();
@@ -1283,6 +1331,10 @@ public class PgConnection implements BaseConnection {
           PSQLState.INVALID_NAME);
     }
 
+    if (elements == null) {
+      return makeArray(oid, null);
+    }
+
     char delim = getTypeInfo().getArrayDelimiter(oid);
     StringBuilder sb = new StringBuilder();
     appendArray(sb, elements, delim);
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
index 28319be3..db0a3782 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
@@ -13,6 +13,7 @@ import org.postgresql.core.ParameterList;
 import org.postgresql.core.Query;
 import org.postgresql.core.QueryExecutor;
 import org.postgresql.core.ServerVersion;
+import org.postgresql.core.TypeInfo;
 import org.postgresql.core.v3.BatchedQuery;
 import org.postgresql.largeobject.LargeObject;
 import org.postgresql.largeobject.LargeObjectManager;
@@ -656,6 +657,8 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
       case Types.ARRAY:
         if (in instanceof Array) {
           setArray(parameterIndex, (Array) in);
+        } else if (PrimitiveArraySupport.isSupportedPrimitiveArray(in)) {
+          setPrimitiveArray(parameterIndex, in);
         } else {
           throw new PSQLException(
               GT.tr("Cannot cast an instance of {0} to type {1}",
@@ -681,6 +684,21 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
     }
   }
 
+  private <A> void setPrimitiveArray(int parameterIndex, A in) throws SQLException {
+    final PrimitiveArraySupport<A> arrayToString = PrimitiveArraySupport.getArraySupport(in);
+
+    final TypeInfo typeInfo = connection.getTypeInfo();
+
+    final int oid = arrayToString.getDefaultArrayTypeOid(typeInfo);
+
+    if (arrayToString.supportBinaryRepresentation() && connection.getPreferQueryMode() != PreferQueryMode.SIMPLE) {
+      bindBytes(parameterIndex, arrayToString.toBinaryRepresentation(connection, in), oid);
+    } else {
+      final char delim = typeInfo.getArrayDelimiter(oid);
+      setString(parameterIndex, arrayToString.toArrayString(delim, in), oid);
+    }
+  }
+
   private static String asString(final Clob in) throws SQLException {
     return in.getSubString(1, (int) in.length());
   }
@@ -942,6 +960,8 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
       setMap(parameterIndex, (Map<?, ?>) x);
     } else if (x instanceof Number) {
       setNumber(parameterIndex, (Number) x);
+    } else if (PrimitiveArraySupport.isSupportedPrimitiveArray(x)) {
+      setPrimitiveArray(parameterIndex, x);
     } else {
       // Can't infer a type.
       throw new PSQLException(GT.tr(
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PrimitiveArraySupport.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PrimitiveArraySupport.java
new file mode 100644
index 00000000..b92f01be
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PrimitiveArraySupport.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright (c) 2004, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.core.Oid;
+import org.postgresql.core.TypeInfo;
+import org.postgresql.util.ByteConverter;
+
+import java.sql.Connection;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.HashMap;
+import java.util.Map;
+
+abstract class PrimitiveArraySupport<A> {
+
+  public abstract int getDefaultArrayTypeOid(TypeInfo tiCache);
+
+  public abstract String toArrayString(char delim, A array);
+
+  public abstract void appendArray(StringBuilder sb, char delim, A array);
+
+  public boolean supportBinaryRepresentation() {
+    return true;
+  }
+
+  public abstract byte[] toBinaryRepresentation(Connection connection, A array) throws SQLFeatureNotSupportedException;
+
+  private static final PrimitiveArraySupport<long[]> LONG_ARRAY = new PrimitiveArraySupport<long[]>() {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getDefaultArrayTypeOid(TypeInfo tiCache) {
+      return Oid.INT8_ARRAY;
+    }
+
+    @Override
+    public String toArrayString(char delim, long[] array) {
+      final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
+      appendArray(sb, delim, array);
+      return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void appendArray(StringBuilder sb, char delim, long[] array) {
+      sb.append('{');
+      for (int i = 0; i < array.length; ++i) {
+        if (i > 0) {
+          sb.append(delim);
+        }
+        sb.append(array[i]);
+      }
+      sb.append('}');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public byte[] toBinaryRepresentation(Connection connection, long[] array) {
+
+      int length = 20 + (12 * array.length);
+      final byte[] bytes = new byte[length];
+
+      // 1 dimension
+      ByteConverter.int4(bytes, 0, 1);
+      // no null
+      ByteConverter.int4(bytes, 4, 0);
+      // oid
+      ByteConverter.int4(bytes, 8, Oid.INT8);
+      // length
+      ByteConverter.int4(bytes, 12, array.length);
+
+      int idx = 20;
+      for (int i = 0; i < array.length; ++i) {
+        bytes[idx + 3] = 8;
+        ByteConverter.int8(bytes, idx + 4, array[i]);
+        idx += 12;
+      }
+
+      return bytes;
+    }
+  };
+
+  private static final PrimitiveArraySupport<int[]> INT_ARRAY = new PrimitiveArraySupport<int[]>() {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getDefaultArrayTypeOid(TypeInfo tiCache) {
+      return Oid.INT4_ARRAY;
+    }
+
+    @Override
+    public String toArrayString(char delim, int[] array) {
+      final StringBuilder sb = new StringBuilder(Math.max(32, array.length * 6));
+      appendArray(sb, delim, array);
+      return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void appendArray(StringBuilder sb, char delim, int[] array) {
+      sb.append('{');
+      for (int i = 0; i < array.length; ++i) {
+        if (i > 0) {
+          sb.append(delim);
+        }
+        sb.append(array[i]);
+      }
+      sb.append('}');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public byte[] toBinaryRepresentation(Connection connection, int[] array) {
+
+      int length = 20 + (8 * array.length);
+      final byte[] bytes = new byte[length];
+
+      // 1 dimension
+      ByteConverter.int4(bytes, 0, 1);
+      // no null
+      ByteConverter.int4(bytes, 4, 0);
+      // oid
+      ByteConverter.int4(bytes, 8, Oid.INT4);
+      // length
+      ByteConverter.int4(bytes, 12, array.length);
+
+      int idx = 20;
+      for (int i = 0; i < array.length; ++i) {
+        bytes[idx + 3] = 4;
+        ByteConverter.int4(bytes, idx + 4, array[i]);
+        idx += 8;
+      }
+
+      return bytes;
+    }
+  };
+
+  private static final PrimitiveArraySupport<short[]> SHORT_ARRAY = new PrimitiveArraySupport<short[]>() {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getDefaultArrayTypeOid(TypeInfo tiCache) {
+      return Oid.INT2_ARRAY;
+    }
+
+    @Override
+    public String toArrayString(char delim, short[] array) {
+      final StringBuilder sb = new StringBuilder(Math.max(32, array.length * 4));
+      appendArray(sb, delim, array);
+      return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void appendArray(StringBuilder sb, char delim, short[] array) {
+      sb.append('{');
+      for (int i = 0; i < array.length; ++i) {
+        if (i > 0) {
+          sb.append(delim);
+        }
+        sb.append(array[i]);
+      }
+      sb.append('}');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public byte[] toBinaryRepresentation(Connection connection, short[] array) {
+
+      int length = 20 + (6 * array.length);
+      final byte[] bytes = new byte[length];
+
+      // 1 dimension
+      ByteConverter.int4(bytes, 0, 1);
+      // no null
+      ByteConverter.int4(bytes, 4, 0);
+      // oid
+      ByteConverter.int4(bytes, 8, Oid.INT2);
+      // length
+      ByteConverter.int4(bytes, 12, array.length);
+
+      int idx = 20;
+      for (int i = 0; i < array.length; ++i) {
+        bytes[idx + 3] = 2;
+        ByteConverter.int2(bytes, idx + 4, array[i]);
+        idx += 6;
+      }
+
+      return bytes;
+    }
+
+  };
+
+  private static final PrimitiveArraySupport<double[]> DOUBLE_ARRAY = new PrimitiveArraySupport<double[]>() {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getDefaultArrayTypeOid(TypeInfo tiCache) {
+      return Oid.FLOAT8_ARRAY;
+    }
+
+    @Override
+    public String toArrayString(char delim, double[] array) {
+      final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
+      appendArray(sb, delim, array);
+      return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void appendArray(StringBuilder sb, char delim, double[] array) {
+      sb.append('{');
+      for (int i = 0; i < array.length; ++i) {
+        if (i > 0) {
+          sb.append(delim);
+        }
+        // use quotes to account for any issues with scientific notation
+        sb.append('"');
+        sb.append(array[i]);
+        sb.append('"');
+      }
+      sb.append('}');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public byte[] toBinaryRepresentation(Connection connection, double[] array) {
+
+      int length = 20 + (12 * array.length);
+      final byte[] bytes = new byte[length];
+
+      // 1 dimension
+      ByteConverter.int4(bytes, 0, 1);
+      // no null
+      ByteConverter.int4(bytes, 4, 0);
+      // oid
+      ByteConverter.int4(bytes, 8, Oid.FLOAT8);
+      // length
+      ByteConverter.int4(bytes, 12, array.length);
+
+      int idx = 20;
+      for (int i = 0; i < array.length; ++i) {
+        bytes[idx + 3] = 8;
+        ByteConverter.float8(bytes, idx + 4, array[i]);
+        idx += 12;
+      }
+
+      return bytes;
+    }
+
+  };
+
+  private static final PrimitiveArraySupport<float[]> FLOAT_ARRAY = new PrimitiveArraySupport<float[]>() {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getDefaultArrayTypeOid(TypeInfo tiCache) {
+      return Oid.FLOAT4_ARRAY;
+    }
+
+    @Override
+    public String toArrayString(char delim, float[] array) {
+      final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
+      appendArray(sb, delim, array);
+      return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void appendArray(StringBuilder sb, char delim, float[] array) {
+      sb.append('{');
+      for (int i = 0; i < array.length; ++i) {
+        if (i > 0) {
+          sb.append(delim);
+        }
+        // use quotes to account for any issues with scientific notation
+        sb.append('"');
+        sb.append(array[i]);
+        sb.append('"');
+      }
+      sb.append('}');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public byte[] toBinaryRepresentation(Connection connection, float[] array) {
+
+      int length = 20 + (8 * array.length);
+      final byte[] bytes = new byte[length];
+
+      // 1 dimension
+      ByteConverter.int4(bytes, 0, 1);
+      // no null
+      ByteConverter.int4(bytes, 4, 0);
+      // oid
+      ByteConverter.int4(bytes, 8, Oid.FLOAT4);
+      // length
+      ByteConverter.int4(bytes, 12, array.length);
+
+      int idx = 20;
+      for (int i = 0; i < array.length; ++i) {
+        bytes[idx + 3] = 4;
+        ByteConverter.float4(bytes, idx + 4, array[i]);
+        idx += 8;
+      }
+
+      return bytes;
+    }
+
+  };
+
+  private static final PrimitiveArraySupport<boolean[]> BOOLEAN_ARRAY = new PrimitiveArraySupport<boolean[]>() {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getDefaultArrayTypeOid(TypeInfo tiCache) {
+      return Oid.BOOL_ARRAY;
+    }
+
+    @Override
+    public String toArrayString(char delim, boolean[] array) {
+      final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
+      appendArray(sb, delim, array);
+      return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void appendArray(StringBuilder sb, char delim, boolean[] array) {
+      sb.append('{');
+      for (int i = 0; i < array.length; ++i) {
+        if (i > 0) {
+          sb.append(delim);
+        }
+        sb.append(array[i] ? '1' : '0');
+      }
+      sb.append('}');
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws SQLFeatureNotSupportedException
+     *           Because this feature is not supported.
+     */
+    @Override
+    public byte[] toBinaryRepresentation(Connection connection, boolean[] array) throws SQLFeatureNotSupportedException {
+      int length = 20 + (5 * array.length);
+      final byte[] bytes = new byte[length];
+
+      // 1 dimension
+      ByteConverter.int4(bytes, 0, 1);
+      // no null
+      ByteConverter.int4(bytes, 4, 0);
+      // oid
+      ByteConverter.int4(bytes, 8, Oid.BOOL);
+      // length
+      ByteConverter.int4(bytes, 12, array.length);
+
+      int idx = 20;
+      for (int i = 0; i < array.length; ++i) {
+        bytes[idx + 3] = 1;
+        ByteConverter.bool(bytes, idx + 4, array[i]);
+        idx += 5;
+      }
+
+      return bytes;
+    }
+
+  };
+
+  private static final PrimitiveArraySupport<String[]> STRING_ARRAY = new PrimitiveArraySupport<String[]>() {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getDefaultArrayTypeOid(TypeInfo tiCache) {
+      return Oid.VARCHAR_ARRAY;
+    }
+
+    @Override
+    public String toArrayString(char delim, String[] array) {
+      final StringBuilder sb = new StringBuilder(Math.max(64, array.length * 8));
+      appendArray(sb, delim, array);
+      return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void appendArray(StringBuilder sb, char delim, String[] array) {
+      sb.append('{');
+      for (int i = 0; i < array.length; ++i) {
+        if (i > 0) {
+          sb.append(delim);
+        }
+        if (array[i] == null) {
+          sb.append('N');
+          sb.append('U');
+          sb.append('L');
+          sb.append('L');
+        } else {
+          PgArray.escapeArrayElement(sb, array[i]);
+        }
+      }
+      sb.append('}');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean supportBinaryRepresentation() {
+      return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws SQLFeatureNotSupportedException
+     *           Because this feature is not supported.
+     */
+    @Override
+    public byte[] toBinaryRepresentation(Connection connection, String[] array) throws SQLFeatureNotSupportedException {
+      throw new SQLFeatureNotSupportedException();
+    }
+
+  };
+
+  private static final Map<Class, PrimitiveArraySupport> ARRAY_CLASS_TO_SUPPORT = new HashMap<Class, PrimitiveArraySupport>((int) (7 / .75) + 1);
+
+  static {
+    ARRAY_CLASS_TO_SUPPORT.put(long[].class, LONG_ARRAY);
+    ARRAY_CLASS_TO_SUPPORT.put(int[].class, INT_ARRAY);
+    ARRAY_CLASS_TO_SUPPORT.put(short[].class, SHORT_ARRAY);
+    ARRAY_CLASS_TO_SUPPORT.put(double[].class, DOUBLE_ARRAY);
+    ARRAY_CLASS_TO_SUPPORT.put(float[].class, FLOAT_ARRAY);
+    ARRAY_CLASS_TO_SUPPORT.put(boolean[].class, BOOLEAN_ARRAY);
+    ARRAY_CLASS_TO_SUPPORT.put(String[].class, STRING_ARRAY);
+  }
+
+  public static boolean isSupportedPrimitiveArray(Object obj) {
+    return obj != null && ARRAY_CLASS_TO_SUPPORT.containsKey(obj.getClass());
+  }
+
+  public static <A> PrimitiveArraySupport<A> getArraySupport(A array) {
+    return ARRAY_CLASS_TO_SUPPORT.get(array.getClass());
+  }
+}
diff --git a/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java b/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
index 73e56d61..5464236c 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
@@ -61,6 +61,19 @@ public class ByteConverter {
     return (short) (((bytes[idx] & 255) << 8) + ((bytes[idx + 1] & 255)));
   }
 
+  /**
+   * Parses a boolean value from the byte array.
+   *
+   * @param bytes
+   *          The byte array to parse.
+   * @param idx
+   *          The starting index to read from bytes.
+   * @return parsed boolean value.
+   */
+  public static boolean bool(byte[] bytes, int idx) {
+    return bytes[idx] == 1;
+  }
+
   /**
    * Parses a float value from the byte array.
    *
@@ -127,6 +140,20 @@ public class ByteConverter {
     target[idx + 1] = (byte) value;
   }
 
+  /**
+   * Encodes a boolean value to the byte array.
+   *
+   * @param target
+   *          The byte array to encode to.
+   * @param idx
+   *          The starting index in the byte array.
+   * @param value
+   *          The value to encode.
+   */
+  public static void bool(byte[] target, int idx, boolean value) {
+    target[idx] = value ? (byte) 1 : (byte) 0;
+  }
+
   /**
    * Encodes a int value to the byte array.
    *


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/PrimitiveArraySupportTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/PrimitiveArraySupportTest.java
new file mode 100644
index 00000000..83b028bb
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/jdbc/PrimitiveArraySupportTest.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2003, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import org.postgresql.core.Oid;
+
+import org.junit.Test;
+
+import java.sql.SQLFeatureNotSupportedException;
+
+public class PrimitiveArraySupportTest {
+
+  public PrimitiveArraySupport<long[]> longArrays = PrimitiveArraySupport.getArraySupport(new long[] {});
+  public PrimitiveArraySupport<int[]> intArrays = PrimitiveArraySupport.getArraySupport(new int[] {});
+  public PrimitiveArraySupport<short[]> shortArrays = PrimitiveArraySupport.getArraySupport(new short[] {});
+  public PrimitiveArraySupport<double[]> doubleArrays = PrimitiveArraySupport.getArraySupport(new double[] {});
+  public PrimitiveArraySupport<float[]> floatArrays = PrimitiveArraySupport.getArraySupport(new float[] {});
+  public PrimitiveArraySupport<boolean[]> booleanArrays = PrimitiveArraySupport.getArraySupport(new boolean[] {});
+
+  @Test
+  public void testLongBinary() throws Exception {
+    final long[] longs = new long[84];
+    for (int i = 0; i < 84; ++i) {
+      longs[i] = i - 3;
+    }
+
+    final PgArray pgArray = new PgArray(null, Oid.INT8_ARRAY, longArrays.toBinaryRepresentation(null, longs));
+
+    Object arrayObj = pgArray.getArray();
+
+    assertThat(arrayObj, instanceOf(Long[].class));
+
+    final Long[] actual = (Long[]) arrayObj;
+
+    assertEquals(longs.length, actual.length);
+
+    for (int i = 0; i < longs.length; ++i) {
+      assertEquals(Long.valueOf(longs[i]), actual[i]);
+    }
+  }
+
+  @Test
+  public void testLongToString() throws Exception {
+    final long[] longs = new long[] { 12367890987L, 987664198234L, -2982470923874L };
+
+    final String arrayString = longArrays.toArrayString(',', longs);
+
+    assertEquals("{12367890987,987664198234,-2982470923874}", arrayString);
+
+    final String altArrayString = longArrays.toArrayString(';', longs);
+
+    assertEquals("{12367890987;987664198234;-2982470923874}", altArrayString);
+  }
+
+  @Test
+  public void testIntBinary() throws Exception {
+    final int[] ints = new int[13];
+    for (int i = 0; i < 13; ++i) {
+      ints[i] = i - 3;
+    }
+
+    final PgArray pgArray = new PgArray(null, Oid.INT4_ARRAY, intArrays.toBinaryRepresentation(null, ints));
+
+    Object arrayObj = pgArray.getArray();
+
+    assertThat(arrayObj, instanceOf(Integer[].class));
+
+    final Integer[] actual = (Integer[]) arrayObj;
+
+    assertEquals(ints.length, actual.length);
+
+    for (int i = 0; i < ints.length; ++i) {
+      assertEquals(Integer.valueOf(ints[i]), actual[i]);
+    }
+  }
+
+  @Test
+  public void testIntToString() throws Exception {
+    final int[] ints = new int[] { 12367890, 987664198, -298247092 };
+
+    final String arrayString = intArrays.toArrayString(',', ints);
+
+    assertEquals("{12367890,987664198,-298247092}", arrayString);
+
+    final String altArrayString = intArrays.toArrayString(';', ints);
+
+    assertEquals("{12367890;987664198;-298247092}", altArrayString);
+
+  }
+
+  @Test
+  public void testShortToBinary() throws Exception {
+    final short[] shorts = new short[13];
+    for (int i = 0; i < 13; ++i) {
+      shorts[i] = (short) (i - 3);
+    }
+
+    final PgArray pgArray = new PgArray(null, Oid.INT4_ARRAY, shortArrays.toBinaryRepresentation(null, shorts));
+
+    Object arrayObj = pgArray.getArray();
+
+    assertThat(arrayObj, instanceOf(Short[].class));
+
+    final Short[] actual = (Short[]) arrayObj;
+
+    assertEquals(shorts.length, actual.length);
+
+    for (int i = 0; i < shorts.length; ++i) {
+      assertEquals(Short.valueOf(shorts[i]), actual[i]);
+    }
+  }
+
+  @Test
+  public void testShortToString() throws Exception {
+    final short[] shorts = new short[] { 123, 34, -57 };
+
+    final String arrayString = shortArrays.toArrayString(',', shorts);
+
+    assertEquals("{123,34,-57}", arrayString);
+
+    final String altArrayString = shortArrays.toArrayString(';', shorts);
+
+    assertEquals("{123;34;-57}", altArrayString);
+
+  }
+
+  @Test
+  public void testDoubleBinary() throws Exception {
+    final double[] doubles = new double[13];
+    for (int i = 0; i < 13; ++i) {
+      doubles[i] = i - 3.1;
+    }
+
+    final PgArray pgArray = new PgArray(null, Oid.FLOAT8_ARRAY, doubleArrays.toBinaryRepresentation(null, doubles));
+
+    Object arrayObj = pgArray.getArray();
+
+    assertThat(arrayObj, instanceOf(Double[].class));
+
+    final Double[] actual = (Double[]) arrayObj;
+
+    assertEquals(doubles.length, actual.length);
+
+    for (int i = 0; i < doubles.length; ++i) {
+      assertEquals(Double.valueOf(doubles[i]), actual[i]);
+    }
+  }
+
+  @Test
+  public void testdoubleToString() throws Exception {
+    final double[] doubles = new double[] { 122353.345, 923487.235987, -23.239486 };
+
+    final String arrayString = doubleArrays.toArrayString(',', doubles);
+
+    assertEquals("{\"122353.345\",\"923487.235987\",\"-23.239486\"}", arrayString);
+
+    final String altArrayString = doubleArrays.toArrayString(';', doubles);
+
+    assertEquals("{\"122353.345\";\"923487.235987\";\"-23.239486\"}", altArrayString);
+
+  }
+
+  @Test
+  public void testFloatBinary() throws Exception {
+    final float[] floats = new float[13];
+    for (int i = 0; i < 13; ++i) {
+      floats[i] = (float) (i - 3.1);
+    }
+
+    final PgArray pgArray = new PgArray(null, Oid.FLOAT4_ARRAY, floatArrays.toBinaryRepresentation(null, floats));
+
+    Object arrayObj = pgArray.getArray();
+
+    assertThat(arrayObj, instanceOf(Float[].class));
+
+    final Float[] actual = (Float[]) arrayObj;
+
+    assertEquals(floats.length, actual.length);
+
+    for (int i = 0; i < floats.length; ++i) {
+      assertEquals(Float.valueOf(floats[i]), actual[i]);
+    }
+  }
+
+  @Test
+  public void testfloatToString() throws Exception {
+    final float[] floats = new float[] { 122353.34f, 923487.25f, -23.2394f };
+
+    final String arrayString = floatArrays.toArrayString(',', floats);
+
+    assertEquals("{\"122353.34\",\"923487.25\",\"-23.2394\"}", arrayString);
+
+    final String altArrayString = floatArrays.toArrayString(';', floats);
+
+    assertEquals("{\"122353.34\";\"923487.25\";\"-23.2394\"}", altArrayString);
+
+  }
+
+  @Test
+  public void testBooleanBinary() throws Exception {
+    final boolean[] bools = new boolean[] { true, true, false };
+
+    final PgArray pgArray = new PgArray(null, Oid.BIT, booleanArrays.toBinaryRepresentation(null, bools));
+
+    Object arrayObj = pgArray.getArray();
+
+    assertThat(arrayObj, instanceOf(Boolean[].class));
+
+    final Boolean[] actual = (Boolean[]) arrayObj;
+
+    assertEquals(bools.length, actual.length);
+
+    for (int i = 0; i < bools.length; ++i) {
+      assertEquals(Boolean.valueOf(bools[i]), actual[i]);
+    }
+  }
+
+  @Test
+  public void testBooleanToString() throws Exception {
+    final boolean[] bools = new boolean[] { true, true, false };
+
+    final String arrayString = booleanArrays.toArrayString(',', bools);
+
+    assertEquals("{1,1,0}", arrayString);
+
+    final String altArrayString = booleanArrays.toArrayString(';', bools);
+
+    assertEquals("{1;1;0}", altArrayString);
+  }
+
+  @Test
+  public void testStringNotSupportBinary() {
+    PrimitiveArraySupport<String[]> stringArrays = PrimitiveArraySupport.getArraySupport(new String[] {});
+    assertNotNull(stringArrays);
+    assertFalse(stringArrays.supportBinaryRepresentation());
+    try {
+      stringArrays.toBinaryRepresentation(null, new String[] { "1.2" });
+      fail("no sql exception thrown");
+    } catch (SQLFeatureNotSupportedException e) {
+
+    }
+  }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ArrayTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ArrayTest.java
index 8aa30d0c..8e1874c9 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ArrayTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ArrayTest.java
@@ -5,12 +5,17 @@
 
 package org.postgresql.test.jdbc2;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.postgresql.PGConnection;
 import org.postgresql.core.BaseConnection;
 import org.postgresql.geometric.PGbox;
 import org.postgresql.geometric.PGpoint;
 import org.postgresql.jdbc.PgArray;
 import org.postgresql.jdbc.PreferQueryMode;
 import org.postgresql.test.TestUtil;
+import org.postgresql.util.PSQLException;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -80,6 +85,122 @@ public class ArrayTest extends BaseTest4 {
     pstmt.close();
   }
 
+  @Test
+  public void testSetPrimitiveObjects() throws SQLException {
+    PreparedStatement pstmt = conn.prepareStatement("INSERT INTO arrtest VALUES (?,?,?)");
+    pstmt.setObject(1, new int[]{1,2,3}, Types.ARRAY);
+    pstmt.setObject(2, new double[]{3.1d, 1.4d}, Types.ARRAY);
+    pstmt.setObject(3, new String[]{"abc", "f'a", "fa\"b"}, Types.ARRAY);
+    pstmt.executeUpdate();
+    pstmt.close();
+
+    Statement stmt = conn.createStatement();
+    ResultSet rs = stmt.executeQuery("SELECT intarr, decarr, strarr FROM arrtest");
+    Assert.assertTrue(rs.next());
+
+    Array arr = rs.getArray(1);
+    Assert.assertEquals(Types.INTEGER, arr.getBaseType());
+    Integer[] intarr = (Integer[]) arr.getArray();
+    assertEquals(3, intarr.length);
+    assertEquals(1, intarr[0].intValue());
+    assertEquals(2, intarr[1].intValue());
+    assertEquals(3, intarr[2].intValue());
+
+    arr = rs.getArray(2);
+    assertEquals(Types.NUMERIC, arr.getBaseType());
+    BigDecimal[] decarr = (BigDecimal[]) arr.getArray();
+    assertEquals(2, decarr.length);
+    assertEquals(new BigDecimal("3.1"), decarr[0]);
+    assertEquals(new BigDecimal("1.4"), decarr[1]);
+
+    arr = rs.getArray(3);
+    assertEquals(Types.VARCHAR, arr.getBaseType());
+    String[] strarr = (String[]) arr.getArray(2, 2);
+    assertEquals(2, strarr.length);
+    assertEquals("f'a", strarr[0]);
+    assertEquals("fa\"b", strarr[1]);
+
+    rs.close();
+  }
+
+  @Test
+  public void testSetPrimitiveArraysObjects() throws SQLException {
+    PreparedStatement pstmt = conn.prepareStatement("INSERT INTO arrtest VALUES (?,?,?)");
+
+    final PGConnection arraySupport = conn.unwrap(PGConnection.class);
+
+    pstmt.setArray(1, arraySupport.createArrayOf("int4", new int[] { 1, 2, 3 }));
+    pstmt.setObject(2, arraySupport.createArrayOf("float8", new double[] { 3.1d, 1.4d }));
+    pstmt.setObject(3, arraySupport.createArrayOf("varchar", new String[] { "abc", "f'a", "fa\"b" }));
+
+    pstmt.executeUpdate();
+    pstmt.close();
+
+    Statement stmt = conn.createStatement();
+    ResultSet rs = stmt.executeQuery("SELECT intarr, decarr, strarr FROM arrtest");
+    Assert.assertTrue(rs.next());
+
+    Array arr = rs.getArray(1);
+    Assert.assertEquals(Types.INTEGER, arr.getBaseType());
+    Integer[] intarr = (Integer[]) arr.getArray();
+    Assert.assertEquals(3, intarr.length);
+    Assert.assertEquals(1, intarr[0].intValue());
+    Assert.assertEquals(2, intarr[1].intValue());
+    Assert.assertEquals(3, intarr[2].intValue());
+
+    arr = rs.getArray(2);
+    Assert.assertEquals(Types.NUMERIC, arr.getBaseType());
+    BigDecimal[] decarr = (BigDecimal[]) arr.getArray();
+    Assert.assertEquals(2, decarr.length);
+    Assert.assertEquals(new BigDecimal("3.1"), decarr[0]);
+    Assert.assertEquals(new BigDecimal("1.4"), decarr[1]);
+
+    arr = rs.getArray(3);
+    Assert.assertEquals(Types.VARCHAR, arr.getBaseType());
+    String[] strarr = (String[]) arr.getArray(2, 2);
+    Assert.assertEquals(2, strarr.length);
+    Assert.assertEquals("f'a", strarr[0]);
+    Assert.assertEquals("fa\"b", strarr[1]);
+
+    try {
+      arraySupport.createArrayOf("int4", Integer.valueOf(1));
+      fail("not an array");
+    } catch (PSQLException e) {
+
+    }
+
+    rs.close();
+  }
+
+  @Test
+  public void testSetNullArrays() throws SQLException {
+    PreparedStatement pstmt = conn.prepareStatement("INSERT INTO arrtest VALUES (?,?,?)");
+
+    final PGConnection arraySupport = conn.unwrap(PGConnection.class);
+
+    pstmt.setArray(1, arraySupport.createArrayOf("int4", null));
+    pstmt.setObject(2, conn.createArrayOf("float8", null));
+    pstmt.setObject(3, arraySupport.createArrayOf("varchar", null));
+
+    pstmt.executeUpdate();
+    pstmt.close();
+
+    Statement stmt = conn.createStatement();
+    ResultSet rs = stmt.executeQuery("SELECT intarr, decarr, strarr FROM arrtest");
+    Assert.assertTrue(rs.next());
+
+    Array arr = rs.getArray(1);
+    Assert.assertNull(arr);
+
+    arr = rs.getArray(2);
+    Assert.assertNull(arr);
+
+    arr = rs.getArray(3);
+    Assert.assertNull(arr);
+
+    rs.close();
+  }
+
   @Test
   public void testRetrieveArrays() throws SQLException {
     Statement stmt = conn.createStatement();
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 87a04e3e..e687cb61 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
@@ -9,6 +9,7 @@ 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.PrimitiveArraySupportTest;
 import org.postgresql.test.core.JavaVersionTest;
 import org.postgresql.test.core.NativeQueryBindLengthTest;
 import org.postgresql.test.util.ExpressionPropertiesTest;
@@ -61,6 +62,7 @@ import org.junit.runners.Suite;
         ResultSetTest.class,
         ResultSetMetaDataTest.class,
         ArrayTest.class,
+        PrimitiveArraySupportTest.class,
         RefCursorTest.class,
 
         DateTest.class,
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java
index 3d3640e3..dba8f899 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java
@@ -7,6 +7,7 @@ package org.postgresql.test.jdbc4;
 
 import org.postgresql.core.ServerVersion;
 import org.postgresql.geometric.PGbox;
+import org.postgresql.jdbc.PgConnection;
 import org.postgresql.jdbc.PreferQueryMode;
 import org.postgresql.test.TestUtil;
 import org.postgresql.test.jdbc2.BaseTest4;
@@ -27,6 +28,7 @@ import java.sql.SQLException;
 import java.sql.Statement;
 import java.sql.Types;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.UUID;
 
@@ -74,6 +76,22 @@ public class ArrayTest extends BaseTest4 {
     super.tearDown();
   }
 
+  @Test
+  public void testCreateArrayOfBool() throws SQLException {
+    PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::bool[]");
+    pstmt.setArray(1, _conn.unwrap(PgConnection.class).createArrayOf("boolean", new boolean[] { true, true, false }));
+
+    ResultSet rs = pstmt.executeQuery();
+    Assert.assertTrue(rs.next());
+    Array arr = rs.getArray(1);
+    Boolean[] out = (Boolean[]) arr.getArray();
+
+    Assert.assertEquals(3, out.length);
+    Assert.assertEquals(Boolean.TRUE, out[0]);
+    Assert.assertEquals(Boolean.TRUE, out[1]);
+    Assert.assertEquals(Boolean.FALSE, out[2]);
+  }
+
   @Test
   public void testCreateArrayOfInt() throws SQLException {
     PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::int[]");
@@ -150,7 +168,7 @@ public class ArrayTest extends BaseTest4 {
     PGobject p2 = new PGobject();
     p2.setType("json");
     p2.setValue("{\"x\": 20}");
-    PGobject[] in = new PGobject[]{p1, p2};
+    PGobject[] in = new PGobject[] { p1, p2 };
     pstmt.setArray(1, _conn.createArrayOf("json", in));
 
     ResultSet rs = pstmt.executeQuery();
@@ -315,27 +333,33 @@ public class ArrayTest extends BaseTest4 {
   @Test
   public void testSetObjectFromJavaArray() throws SQLException {
     String[] strArray = new String[]{"a", "b", "c"};
+    Object[] objCopy = Arrays.copyOf(strArray, strArray.length, Object[].class);
 
     PreparedStatement pstmt = _conn.prepareStatement("INSERT INTO arrtest(strarr) VALUES (?)");
 
-    // Incorrect, but commonly attempted by many ORMs:
+    //cannot handle generic Object[]
     try {
-      pstmt.setObject(1, strArray, Types.ARRAY);
+      pstmt.setObject(1, objCopy, Types.ARRAY);
       pstmt.executeUpdate();
       Assert.fail("setObject() with a Java array parameter and Types.ARRAY shouldn't succeed");
     } catch (org.postgresql.util.PSQLException ex) {
       // Expected failure.
     }
 
-    // Also incorrect, but commonly attempted by many ORMs:
     try {
-      pstmt.setObject(1, strArray);
+      pstmt.setObject(1, objCopy);
       pstmt.executeUpdate();
       Assert.fail("setObject() with a Java array parameter and no Types argument shouldn't succeed");
     } catch (org.postgresql.util.PSQLException ex) {
       // Expected failure.
     }
 
+    pstmt.setObject(1, strArray);
+    pstmt.executeUpdate();
+
+    pstmt.setObject(1, strArray, Types.ARRAY);
+    pstmt.executeUpdate();
+
     // Correct way, though the use of "text" as a type is non-portable.
     // Only supported for JDK 1.6 and JDBC4
     Array sqlArray = _conn.createArrayOf("text", strArray);
@@ -549,10 +573,11 @@ public class ArrayTest extends BaseTest4 {
     Assert.assertNull("null array should return null on getObject", getObject);
   }
 
-  @Test(expected = NullPointerException.class)
+  @Test
   public void createNullArray() throws SQLException {
     Array arr = con.createArrayOf("float8", null);
-    Assert.fail("createArrayOf(float8, null) should fail with NPE");
+    Assert.assertNotNull(arr);
+    Assert.assertNull(arr.getArray());
   }
 
   @Test