Repository: pgjdbc/pgjdbc
Pull request #1037 (2017-12-18 16:29:59)

refactor: replace some usages of initCause
replace some usages of initCause with appropriate constructor

codecov-io: # [Codecov](https://codecov.io/gh/pgjdbc/pgjdbc/pull/1037?src=pr&el=h1) Report
> Merging [#1037](https://codecov.io/gh/pgjdbc/pgjdbc/pull/1037?src=pr&el=desc) into [master](https://codecov.io/gh/pgjdbc/pgjdbc/commit/ed27c5b464563448079189b1759ecf05d4726ea0?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `37.5%`.



```diff
@@             Coverage Diff              @@
##             master    #1037      +/-   ##
============================================
+ Coverage     66.39%   66.39%   +<.01%     
+ Complexity     3609     3607       -2     
============================================
  Files           168      168              
  Lines         15365    15355      -10     
  Branches       2495     2494       -1     
============================================
- Hits          10201    10195       -6     
+ Misses         3979     3974       -5     
- Partials       1185     1186       +1
```


Patch:
diff --git a/.travis.yml b/.travis.yml
index b0ee930a..fae29d4b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,8 @@ 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"
+  - export PG_SLAVE1_DATADIR="/etc/postgresql/${PG_VERSION}/slave1"
+  - export PG_SLAVE2_DATADIR="/etc/postgresql/${PG_VERSION}/slave2"
   -  ./.travis/travis_install_postgres.sh
   - test "x$XA" == 'x' || ./.travis/travis_configure_xa.sh
   - test "x$REPLICATION" == 'x' || ./.travis/travis_configure_replication.sh
@@ -13,6 +15,7 @@ before_script:
   - test "x$PG_VERSION" = 'xHEAD' || psql -U postgres -c "create user test with password 'test';"
   - test "x$REPLICATION" == 'x' || psql -U postgres -c "alter user test with replication;"
   - psql -c 'create database test owner test;' -U postgres
+  - test "x$REPLICATION" == 'x' || ./.travis/travis_create_slaves.sh
   - echo "MAVEN_OPTS='-Xmx1g -Dgpg.skip=true'" > ~/.mavenrc
   - test "x$PG_VERSION" == 'x' || test "x$NO_HSTORE" == 'xY' || psql test -c 'CREATE EXTENSION hstore;' -U postgres
   - test "x$PG_VERSION" == 'x' || test "x$CREATE_PLPGSQL" == 'x' || createlang -U postgres plpgsql test
@@ -64,11 +67,6 @@ cache:
 
 matrix:
   fast_finish: true
-  allow_failures:
-    - env: # REMOVE FAILURE extendedCacheEverything
-        - PG_VERSION=9.4
-        - QUERY_MODE=extendedCacheEverything
-        - COVERAGE=N
   include:
     - jdk: oraclejdk8
       env: RUN_CHECKSTYLE=true
@@ -89,6 +87,7 @@ matrix:
         - PG_VERSION=10
         - JDK=9
         - OPENJ9=181
+        - TZ=America/New_York # flips between −05:00 and −04:00
     - jdk: oraclejdk8
       sudo: required
       env:
@@ -96,6 +95,7 @@ matrix:
         - XA=true
         - REPLICATION=Y
         - COVERAGE=Y
+        - TZ=Pacific/Chatham # flips between +12:45 and +13:45
     - jdk: oraclejdk8
       sudo: required
       addons:
@@ -123,6 +123,7 @@ matrix:
         - PG_VERSION=9.4
         - QUERY_MODE=extendedCacheEverything
         - COVERAGE=N
+        - TZ=Europe/Moscow # +03:00, no DST
     - jdk: openjdk7
       sudo: required
       addons:
@@ -132,6 +133,7 @@ matrix:
         - ZULU_JDK=7
         - MCENTRAL=Y
         - COVERAGE=Y
+        - TZ=UTC
     - jdk: openjdk6
       sudo: required
       addons:
diff --git a/.travis/travis_build.sh b/.travis/travis_build.sh
index fbdc8bb9..2aa0e1bf 100755
--- a/.travis/travis_build.sh
+++ b/.travis/travis_build.sh
@@ -3,6 +3,9 @@ set -x -e
 
 if [[ "${FEDORA_CI}" == *"Y" ]];
 then
+  # Try to prevent "stdout: write error"
+  # WA is taken from https://github.com/travis-ci/travis-ci/issues/4704#issuecomment-348435959
+  python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);'
   export PARENT_VERSION=$(mvn -B -N org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.parent.version | grep -v '\[')
   exec ./packaging/rpm_ci
 fi
diff --git a/.travis/travis_create_slaves.sh b/.travis/travis_create_slaves.sh
new file mode 100755
index 00000000..2eba2450
--- /dev/null
+++ b/.travis/travis_create_slaves.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+set -x -e
+
+if [ -z "$PG_VERSION" ]
+then
+    echo "env PG_VERSION not define";
+elif [ "x${PG_VERSION}" = "xHEAD" ]
+then
+    PG_CTL="/usr/local/pgsql/bin/pg_ctl"
+    PG_BASEBACKUP="/usr/local/pgsql/bin/pg_basebackup"
+else 
+    PG_CTL="/usr/lib/postgresql/${PG_VERSION}/bin/pg_ctl"
+    PG_BASEBACKUP="/usr/lib/postgresql/${PG_VERSION}/bin/pg_basebackup"
+fi
+
+#Create Slave 1
+sudo rm -rf ${PG_SLAVE1_DATADIR}
+sudo mkdir -p ${PG_SLAVE1_DATADIR}
+sudo chmod 700 ${PG_SLAVE1_DATADIR}
+sudo chown -R postgres:postgres ${PG_SLAVE1_DATADIR}
+
+sudo su postgres -c "$PG_BASEBACKUP -Upostgres -D ${PG_SLAVE1_DATADIR} -X stream"
+
+sudo su postgres -c "echo standby_mode = \'on\' >${PG_SLAVE1_DATADIR}/recovery.conf"
+sudo su postgres -c "echo primary_conninfo = \'host=localhost port=5432 user=test password=test\' >>${PG_SLAVE1_DATADIR}/recovery.conf"
+sudo su postgres -c "echo recovery_target_timeline = \'latest\' >>${PG_SLAVE1_DATADIR}/recovery.conf"
+
+if [[ "x${PG_VERSION}" != "xHEAD" ]]
+then
+    sudo su postgres -c "echo 'local all all trust' > ${PG_SLAVE1_DATADIR}/pg_hba.conf"
+    sudo su postgres -c "echo 'host all all 127.0.0.1/32 trust' >> ${PG_SLAVE1_DATADIR}/pg_hba.conf"
+    sudo su postgres -c "echo 'host all all ::1/128 trust' >> ${PG_SLAVE1_DATADIR}/pg_hba.conf"
+
+    sudo su postgres -c "touch ${PG_SLAVE1_DATADIR}/pg_ident.conf"
+
+    sudo su postgres -c "cp -f ${PG_DATADIR}/postgresql.conf ${PG_SLAVE1_DATADIR}/postgresql.conf"
+    sudo sed -i -e "/^[ \t]*data_directory.*/d" ${PG_SLAVE1_DATADIR}/postgresql.conf
+    sudo sed -i -e "/^[ \t]*hba_file.*/d" ${PG_SLAVE1_DATADIR}/postgresql.conf
+    sudo sed -i -e "/^[ \t]*ident_file.*/d" ${PG_SLAVE1_DATADIR}/postgresql.conf
+    sudo sed -i -e "s/^#\?hot_standby.*/hot_standby = on/g" ${PG_SLAVE1_DATADIR}/postgresql.conf
+fi
+
+#Start Slave 1
+sudo su postgres -c "$PG_CTL -D ${PG_SLAVE1_DATADIR} -w -t 300 -c -o '-p 5433' -l /tmp/postgres_slave1.log start" || (sudo tail /tmp/postgres_slave1.log ; exit 1)
+sudo tail /tmp/postgres_slave1.log
+
+#Create Slave 2
+sudo rm -rf ${PG_SLAVE2_DATADIR}
+sudo mkdir -p ${PG_SLAVE2_DATADIR}
+sudo chmod 700 ${PG_SLAVE2_DATADIR}
+sudo chown -R postgres:postgres ${PG_SLAVE2_DATADIR}
+
+sudo su postgres -c "$PG_BASEBACKUP -Upostgres -D ${PG_SLAVE2_DATADIR} -X stream"
+
+sudo su postgres -c "echo standby_mode = \'on\' >${PG_SLAVE2_DATADIR}/recovery.conf"
+sudo su postgres -c "echo primary_conninfo = \'host=localhost port=5432 user=test password=test\' >>${PG_SLAVE2_DATADIR}/recovery.conf"
+sudo su postgres -c "echo recovery_target_timeline = \'latest\' >>${PG_SLAVE2_DATADIR}/recovery.conf"
+
+if [[ "x${PG_VERSION}" != "xHEAD" ]]
+then
+    sudo su postgres -c "echo 'local all all trust' > ${PG_SLAVE2_DATADIR}/pg_hba.conf"
+    sudo su postgres -c "echo 'host all all 127.0.0.1/32 trust' >> ${PG_SLAVE2_DATADIR}/pg_hba.conf"
+    sudo su postgres -c "echo 'host all all ::1/128 trust' >> ${PG_SLAVE2_DATADIR}/pg_hba.conf"
+
+    sudo su postgres -c "touch ${PG_SLAVE2_DATADIR}/pg_ident.conf"
+
+    sudo su postgres -c "cp -f ${PG_DATADIR}/postgresql.conf ${PG_SLAVE2_DATADIR}/postgresql.conf"
+    sudo sed -i -e "/^[ \t]*data_directory.*/d" ${PG_SLAVE2_DATADIR}/postgresql.conf
+    sudo sed -i -e "/^[ \t]*hba_file.*/d" ${PG_SLAVE2_DATADIR}/postgresql.conf
+    sudo sed -i -e "/^[ \t]*ident_file.*/d" ${PG_SLAVE2_DATADIR}/postgresql.conf
+    sudo sed -i -e "s/^#\?hot_standby.*/hot_standby = on/g" ${PG_SLAVE2_DATADIR}/postgresql.conf
+fi
+
+#Start Slave 2
+sudo su postgres -c "$PG_CTL -D ${PG_SLAVE2_DATADIR} -w -t 300 -c -o '-p 5434' -l /tmp/postgres_slave2.log start" || (sudo tail /tmp/postgres_slave2.log ; exit 1)
+sudo tail /tmp/postgres_slave2.log
diff --git a/README.md b/README.md
index a932c49b..663484f0 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,7 @@ For more information you can read [the PgJDBC driver documentation](https://jdbc
 The driver recognises JDBC URLs of the form:
 ```
 jdbc:postgresql:database
-jdbc:postgresql:/
+jdbc:postgresql:
 jdbc:postgresql://host/database
 jdbc:postgresql://host/
 jdbc:postgresql://host:port/database
@@ -93,7 +93,7 @@ jdbc:postgresql://host:port/
 ```
 The general format for a JDBC URL for connecting to a PostgreSQL server is as follows, with items in square brackets ([ ]) being optional:
 ```
-jdbc:postgresql://[host][:port][/[database]][?property1=value1[&property2=value2]...]
+jdbc:postgresql:[//host[:port]/][database][?property1=value1[&property2=value2]...]
 ```
 where:
  * **jdbc:postgresql:** (Required) is known as the sub-protocol and is constant.
@@ -141,7 +141,7 @@ In addition to the standard connection parameters the driver supports a number o
 | assumeMinServerVersion        | String  | null    | Assume the server is at least that version |
 | currentSchema                 | String  | null    | Specify the schema to be set in the search-path |
 | targetServerType              | String  | any     | Specifies what kind of server to connect, possible values: any, master, slave, preferSlave |
-| hostRecheckSeconds            | Integer | 10      | Specifies period (seconds) after host statuses are checked again in case they have changed |
+| hostRecheckSeconds            | Integer | 10      | Specifies period (seconds) after which the host status is checked again in case it has changed |
 | loadBalanceHosts              | Boolean | false   | If disabled hosts are connected in the given order. If enabled hosts are chosen randomly from the set of suitable candidates |
 | socketFactory                 | String  | null    | Specify a socket factory for socket creation |
 | socketFactoryArg              | String  | null    | Argument forwarded to constructor of SocketFactory class. |
diff --git a/build.properties b/build.properties
index 6317389f..d41f4071 100644
--- a/build.properties
+++ b/build.properties
@@ -5,6 +5,10 @@
 
 server=localhost
 port=5432
+slaveServer=localhost
+slavePort=5433
+slaveServer2=localhost
+slavePort2=5434
 database=test
 username=test
 password=test
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/connect.md b/docs/documentation/head/connect.md
index 87f1e99d..fb95cb00 100644
--- a/docs/documentation/head/connect.md
+++ b/docs/documentation/head/connect.md
@@ -95,8 +95,11 @@ Connection conn = DriverManager.getConnection(url);
 
 * **sslmode** = String
 
-	possible values are "verify-ca" and "verify-full" setting these will 
-	necessitate storing the server certificate on the client machine ["Configuring the client"](ssl-client.html).
+	possible values include "disable", "require", "verify-ca" and "verify-full", "allow" and "prefer" 
+	will throw an exception. "require" will default to a non validating SSL factory and not check the 
+	validity of the certificates. "verify-ca" and "verify-full" use a validating SSL factory and will 
+	check that the ca is correct and the host is correct. Setting these will necessitate storing the 
+	server certificate on the client machine ["Configuring the client"](ssl-client.html).
 
 * **sslcert** = String
 
@@ -304,7 +307,15 @@ Connection conn = DriverManager.getConnection(url);
 
 * **jaasApplicationName** = String
 
-	Specifies the name of the JAAS system or application login configuration. 
+	Specifies the name of the JAAS system or application login configuration.
+
+* **jaasLogin** = boolean
+
+	Specifies whether to perform a JAAS login before authenticating with GSSAPI.
+	If set to `true` (the default), the driver will attempt to obtain GSS credentials
+	using the configured JAAS login module(s) (e.g. `Krb5LoginModule`) before
+	authenticating. To skip the JAAS login, for example if the native GSS
+	implementation is being used to obtain credentials, set this to `false`.
 
 * **ApplicationName** = String
 
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/load.md b/docs/documentation/head/load.md
index 7a94a2fa..5a3646f3 100644
--- a/docs/documentation/head/load.md
+++ b/docs/documentation/head/load.md
@@ -9,41 +9,22 @@ nexttitle: Connecting to the Database
 next: connect.html
 ---
 		
-Before you can connect to a database, you need to load the driver. There are two
-methods available, and it depends on your code which is the best one to use.
+Applications do not need to explicitly load the org.postgresql.Driver
+class because the pgjdbc driver jar supports the Java Service Provider
+mechanism. The driver will be loaded by the JVM when the application
+connects to PostgreSQL™ (as long as the driver's jar file is on the
+classpath).
 
-In the first method, your code implicitly loads the driver using the `Class.forName()`
-method. For PostgreSQL™, you would use:
-
-```java
-Class.forName("org.postgresql.Driver");
-```
-
-This will load the driver, and while loading, the driver will automatically
-register itself with JDBC.
 
 ### Note
 
-The `forName()` method can throw a `ClassNotFoundException` if the driver is not
-available.
-
-This is the most common method to use, but restricts your code to use just PostgreSQL™.
-If your code may access another database system in the future, and you do not
-use any PostgreSQL™-specific extensions, then the second method is advisable.
+Prior to Java 1.6, the driver had to be loaded by the application - either by calling
 
-The second method passes the driver as a parameter to the JVM as it starts, using
-the `-D` argument. Example:
+```java
+Class.forName("org.postgresql.Driver");
+```
+or by passing the driver class name as a JVM parameter.
 
 `java -Djdbc.drivers=org.postgresql.Driver example.ImageViewer`
 
-In this example, the JVM will attempt to load the driver as part of its initialization.
-Once done, the ImageViewer is started.
-
-Now, this method is the better one to use because it allows your code to be used
-with other database packages without recompiling the code. The only thing that
-would also change is the connection URL, which is covered next.
-
-One last thing: When your code then tries to open a `Connection`, and you get a
-No driver available `SQLException` being thrown, this is probably caused by the
-driver not being in the class path, or the value in the parameter not being
-correct.
+These older methods of loading the driver are still supported but they are no longer necessary.
diff --git a/docs/documentation/head/replication.md b/docs/documentation/head/replication.md
index c90caeb1..5d821fb2 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**
@@ -32,7 +32,7 @@ do not appear in the stream. Thus, if you apply the change events in the same or
 transactionally consistent copy of the database. It's looks like the Event Sourcing pattern that you previously implemented
 in your application, but now it's available out of the box from the PostgreSQL database.
 
-For access to real-time changes PostgreSQL provides the streaming replication protocol. Replication protocol can be physical or logical. Physical replication protocol is used for Master/Slave replication. Logical replication protocol can be used
+For access to real-time changes PostgreSQL provides the streaming replication protocol. Replication protocol can be physical or logical. Physical replication protocol is used for Master/Secondary replication. Logical replication protocol can be used
 to stream changes to an external system. 
 
 
diff --git a/packaging/rpm/postgresql-jdbc.spec.tpl b/packaging/rpm/postgresql-jdbc.spec.tpl
index 0bb4f2a5..eef41c47 100644
--- a/packaging/rpm/postgresql-jdbc.spec.tpl
+++ b/packaging/rpm/postgresql-jdbc.spec.tpl
@@ -63,6 +63,7 @@ BuildRequires:	java-devel >= 1.8
 BuildRequires:	maven-local
 BuildRequires:	java-comment-preprocessor
 BuildRequires:	properties-maven-plugin
+BuildRequires:	maven-clean-plugin
 BuildRequires:	maven-enforcer-plugin
 BuildRequires:	maven-plugin-bundle
 BuildRequires:	maven-plugin-build-helper
diff --git a/pgjdbc/pom.xml b/pgjdbc/pom.xml
index 26f523c3..8409fb92 100644
--- a/pgjdbc/pom.xml
+++ b/pgjdbc/pom.xml
@@ -3,14 +3,14 @@
   <parent>
     <groupId>org.postgresql</groupId>
     <artifactId>pgjdbc-core-parent</artifactId>
-    <version>1.1.2</version>
+    <version>1.1.3</version>
     <relativePath />
   </parent>
 
   <artifactId>postgresql</artifactId>
   <packaging>bundle</packaging>
   <name>PostgreSQL JDBC Driver - JDBC 4.2</name>
-  <version>42.1.5-SNAPSHOT</version>
+  <version>42.2.0-SNAPSHOT</version>
   <description>Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database</description>
   <url>https://github.com/pgjdbc/pgjdbc</url>
 
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/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
index 3fa9654a..e3ea9f7c 100644
--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java
+++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
@@ -181,7 +181,8 @@ public enum PGProperty {
    * {@code verify-full}, or {@code disable} ({@code allow} and {@code prefer} are not implemented)
    * If not set, the {@code ssl} property may be checked to enable SSL mode.
    */
-  SSL_MODE("sslmode", null, "Parameter governing the use of SSL"),
+  SSL_MODE("sslmode", null, "Parameter governing the use of SSL",false,
+      "disable", "require", "verify-ca", "verify-full"),
 
   /**
    * Classname of the SSL Factory to use (instance of {@code javax.net.ssl.SSLSocketFactory}).
@@ -304,6 +305,13 @@ public enum PGProperty {
    */
   APPLICATION_NAME("ApplicationName", DriverInfo.DRIVER_NAME, "Name of the Application (backend >= 9.0)"),
 
+  /**
+   * Flag to enable/disable obtaining a GSS credential via JAAS login before authenticating.
+   * Useful if setting system property javax.security.auth.useSubjectCredsOnly=false
+   * or using native GSS with system property sun.security.jgss.native=true
+   */
+  JAAS_LOGIN("jaasLogin", "true", "Login with JAAS before doing GSSAPI authentication"),
+
   /**
    * Specifies the name of the JAAS system or application login configuration.
    */
@@ -359,7 +367,7 @@ public enum PGProperty {
       "If disabled hosts are connected in the given order. If enabled hosts are chosen randomly from the set of suitable candidates"),
 
   HOST_RECHECK_SECONDS("hostRecheckSeconds", "10",
-      "Specifies period (seconds) after host statuses are checked again in case they have changed"),
+      "Specifies period (seconds) after which the host status is checked again in case it has changed"),
 
   /**
    * Specifies which mode is used to execute queries to database: simple means ('Q' execute, no parse, no bind, text mode only),
diff --git a/pgjdbc/src/main/java/org/postgresql/copy/PGCopyInputStream.java b/pgjdbc/src/main/java/org/postgresql/copy/PGCopyInputStream.java
index 0a166a98..c0f8891d 100644
--- a/pgjdbc/src/main/java/org/postgresql/copy/PGCopyInputStream.java
+++ b/pgjdbc/src/main/java/org/postgresql/copy/PGCopyInputStream.java
@@ -69,20 +69,24 @@ public class PGCopyInputStream extends InputStream implements CopyOut {
   }
 
 
+  @Override
   public int available() throws IOException {
     checkClosed();
     return (buf != null ? len - at : 0);
   }
 
+  @Override
   public int read() throws IOException {
     checkClosed();
     return gotBuf() ? buf[at++] : -1;
   }
 
+  @Override
   public int read(byte[] buf) throws IOException {
     return read(buf, 0, buf.length);
   }
 
+  @Override
   public int read(byte[] buf, int off, int siz) throws IOException {
     checkClosed();
     int got = 0;
@@ -93,15 +97,14 @@ public class PGCopyInputStream extends InputStream implements CopyOut {
     return got == 0 && !didReadSomething ? -1 : got;
   }
 
+  @Override
   public byte[] readFromCopy() throws SQLException {
     byte[] result = buf;
     try {
       if (gotBuf()) {
         if (at > 0 || len < buf.length) {
           byte[] ba = new byte[len - at];
-          for (int i = at; i < len; i++) {
-            ba[i - at] = buf[i];
-          }
+          System.arraycopy(buf, at, ba, 0, len - at);
           result = ba;
         }
         at = len; // either partly or fully returned, buffer is exhausted
@@ -117,6 +120,7 @@ public class PGCopyInputStream extends InputStream implements CopyOut {
     return readFromCopy();
   }
 
+  @Override
   public void close() throws IOException {
     // Don't complain about a double close.
     if (op == null) {
@@ -127,34 +131,38 @@ public class PGCopyInputStream extends InputStream implements CopyOut {
       try {
         op.cancelCopy();
       } catch (SQLException se) {
-        IOException ioe = new IOException("Failed to close copy reader.");
-        ioe.initCause(se);
-        throw ioe;
+        throw new IOException("Failed to close copy reader.", se);
       }
     }
     op = null;
   }
 
+  @Override
   public void cancelCopy() throws SQLException {
     op.cancelCopy();
   }
 
+  @Override
   public int getFormat() {
     return op.getFormat();
   }
 
+  @Override
   public int getFieldFormat(int field) {
     return op.getFieldFormat(field);
   }
 
+  @Override
   public int getFieldCount() {
     return op.getFieldCount();
   }
 
+  @Override
   public boolean isActive() {
     return op != null && op.isActive();
   }
 
+  @Override
   public long getHandledRowCount() {
     return op.getHandledRowCount();
   }
diff --git a/pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java b/pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java
index 5f420e52..92653a47 100644
--- a/pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java
+++ b/pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java
@@ -65,6 +65,7 @@ public class PGCopyOutputStream extends OutputStream implements CopyIn {
     copyBuffer = new byte[bufferSize];
   }
 
+  @Override
   public void write(int b) throws IOException {
     checkClosed();
     if (b < 0 || b > 255) {
@@ -74,18 +75,18 @@ public class PGCopyOutputStream extends OutputStream implements CopyIn {
     write(singleByteBuffer, 0, 1);
   }
 
+  @Override
   public void write(byte[] buf) throws IOException {
     write(buf, 0, buf.length);
   }
 
+  @Override
   public void write(byte[] buf, int off, int siz) throws IOException {
     checkClosed();
     try {
       writeToCopy(buf, off, siz);
     } catch (SQLException se) {
-      IOException ioe = new IOException("Write to copy failed.");
-      ioe.initCause(se);
-      throw ioe;
+      throw new IOException("Write to copy failed.", se);
     }
   }
 
@@ -95,6 +96,7 @@ public class PGCopyOutputStream extends OutputStream implements CopyIn {
     }
   }
 
+  @Override
   public void close() throws IOException {
     // Don't complain about a double close.
     if (op == null) {
@@ -104,25 +106,23 @@ public class PGCopyOutputStream extends OutputStream implements CopyIn {
     try {
       endCopy();
     } catch (SQLException se) {
-      IOException ioe = new IOException("Ending write to copy failed.");
-      ioe.initCause(se);
-      throw ioe;
+      throw new IOException("Ending write to copy failed.", se);
     }
     op = null;
   }
 
+  @Override
   public void flush() throws IOException {
     try {
       op.writeToCopy(copyBuffer, 0, at);
       at = 0;
       op.flushCopy();
     } catch (SQLException e) {
-      IOException ioe = new IOException("Unable to flush stream");
-      ioe.initCause(e);
-      throw ioe;
+      throw new IOException("Unable to flush stream", e);
     }
   }
 
+  @Override
   public void writeToCopy(byte[] buf, int off, int siz) throws SQLException {
     if (at > 0
         && siz > copyBuffer.length - at) { // would not fit into rest of our buf, so flush buf
@@ -137,30 +137,37 @@ public class PGCopyOutputStream extends OutputStream implements CopyIn {
     }
   }
 
+  @Override
   public int getFormat() {
     return op.getFormat();
   }
 
+  @Override
   public int getFieldFormat(int field) {
     return op.getFieldFormat(field);
   }
 
+  @Override
   public void cancelCopy() throws SQLException {
     op.cancelCopy();
   }
 
+  @Override
   public int getFieldCount() {
     return op.getFieldCount();
   }
 
+  @Override
   public boolean isActive() {
     return op.isActive();
   }
 
+  @Override
   public void flushCopy() throws SQLException {
     op.flushCopy();
   }
 
+  @Override
   public long endCopy() throws SQLException {
     if (at > 0) {
       op.writeToCopy(copyBuffer, 0, at);
@@ -169,6 +176,7 @@ public class PGCopyOutputStream extends OutputStream implements CopyIn {
     return getHandledRowCount();
   }
 
+  @Override
   public long getHandledRowCount() {
     return op.getHandledRowCount();
   }
diff --git a/pgjdbc/src/main/java/org/postgresql/core/PGStream.java b/pgjdbc/src/main/java/org/postgresql/core/PGStream.java
index 61ca2521..cbb1bd63 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/PGStream.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/PGStream.java
@@ -147,6 +147,9 @@ public class PGStream implements Closeable, Flushable {
    * @throws IOException if something goes wrong
    */
   public void setEncoding(Encoding encoding) throws IOException {
+    if (this.encoding != null && this.encoding.name().equals(encoding.name())) {
+      return;
+    }
     // Close down any old writer.
     if (encodingWriter != null) {
       encodingWriter.close();
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 9dcc42b8..230f52c9 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
@@ -15,6 +15,7 @@ import org.postgresql.core.SetupQueryRunner;
 import org.postgresql.core.SocketFactoryFactory;
 import org.postgresql.core.Utils;
 import org.postgresql.core.Version;
+import org.postgresql.hostchooser.CandidateHost;
 import org.postgresql.hostchooser.GlobalHostStatusTracker;
 import org.postgresql.hostchooser.HostChooser;
 import org.postgresql.hostchooser.HostChooserFactory;
@@ -32,11 +33,14 @@ import java.io.IOException;
 import java.net.ConnectException;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.TimeZone;
 import java.util.logging.Level;
+import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 import javax.net.SocketFactory;
@@ -121,15 +125,30 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
           PSQLState.CONNECTION_UNABLE_TO_CONNECT);
     }
 
+    long lastKnownTime = System.currentTimeMillis() - PGProperty.HOST_RECHECK_SECONDS.getInt(info) * 1000;
+
     SocketFactory socketFactory = SocketFactoryFactory.getSocketFactory(info);
 
     HostChooser hostChooser =
         HostChooserFactory.createHostChooser(hostSpecs, targetServerType, info);
-    Iterator<HostSpec> hostIter = hostChooser.iterator();
+    Iterator<CandidateHost> hostIter = hostChooser.iterator();
+    Map<HostSpec, HostStatus> knownStates = new HashMap<HostSpec, HostStatus>();
     while (hostIter.hasNext()) {
-      HostSpec hostSpec = hostIter.next();
+      CandidateHost candidateHost = hostIter.next();
+      HostSpec hostSpec = candidateHost.hostSpec;
       LOGGER.log(Level.FINE, "Trying to establish a protocol version 3 connection to {0}", hostSpec);
 
+      // Note: per-connect-attempt status map is used here instead of GlobalHostStatusTracker
+      // for the case when "no good hosts" match (e.g. all the hosts are known as "connectfail")
+      // In that case, the system tries to connect to each host in order, thus it should not look into
+      // GlobalHostStatusTracker
+      HostStatus knownStatus = knownStates.get(hostSpec);
+      if (knownStatus != null && !candidateHost.targetServerType.allowConnectingTo(knownStatus)) {
+        LOGGER.log(Level.FINER, "Known status of host {0} is {1}, and required status was {2}. Will try next host",
+            new Object[]{hostSpec, knownStatus, candidateHost.targetServerType});
+        continue;
+      }
+
       //
       // Establish a connection.
       //
@@ -195,19 +214,14 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
 
         // Check Master or Slave
         HostStatus hostStatus = HostStatus.ConnectOK;
-        if (targetServerType != HostRequirement.any) {
+        if (candidateHost.targetServerType != HostRequirement.any) {
           hostStatus = isMaster(queryExecutor) ? HostStatus.Master : HostStatus.Slave;
         }
         GlobalHostStatusTracker.reportHostStatus(hostSpec, hostStatus);
-        if (!targetServerType.allowConnectingTo(hostStatus)) {
+        knownStates.put(hostSpec, hostStatus);
+        if (!candidateHost.targetServerType.allowConnectingTo(hostStatus)) {
           queryExecutor.close();
-          if (hostIter.hasNext()) {
-            // still more addresses to try
-            continue;
-          }
-          throw new PSQLException(GT
-              .tr("Could not find a server with specified targetServerType: {0}", targetServerType),
-              PSQLState.CONNECTION_UNABLE_TO_CONNECT);
+          continue;
         }
 
         runInitialQueries(queryExecutor, info);
@@ -216,7 +230,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
         return queryExecutor;
       } catch (UnsupportedProtocolException upe) {
         // Swallow this and return null so ConnectionFactory tries the next protocol.
-        LOGGER.log(Level.SEVERE, "Protocol not supported, abandoning connection.");
+        LOGGER.log(Level.SEVERE, "Protocol not supported, abandoning connection.", upe);
         closeStream(newStream);
         return null;
       } catch (ConnectException cex) {
@@ -224,6 +238,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
         // ConnectException is thrown when the connection cannot be made.
         // we trap this an return a more meaningful message for the end user
         GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail);
+        knownStates.put(hostSpec, HostStatus.ConnectFail);
+        log(Level.WARNING, "ConnectException occurred while connecting to {0}", cex, hostSpec);
         if (hostIter.hasNext()) {
           // still more addresses to try
           continue;
@@ -234,6 +250,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
       } catch (IOException ioe) {
         closeStream(newStream);
         GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail);
+        knownStates.put(hostSpec, HostStatus.ConnectFail);
+        log(Level.WARNING, "IOException occurred while connecting to {0}", ioe, hostSpec);
         if (hostIter.hasNext()) {
           // still more addresses to try
           continue;
@@ -242,6 +260,9 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
             PSQLState.CONNECTION_UNABLE_TO_CONNECT, ioe);
       } catch (SQLException se) {
         closeStream(newStream);
+        log(Level.WARNING, "SQLException occurred while connecting to {0}", se, hostSpec);
+        GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail);
+        knownStates.put(hostSpec, HostStatus.ConnectFail);
         if (hostIter.hasNext()) {
           // still more addresses to try
           continue;
@@ -249,7 +270,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
         throw se;
       }
     }
-    throw new PSQLException(GT.tr("The connection url is invalid."),
+    throw new PSQLException(GT
+        .tr("Could not find a server with specified targetServerType: {0}", targetServerType),
         PSQLState.CONNECTION_UNABLE_TO_CONNECT);
   }
 
@@ -287,6 +309,16 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
     return paramList;
   }
 
+  private static void log(Level level, String msg, Throwable thrown, Object... params) {
+    if (!LOGGER.isLoggable(level)) {
+      return;
+    }
+    LogRecord rec = new LogRecord(level, msg);
+    rec.setParameters(params);
+    rec.setThrown(thrown);
+    LOGGER.log(rec);
+  }
+
   /**
    * Convert Java time zone to postgres time zone. All others stay the same except that GMT+nn
    * changes to GMT-nn and vise versa.
@@ -569,7 +601,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
                   /* Use JGSS's GSSAPI for this request */
                   org.postgresql.gss.MakeGSS.authenticate(pgStream, host, user, password,
                       PGProperty.JAAS_APPLICATION_NAME.get(info),
-                      PGProperty.KERBEROS_SERVER_NAME.get(info), usespnego);
+                      PGProperty.KERBEROS_SERVER_NAME.get(info), usespnego,
+                      PGProperty.JAAS_LOGIN.getBoolean(info));
                 }
                 break;
 
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
index 76967e0d..b409c302 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
@@ -53,6 +53,7 @@ import java.sql.SQLWarning;
 import java.sql.Statement;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -352,10 +353,10 @@ public class QueryExecutorImpl extends QueryExecutorBase {
         || !(query instanceof SimpleQuery)
         || ((SimpleQuery) query).getFields() != null)) {
       sendOneQuery(autoSaveQuery, SimpleQuery.NO_PARAMETERS, 1, 0,
-          updateQueryMode(QUERY_NO_RESULTS | QUERY_NO_METADATA)
+          QUERY_NO_RESULTS | QUERY_NO_METADATA
               // PostgreSQL does not support bind, exec, simple, sync message flow,
               // so we force autosavepoint to use simple if the main query is using simple
-              | (flags & QueryExecutor.QUERY_EXECUTE_AS_SIMPLE));
+              | QUERY_EXECUTE_AS_SIMPLE);
       return true;
     }
     return false;
@@ -366,8 +367,9 @@ public class QueryExecutorImpl extends QueryExecutorBase {
         && getTransactionState() == TransactionState.FAILED
         && (getAutoSave() == AutoSave.ALWAYS || willHealOnRetry(e))) {
       try {
+        // ROLLBACK and AUTOSAVE are executed as simple always to overcome "statement no longer exists S_xx"
         execute(restoreToAutoSave, SimpleQuery.NO_PARAMETERS, new ResultHandlerDelegate(null),
-            1, 0, updateQueryMode(QUERY_NO_RESULTS | QUERY_NO_METADATA));
+            1, 0, QUERY_NO_RESULTS | QUERY_NO_METADATA | QUERY_EXECUTE_AS_SIMPLE);
       } catch (SQLException e2) {
         // That's O(N), sorry
         e.setNextException(e2);
@@ -1196,45 +1198,12 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           break;
         case 'S': // Parameter Status
         {
-          int l_len = pgStream.receiveInteger4();
-          String name = pgStream.receiveString();
-          String value = pgStream.receiveString();
-
-          if (LOGGER.isLoggable(Level.FINEST)) {
-            LOGGER.log(Level.FINEST, " <=BE ParameterStatus({0} = {1})", new Object[]{name, value});
-          }
-
-          if (name.equals("client_encoding") && !value.equalsIgnoreCase("UTF8")
-              && !allowEncodingChanges) {
-            close(); // we're screwed now; we can't trust any subsequent string.
-            error = new PSQLException(GT.tr(
-                "The server''s client_encoding parameter was changed to {0}. The JDBC driver requires client_encoding to be UTF8 for correct operation.",
-                value), PSQLState.CONNECTION_FAILURE);
-            endReceiving = true;
-          }
-
-          if (name.equals("DateStyle") && !value.startsWith("ISO,")) {
-            close(); // we're screwed now; we can't trust any subsequent date.
-            error = new PSQLException(GT.tr(
-                "The server''s DateStyle parameter was changed to {0}. The JDBC driver requires DateStyle to begin with ISO for correct operation.",
-                value), PSQLState.CONNECTION_FAILURE);
+          try {
+            receiveParameterStatus();
+          } catch (SQLException e) {
+            error = e;
             endReceiving = true;
           }
-
-          if (name.equals("standard_conforming_strings")) {
-            if (value.equals("on")) {
-              setStandardConformingStrings(true);
-            } else if (value.equals("off")) {
-              setStandardConformingStrings(false);
-            } else {
-              close();
-              // we're screwed now; we don't know how to escape string literals
-              error = new PSQLException(GT.tr(
-                  "The server''s standard_conforming_strings parameter was reported as {0}. The JDBC driver expected on or off.",
-                  value), PSQLState.CONNECTION_FAILURE);
-              endReceiving = true;
-            }
-          }
           break;
         }
 
@@ -1394,6 +1363,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
     pgStream.sendChar('S'); // Sync
     pgStream.sendInteger4(4); // Length
     pgStream.flush();
+    // Below "add queues" are likely not required at all
     pendingExecuteQueue.add(new ExecuteRequest(sync, null, true));
     pendingDescribePortalQueue.add(sync);
   }
@@ -2067,8 +2037,10 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           Portal currentPortal = executeData.portal;
 
           Field[] fields = currentQuery.getFields();
-          if (fields != null && !noResults && tuples == null) {
-            tuples = new ArrayList<byte[][]>();
+          if (fields != null && tuples == null) {
+            // When no results expected, pretend an empty resultset was returned
+            // Not sure if new ArrayList can be always replaced with emptyList
+            tuples = noResults ? Collections.<byte[][]>emptyList() : new ArrayList<byte[][]>();
           }
 
           handler.handleResultRows(currentQuery, fields, tuples, currentPortal);
@@ -2115,8 +2087,10 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           }
 
           Field[] fields = currentQuery.getFields();
-          if (fields != null && !noResults && tuples == null) {
-            tuples = new ArrayList<byte[][]>();
+          if (fields != null && tuples == null) {
+            // When no results expected, pretend an empty resultset was returned
+            // Not sure if new ArrayList can be always replaced with emptyList
+            tuples = noResults ? Collections.<byte[][]>emptyList() : new ArrayList<byte[][]>();
           }
 
           // If we received tuples we must know the structure of the
@@ -2229,52 +2203,12 @@ public class QueryExecutorImpl extends QueryExecutorBase {
 
         case 'S': // Parameter Status
         {
-          int l_len = pgStream.receiveInteger4();
-          String name = pgStream.receiveString();
-          String value = pgStream.receiveString();
-
-          if (LOGGER.isLoggable(Level.FINEST)) {
-            LOGGER.log(Level.FINEST, " <=BE ParameterStatus({0} = {1})", new Object[]{name, value});
-          }
-
-          if (name.equals("client_encoding") && !value.equalsIgnoreCase("UTF8")
-              && !allowEncodingChanges) {
-            close(); // we're screwed now; we can't trust any subsequent string.
-            handler.handleError(new PSQLException(GT.tr(
-                "The server''s client_encoding parameter was changed to {0}. The JDBC driver requires client_encoding to be UTF8 for correct operation.",
-                value), PSQLState.CONNECTION_FAILURE));
-            endQuery = true;
-          }
-
-          if (name.equals("DateStyle") && !value.startsWith("ISO,")) {
-            close(); // we're screwed now; we can't trust any subsequent date.
-            handler.handleError(new PSQLException(GT.tr(
-                "The server''s DateStyle parameter was changed to {0}. The JDBC driver requires DateStyle to begin with ISO for correct operation.",
-                value), PSQLState.CONNECTION_FAILURE));
+          try {
+            receiveParameterStatus();
+          } catch (SQLException e) {
+            handler.handleError(e);
             endQuery = true;
           }
-
-          if (name.equals("standard_conforming_strings")) {
-            if (value.equals("on")) {
-              setStandardConformingStrings(true);
-            } else if (value.equals("off")) {
-              setStandardConformingStrings(false);
-            } else {
-              close();
-              // we're screwed now; we don't know how to escape string literals
-              handler.handleError(new PSQLException(GT.tr(
-                  "The server''s standard_conforming_strings parameter was reported as {0}. The JDBC driver expected on or off.",
-                  value), PSQLState.CONNECTION_FAILURE));
-              endQuery = true;
-            }
-          }
-
-          if ("TimeZone".equals(name)) {
-            setTimeZone(TimestampUtils.parseBackendTimeZone(value));
-          }
-          if ("application_name".equals(name)) {
-            setApplicationName(value);
-          }
           break;
         }
 
@@ -2327,8 +2261,19 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           }
 
           pendingParseQueue.clear(); // No more ParseComplete messages expected.
-          pendingDescribeStatementQueue.clear(); // No more ParameterDescription messages expected.
-          pendingDescribePortalQueue.clear(); // No more RowDescription messages expected.
+          // Pending "describe" requests might be there in case of error
+          // If that is the case, reset "described" status, so the statement is properly
+          // described on next execution
+          while (!pendingDescribeStatementQueue.isEmpty()) {
+            DescribeRequest request = pendingDescribeStatementQueue.removeFirst();
+            LOGGER.log(Level.FINEST, " FE marking setStatementDescribed(false) for query {0}", request.query);
+            request.query.setStatementDescribed(false);
+          }
+          while (!pendingDescribePortalQueue.isEmpty()) {
+            SimpleQuery describePortalQuery = pendingDescribePortalQueue.removeFirst();
+            LOGGER.log(Level.FINEST, " FE marking setPortalDescribed(false) for query {0}", describePortalQuery);
+            describePortalQuery.setPortalDescribed(false);
+          }
           pendingBindQueue.clear(); // No more BindComplete messages expected.
           pendingExecuteQueue.clear(); // No more query executions expected.
           break;
@@ -2621,43 +2566,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
 
         case 'S':
           // ParameterStatus
-          int l_len = pgStream.receiveInteger4();
-          String name = pgStream.receiveString();
-          String value = pgStream.receiveString();
-
-          if (LOGGER.isLoggable(Level.FINEST)) {
-            LOGGER.log(Level.FINEST, " <=BE ParameterStatus({0} = {1})", new Object[]{name, value});
-          }
-
-          if ("server_version_num".equals(name)) {
-            setServerVersionNum(Integer.parseInt(value));
-          } else if ("server_version".equals(name)) {
-            setServerVersion(value);
-          } else if ("client_encoding".equals(name)) {
-            if (!"UTF8".equals(value)) {
-              throw new PSQLException(GT.tr("Protocol error.  Session setup failed."),
-                  PSQLState.PROTOCOL_VIOLATION);
-            }
-            pgStream.setEncoding(Encoding.getDatabaseEncoding("UTF8"));
-          } else if ("standard_conforming_strings".equals(name)) {
-            if ("on".equals(value)) {
-              setStandardConformingStrings(true);
-            } else if ("off".equals(value)) {
-              setStandardConformingStrings(false);
-            } else {
-              throw new PSQLException(GT.tr("Protocol error.  Session setup failed."),
-                  PSQLState.PROTOCOL_VIOLATION);
-            }
-          } else if ("integer_datetimes".equals(name)) {
-            if ("on".equals(value)) {
-              setIntegerDateTimes(true);
-            } else if ("off".equals(value)) {
-              setIntegerDateTimes(false);
-            } else {
-              throw new PSQLException(GT.tr("Protocol error.  Session setup failed."),
-                  PSQLState.PROTOCOL_VIOLATION);
-            }
-          }
+          receiveParameterStatus();
 
           break;
 
@@ -2671,7 +2580,80 @@ public class QueryExecutorImpl extends QueryExecutorBase {
         PSQLState.PROTOCOL_VIOLATION);
   }
 
+  public void receiveParameterStatus() throws IOException, SQLException {
+    // ParameterStatus
+    int l_len = pgStream.receiveInteger4();
+    String name = pgStream.receiveString();
+    String value = pgStream.receiveString();
+
+    if (LOGGER.isLoggable(Level.FINEST)) {
+      LOGGER.log(Level.FINEST, " <=BE ParameterStatus({0} = {1})", new Object[]{name, value});
+    }
+
+    if (name.equals("client_encoding") && !value.equalsIgnoreCase("UTF8")
+        && !allowEncodingChanges) {
+      close(); // we're screwed now; we can't trust any subsequent string.
+      throw new PSQLException(GT.tr(
+          "The server''s client_encoding parameter was changed to {0}. The JDBC driver requires client_encoding to be UTF8 for correct operation.",
+          value), PSQLState.CONNECTION_FAILURE);
+    }
 
+    if (name.equals("DateStyle") && !value.startsWith("ISO,")) {
+      close(); // we're screwed now; we can't trust any subsequent date.
+      throw new PSQLException(GT.tr(
+          "The server''s DateStyle parameter was changed to {0}. The JDBC driver requires DateStyle to begin with ISO for correct operation.",
+          value), PSQLState.CONNECTION_FAILURE);
+    }
+
+    if (name.equals("standard_conforming_strings")) {
+      if (value.equals("on")) {
+        setStandardConformingStrings(true);
+      } else if (value.equals("off")) {
+        setStandardConformingStrings(false);
+      } else {
+        close();
+        // we're screwed now; we don't know how to escape string literals
+        throw new PSQLException(GT.tr(
+            "The server''s standard_conforming_strings parameter was reported as {0}. The JDBC driver expected on or off.",
+            value), PSQLState.CONNECTION_FAILURE);
+      }
+      return;
+    }
+
+    if ("TimeZone".equals(name)) {
+      setTimeZone(TimestampUtils.parseBackendTimeZone(value));
+    } else if ("application_name".equals(name)) {
+      setApplicationName(value);
+    } else if ("server_version_num".equals(name)) {
+      setServerVersionNum(Integer.parseInt(value));
+    } else if ("server_version".equals(name)) {
+      setServerVersion(value);
+    } else if ("client_encoding".equals(name)) {
+      if (!"UTF8".equals(value)) {
+        throw new PSQLException(GT.tr("Protocol error.  Session setup failed."),
+            PSQLState.PROTOCOL_VIOLATION);
+      }
+      pgStream.setEncoding(Encoding.getDatabaseEncoding("UTF8"));
+    } else if ("standard_conforming_strings".equals(name)) {
+      if ("on".equals(value)) {
+        setStandardConformingStrings(true);
+      } else if ("off".equals(value)) {
+        setStandardConformingStrings(false);
+      } else {
+        throw new PSQLException(GT.tr("Protocol error.  Session setup failed."),
+            PSQLState.PROTOCOL_VIOLATION);
+      }
+    } else if ("integer_datetimes".equals(name)) {
+      if ("on".equals(value)) {
+        setIntegerDateTimes(true);
+      } else if ("off".equals(value)) {
+        setIntegerDateTimes(false);
+      } else {
+        throw new PSQLException(GT.tr("Protocol error.  Session setup failed."),
+            PSQLState.PROTOCOL_VIOLATION);
+      }
+    }
+  }
 
   public void setTimeZone(TimeZone timeZone) {
     this.timeZone = timeZone;
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 2716aac5..0d5261a3 100644
--- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
+++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
@@ -880,6 +880,22 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     PGProperty.JAAS_APPLICATION_NAME.set(properties, name);
   }
 
+  /**
+   * @return true if perform JAAS login before GSS authentication
+   * @see PGProperty#JAAS_LOGIN
+   */
+  public boolean getJaasLogin() {
+    return PGProperty.JAAS_LOGIN.getBoolean(properties);
+  }
+
+  /**
+   * @param doLogin true if perform JAAS login before GSS authentication
+   * @see PGProperty#JAAS_LOGIN
+   */
+  public void setJaasLogin(boolean doLogin) {
+    PGProperty.JAAS_LOGIN.set(properties, doLogin);
+  }
+
   /**
    * @return Kerberos server name
    * @see PGProperty#KERBEROS_SERVER_NAME
diff --git a/pgjdbc/src/main/java/org/postgresql/gss/MakeGSS.java b/pgjdbc/src/main/java/org/postgresql/gss/MakeGSS.java
index f3b176bc..af01f993 100644
--- a/pgjdbc/src/main/java/org/postgresql/gss/MakeGSS.java
+++ b/pgjdbc/src/main/java/org/postgresql/gss/MakeGSS.java
@@ -29,7 +29,7 @@ public class MakeGSS {
   private static final Logger LOGGER = Logger.getLogger(MakeGSS.class.getName());
 
   public static void authenticate(PGStream pgStream, String host, String user, String password,
-      String jaasApplicationName, String kerberosServerName, boolean useSpnego)
+      String jaasApplicationName, String kerberosServerName, boolean useSpnego, boolean jaasLogin)
           throws IOException, SQLException {
     LOGGER.log(Level.FINEST, " <=BE AuthenticationReqGSS");
 
@@ -42,7 +42,7 @@ public class MakeGSS {
 
     Exception result;
     try {
-      boolean performAuthentication = true;
+      boolean performAuthentication = jaasLogin;
       GSSCredential gssCredential = null;
       Subject sub = Subject.getSubject(AccessController.getContext());
       if (sub != null) {
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/CandidateHost.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/CandidateHost.java
new file mode 100644
index 00000000..220ae1cd
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/CandidateHost.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.hostchooser;
+
+import org.postgresql.util.HostSpec;
+
+
+/**
+ * Candidate host to be connected.
+ */
+public class CandidateHost {
+  public final HostSpec hostSpec;
+  public final HostRequirement targetServerType;
+
+  public CandidateHost(HostSpec hostSpec, HostRequirement targetServerType) {
+    this.hostSpec = hostSpec;
+    this.targetServerType = targetServerType;
+  }
+}
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/GlobalHostStatusTracker.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/GlobalHostStatusTracker.java
index 6479a937..bd53ab93 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/GlobalHostStatusTracker.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/GlobalHostStatusTracker.java
@@ -30,23 +30,16 @@ public class GlobalHostStatusTracker {
   public static void reportHostStatus(HostSpec hostSpec, HostStatus hostStatus) {
     long now = currentTimeMillis();
     synchronized (hostStatusMap) {
-      HostSpecStatus oldStatus = hostStatusMap.get(hostSpec);
-      if (oldStatus == null || updateStatusFromTo(oldStatus.status, hostStatus)) {
-        hostStatusMap.put(hostSpec, new HostSpecStatus(hostSpec, hostStatus, now));
+      HostSpecStatus hostSpecStatus = hostStatusMap.get(hostSpec);
+      if (hostSpecStatus == null) {
+        hostSpecStatus = new HostSpecStatus(hostSpec);
+        hostStatusMap.put(hostSpec, hostSpecStatus);
       }
+      hostSpecStatus.status = hostStatus;
+      hostSpecStatus.lastUpdated = now;
     }
   }
 
-  private static boolean updateStatusFromTo(HostStatus oldStatus, HostStatus newStatus) {
-    if (oldStatus == null) {
-      return true;
-    }
-    if (newStatus == HostStatus.ConnectOK) {
-      return oldStatus != HostStatus.Master && oldStatus != HostStatus.Slave;
-    }
-    return true;
-  }
-
   /**
    * Returns a list of candidate hosts that have the required targetServerType.
    *
@@ -55,38 +48,31 @@ public class GlobalHostStatusTracker {
    * @param hostRecheckMillis How stale information is allowed.
    * @return candidate hosts to connect to.
    */
-  static List<HostSpecStatus> getCandidateHosts(HostSpec[] hostSpecs,
+  static List<HostSpec> getCandidateHosts(HostSpec[] hostSpecs,
       HostRequirement targetServerType, long hostRecheckMillis) {
-    List<HostSpecStatus> candidates = new ArrayList<HostSpecStatus>(hostSpecs.length);
+    List<HostSpec> candidates = new ArrayList<HostSpec>(hostSpecs.length);
     long latestAllowedUpdate = currentTimeMillis() - hostRecheckMillis;
     synchronized (hostStatusMap) {
       for (HostSpec hostSpec : hostSpecs) {
         HostSpecStatus hostInfo = hostStatusMap.get(hostSpec);
-        // return null status wrapper if if the current value is not known or is too old
-        if (hostInfo == null || hostInfo.lastUpdated < latestAllowedUpdate) {
-          hostInfo = new HostSpecStatus(hostSpec, null, Long.MAX_VALUE);
-        }
         // candidates are nodes we do not know about and the nodes with correct type
-        if (hostInfo.status == null || targetServerType.allowConnectingTo(hostInfo.status)) {
-          candidates.add(hostInfo);
+        if (hostInfo == null
+            || hostInfo.lastUpdated < latestAllowedUpdate
+            || targetServerType.allowConnectingTo(hostInfo.status)) {
+          candidates.add(hostSpec);
         }
       }
     }
     return candidates;
   }
 
-  /**
-   * Immutable structure of known status of one HostSpec.
-   */
   static class HostSpecStatus {
     final HostSpec host;
-    final HostStatus status;
-    final long lastUpdated;
+    HostStatus status;
+    long lastUpdated;
 
-    HostSpecStatus(HostSpec host, HostStatus hostStatus, long lastUpdated) {
+    HostSpecStatus(HostSpec host) {
       this.host = host;
-      this.status = hostStatus;
-      this.lastUpdated = lastUpdated;
     }
 
     @Override
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooser.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooser.java
index d726c285..a506b7bf 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooser.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooser.java
@@ -5,19 +5,17 @@
 
 package org.postgresql.hostchooser;
 
-import org.postgresql.util.HostSpec;
-
 import java.util.Iterator;
 
 /**
  * Lists connections in preferred order.
  */
-public interface HostChooser extends Iterable<HostSpec> {
+public interface HostChooser extends Iterable<CandidateHost> {
   /**
    * Lists connection hosts in preferred order.
    *
    * @return connection hosts in preferred order.
    */
   @Override
-  Iterator<HostSpec> iterator();
+  Iterator<CandidateHost> iterator();
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooserFactory.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooserFactory.java
index 4fb27b1c..4099fa03 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooserFactory.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostChooserFactory.java
@@ -17,7 +17,7 @@ public class HostChooserFactory {
   public static HostChooser createHostChooser(HostSpec[] hostSpecs,
       HostRequirement targetServerType, Properties info) {
     if (hostSpecs.length == 1) {
-      return new SingleHostChooser(hostSpecs[0]);
+      return new SingleHostChooser(hostSpecs[0], targetServerType);
     }
     return new MultiHostChooser(hostSpecs, targetServerType, info);
   }
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/MultiHostChooser.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/MultiHostChooser.java
index 8d1925ba..4ee2014b 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/MultiHostChooser.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/MultiHostChooser.java
@@ -5,17 +5,16 @@
 
 package org.postgresql.hostchooser;
 
-import static java.util.Arrays.asList;
 import static java.util.Collections.shuffle;
-import static java.util.Collections.sort;
 
 import org.postgresql.PGProperty;
-import org.postgresql.hostchooser.GlobalHostStatusTracker.HostSpecStatus;
 import org.postgresql.util.HostSpec;
 import org.postgresql.util.PSQLException;
 
+import java.util.AbstractList;
 import java.util.ArrayList;
-import java.util.Comparator;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
@@ -23,13 +22,13 @@ import java.util.Properties;
 /**
  * HostChooser that keeps track of known host statuses.
  */
-public class MultiHostChooser implements HostChooser {
+class MultiHostChooser implements HostChooser {
   private HostSpec[] hostSpecs;
   private final HostRequirement targetServerType;
   private int hostRecheckTime;
   private boolean loadBalance;
 
-  protected MultiHostChooser(HostSpec[] hostSpecs, HostRequirement targetServerType,
+  MultiHostChooser(HostSpec[] hostSpecs, HostRequirement targetServerType,
       Properties info) {
     this.hostSpecs = hostSpecs;
     this.targetServerType = targetServerType;
@@ -42,79 +41,98 @@ public class MultiHostChooser implements HostChooser {
   }
 
   @Override
-  public Iterator<HostSpec> iterator() {
-    List<HostSpecStatus> candidates =
-        GlobalHostStatusTracker.getCandidateHosts(hostSpecs, targetServerType, hostRecheckTime);
-    // if no candidates are suitable (all wrong type or unavailable) then we try original list in
-    // order
-    if (candidates.isEmpty()) {
-      return asList(hostSpecs).iterator();
-    }
-    if (candidates.size() == 1) {
-      return asList(candidates.get(0).host).iterator();
+  public Iterator<CandidateHost> iterator() {
+    Iterator<CandidateHost> res = candidateIterator();
+    if (!res.hasNext()) {
+      // In case all the candidate hosts are unavailable or do not match, try all the hosts just in case
+      List<HostSpec> allHosts = Arrays.asList(hostSpecs);
+      if (loadBalance) {
+        allHosts = new ArrayList<HostSpec>(allHosts);
+        Collections.shuffle(allHosts);
+      }
+      res = withReqStatus(targetServerType, allHosts).iterator();
     }
-    sortCandidates(candidates);
-    shuffleGoodHosts(candidates);
-    return extractHostSpecs(candidates).iterator();
+    return res;
   }
 
-  private void sortCandidates(List<HostSpecStatus> candidates) {
-    if (targetServerType == HostRequirement.any) {
-      return;
+  private Iterator<CandidateHost> candidateIterator() {
+    if (targetServerType != HostRequirement.preferSlave) {
+      return getCandidateHosts(targetServerType).iterator();
     }
-    sort(candidates, new HostSpecByTargetServerTypeComparator());
-  }
 
-  private void shuffleGoodHosts(List<HostSpecStatus> candidates) {
-    if (!loadBalance) {
-      return;
+    // preferSlave tries to find slave hosts first
+    // Note: sort does not work here since there are "unknown" hosts,
+    // and that "unkown" might turn out to be master, so we should discard that
+    // if other slaves exist
+    List<CandidateHost> slaves = getCandidateHosts(HostRequirement.slave);
+    List<CandidateHost> any = getCandidateHosts(HostRequirement.any);
+
+    if (slaves.isEmpty()) {
+      return any.iterator();
     }
-    int count;
-    for (count = 1; count < candidates.size(); count++) {
-      HostSpecStatus hostSpecStatus = candidates.get(count);
-      if (hostSpecStatus.status != null
-          && !targetServerType.allowConnectingTo(hostSpecStatus.status)) {
-        break;
-      }
+
+    if (any.isEmpty()) {
+      return slaves.iterator();
     }
-    if (count == 1) {
-      return;
+
+    if (slaves.get(slaves.size() - 1).equals(any.get(0))) {
+      // When the last slave's hostspec is the same as the first in "any" list, there's no need
+      // to attempt to connect it as "slave"
+      // Note: this is only an optimization
+      slaves = rtrim(1, slaves);
     }
-    List<HostSpecStatus> goodHosts = candidates.subList(0, count);
-    shuffle(goodHosts);
+    return append(slaves, any).iterator();
   }
 
-  private List<HostSpec> extractHostSpecs(List<HostSpecStatus> hostSpecStatuses) {
-    List<HostSpec> hostSpecs = new ArrayList<HostSpec>(hostSpecStatuses.size());
-    for (HostSpecStatus hostSpecStatus : hostSpecStatuses) {
-      hostSpecs.add(hostSpecStatus.host);
+  private List<CandidateHost> getCandidateHosts(HostRequirement hostRequirement) {
+    List<HostSpec> candidates =
+        GlobalHostStatusTracker.getCandidateHosts(hostSpecs, hostRequirement, hostRecheckTime);
+    if (loadBalance) {
+      shuffle(candidates);
     }
-    return hostSpecs;
+    return withReqStatus(hostRequirement, candidates);
   }
 
-  class HostSpecByTargetServerTypeComparator implements Comparator<HostSpecStatus> {
-    @Override
-    public int compare(HostSpecStatus o1, HostSpecStatus o2) {
-      int r1 = rank(o1.status, targetServerType);
-      int r2 = rank(o2.status, targetServerType);
-      return r1 == r2 ? 0 : r1 > r2 ? -1 : 1;
-    }
+  private List<CandidateHost> withReqStatus(final HostRequirement requirement, final List<HostSpec> hosts) {
+    return new AbstractList<CandidateHost>() {
+      @Override
+      public CandidateHost get(int index) {
+        return new CandidateHost(hosts.get(index), requirement);
+      }
+
+      @Override
+      public int size() {
+        return hosts.size();
+      }
+    };
+  }
 
-    private int rank(HostStatus status, HostRequirement targetServerType) {
-      if (status == HostStatus.ConnectFail) {
-        return -1;
+  private <T> List<T> append(final List<T> a, final List<T> b) {
+    return new AbstractList<T>() {
+      @Override
+      public T get(int index) {
+        return index < a.size() ? a.get(index) : b.get(index - a.size());
       }
-      switch (targetServerType) {
-        case master:
-          return status == HostStatus.Master || status == null ? 1 : 0;
-        case slave:
-          return status == HostStatus.Slave || status == null ? 1 : 0;
-        case preferSlave:
-          return status == HostStatus.Slave || status == null ? 2
-              : status == HostStatus.Master ? 1 : 0;
-        default:
-          return 0;
+
+      @Override
+      public int size() {
+        return a.size() + b.size();
       }
-    }
+    };
+  }
+
+  private <T> List<T> rtrim(final int size, final List<T> a) {
+    return new AbstractList<T>() {
+      @Override
+      public T get(int index) {
+        return a.get(index);
+      }
+
+      @Override
+      public int size() {
+        return Math.max(0, a.size() - size);
+      }
+    };
   }
+
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/SingleHostChooser.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/SingleHostChooser.java
index 97965b07..e79e8348 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/SingleHostChooser.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/SingleHostChooser.java
@@ -14,15 +14,15 @@ import java.util.Iterator;
 /**
  * Host chooser that returns the single host.
  */
-public class SingleHostChooser implements HostChooser {
-  private final Collection<HostSpec> hostSpec;
+class SingleHostChooser implements HostChooser {
+  private final Collection<CandidateHost> candidateHost;
 
-  public SingleHostChooser(HostSpec hostSpec) {
-    this.hostSpec = Collections.singletonList(hostSpec);
+  SingleHostChooser(HostSpec hostSpec, HostRequirement targetServerType) {
+    this.candidateHost = Collections.singletonList(new CandidateHost(hostSpec, targetServerType));
   }
 
   @Override
-  public Iterator<HostSpec> iterator() {
-    return hostSpec.iterator();
+  public Iterator<CandidateHost> iterator() {
+    return candidateHost.iterator();
   }
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/BatchResultHandler.java b/pgjdbc/src/main/java/org/postgresql/jdbc/BatchResultHandler.java
index 5cf84ff3..18bf4fe9 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/BatchResultHandler.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/BatchResultHandler.java
@@ -51,6 +51,7 @@ public class BatchResultHandler extends ResultHandlerBase {
     this.allGeneratedRows = !expectGeneratedKeys ? null : new ArrayList<List<byte[][]>>();
   }
 
+  @Override
   public void handleResultRows(Query fromQuery, Field[] fields, List<byte[][]> tuples,
       ResultCursor cursor) {
     // If SELECT, then handleCommandStatus call would just be missing
@@ -73,6 +74,7 @@ public class BatchResultHandler extends ResultHandlerBase {
     latestGeneratedRows = tuples;
   }
 
+  @Override
   public void handleCommandStatus(String status, int updateCount, long insertOID) {
     if (latestGeneratedRows != null) {
       // We have DML. Decrease resultIndex that was just increased in handleResultRows
@@ -125,6 +127,7 @@ public class BatchResultHandler extends ResultHandlerBase {
     allGeneratedRows.clear();
   }
 
+  @Override
   public void handleWarning(SQLWarning warning) {
     pgStatement.addWarning(warning);
   }
@@ -145,8 +148,7 @@ public class BatchResultHandler extends ResultHandlerBase {
       BatchUpdateException batchException = new BatchUpdateException(
           GT.tr("Batch entry {0} {1} was aborted: {2}  Call getNextException to see other errors in the batch.",
               resultIndex, queryString, newError.getMessage()),
-          newError.getSQLState(), uncompressUpdateCount());
-      batchException.initCause(newError);
+          newError.getSQLState(), uncompressUpdateCount(), newError);
       super.handleError(batchException);
     }
     resultIndex++;
@@ -154,6 +156,7 @@ public class BatchResultHandler extends ResultHandlerBase {
     super.handleError(newError);
   }
 
+  @Override
   public void handleCompletion() throws SQLException {
     updateGeneratedKeys();
     SQLException batchException = getException();
@@ -163,9 +166,8 @@ public class BatchResultHandler extends ResultHandlerBase {
         BatchUpdateException newException = new BatchUpdateException(
             batchException.getMessage(),
             batchException.getSQLState(),
-            uncompressUpdateCount()
-        );
-        newException.initCause(batchException.getCause());
+            uncompressUpdateCount(),
+            batchException.getCause());
         SQLException next = batchException.getNextException();
         if (next != null) {
           newException.setNextException(next);
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/CallableBatchResultHandler.java b/pgjdbc/src/main/java/org/postgresql/jdbc/CallableBatchResultHandler.java
index ac24ad8f..ef79c578 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/CallableBatchResultHandler.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/CallableBatchResultHandler.java
@@ -17,6 +17,7 @@ class CallableBatchResultHandler extends BatchResultHandler {
     super(statement, queries, parameterLists, false);
   }
 
+  @Override
   public void handleResultRows(Query fromQuery, Field[] fields, List<byte[][]> tuples, ResultCursor cursor) {
     /* ignore */
   }
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/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
index c7d10f8c..8c554e7a 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
@@ -71,6 +71,7 @@ import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.TimeZone;
 import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 
 
@@ -518,8 +519,14 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
         // If backend provides just TIMESTAMP, we use "cal" timezone
         // If backend provides TIMESTAMPTZ, we ignore "cal" as we know true instant value
         Timestamp timestamp = getTimestamp(i, cal);
+        long timeMillis = timestamp.getTime();
+        if (oid == Oid.TIMESTAMPTZ) {
+          // time zone == UTC since BINARY "timestamp with time zone" is always sent in UTC
+          // So we truncate days
+          return new Time(timeMillis % TimeUnit.DAYS.toMillis(1));
+        }
         // Here we just truncate date part
-        return connection.getTimestampUtils().convertToTime(timestamp.getTime(), tz);
+        return connection.getTimestampUtils().convertToTime(timeMillis, tz);
       } else {
         throw new PSQLException(
             GT.tr("Cannot convert the column of type {0} to requested type {1}.",
@@ -529,13 +536,7 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     }
 
     String string = getString(i);
-    if (string.equals("24:00:00")) {
-      return Time.valueOf(string);
-    } else if (string.equals("00:00:00")) {
-      return new Time(0);
-    } else {
-      return connection.getTimestampUtils().toTime(cal, string);
-    }
+    return connection.getTimestampUtils().toTime(cal, string);
   }
 
   //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
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/jdbc/TimestampUtils.java b/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
index aad8d137..182008dc 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
@@ -25,6 +25,7 @@ import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.OffsetDateTime;
 import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.time.chrono.IsoEra;
 import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoField;
@@ -463,25 +464,45 @@ public class TimestampUtils {
 
   public synchronized Time toTime(Calendar cal, String s) throws SQLException {
     // 1) Parse backend string
-    Timestamp timestamp = toTimestamp(cal, s);
-
-    if (timestamp == null) {
+    if (s == null) {
       return null;
     }
-
-    long millis = timestamp.getTime();
-    // infinity cannot be represented as Time
-    // so there's not much we can do here.
-    if (millis <= PGStatement.DATE_NEGATIVE_INFINITY
-        || millis >= PGStatement.DATE_POSITIVE_INFINITY) {
-      throw new PSQLException(
-          GT.tr("Infinite value found for timestamp/date. This cannot be represented as time."),
-          PSQLState.DATETIME_OVERFLOW);
+    ParsedTimestamp ts = parseBackendTimestamp(s);
+    Calendar useCal = ts.tz != null ? ts.tz : setupCalendar(cal);
+    if (ts.tz == null) {
+      // When no time zone provided (e.g. time or timestamp)
+      // We get the year-month-day from the string, then truncate the day to 1970-01-01
+      // This is used for timestamp -> time conversion
+      // Note: this cannot be merged with "else" branch since
+      // timestamps at which the time flips to/from DST depend on the date
+      // For instance, 2000-03-26 02:00:00 is invalid timestamp in Europe/Moscow time zone
+      // and the valid one is 2000-03-26 03:00:00. That is why we parse full timestamp
+      // then set year to 1970 later
+      useCal.set(Calendar.ERA, ts.era);
+      useCal.set(Calendar.YEAR, ts.year);
+      useCal.set(Calendar.MONTH, ts.month - 1);
+      useCal.set(Calendar.DAY_OF_MONTH, ts.day);
+    } else {
+      // When time zone is given, we just pick the time part and assume date to be 1970-01-01
+      // this is used for time, timez, and timestamptz parsing
+      useCal.set(Calendar.ERA, GregorianCalendar.AD);
+      useCal.set(Calendar.YEAR, 1970);
+      useCal.set(Calendar.MONTH, Calendar.JANUARY);
+      useCal.set(Calendar.DAY_OF_MONTH, 1);
     }
+    useCal.set(Calendar.HOUR_OF_DAY, ts.hour);
+    useCal.set(Calendar.MINUTE, ts.minute);
+    useCal.set(Calendar.SECOND, ts.second);
+    useCal.set(Calendar.MILLISECOND, 0);
 
+    long timeMillis = useCal.getTimeInMillis() + ts.nanos / 1000000;
+    if (ts.tz != null || (ts.year == 1970 && ts.era == GregorianCalendar.AD)) {
+      // time with time zone has proper time zone, so the value can be returned as is
+      return new Time(timeMillis);
+    }
 
-    // 2) Truncate date part so in given time zone the date would be formatted as 00:00
-    return convertToTime(timestamp.getTime(), cal == null ? null : cal.getTimeZone());
+    // 2) Truncate date part so in given time zone the date would be formatted as 01/01/1970
+    return convertToTime(timeMillis, useCal == null ? null : useCal.getTimeZone());
   }
 
   public synchronized Date toDate(Calendar cal, String s) throws SQLException {
@@ -741,6 +762,10 @@ public class TimestampUtils {
     return sbuf.toString();
   }
 
+  /**
+   * Formats {@link LocalDateTime} to be sent to the backend, thus it adds time zone.
+   * Do not use this method in {@link java.sql.ResultSet#getString(int)}
+   */
   public synchronized String toString(LocalDateTime localDateTime) {
     if (LocalDateTime.MAX.equals(localDateTime)) {
       return "infinity";
@@ -748,15 +773,9 @@ public class TimestampUtils {
       return "-infinity";
     }
 
-    sbuf.setLength(0);
-
-    LocalDate localDate = localDateTime.toLocalDate();
-    appendDate(sbuf, localDate);
-    sbuf.append(' ');
-    appendTime(sbuf, localDateTime.toLocalTime());
-    appendEra(sbuf, localDate);
-
-    return sbuf.toString();
+    // LocalDateTime is always passed with time zone so backend can decide between timestamp and timestamptz
+    ZonedDateTime zonedDateTime = localDateTime.atZone(getDefaultTz().toZoneId());
+    return toString(zonedDateTime.toOffsetDateTime());
   }
 
   private static void appendDate(StringBuilder sb, LocalDate localDate) {
@@ -913,16 +932,17 @@ public class TimestampUtils {
       timeOffset = ByteConverter.int4(bytes, 8);
       timeOffset *= -1000;
       millis -= timeOffset;
-    } else {
-      if (tz == null) {
-        tz = getDefaultTz();
-      }
+      return new Time(millis);
+    }
 
-      // Here be dragons: backend did not provide us the timezone, so we guess the actual point in
-      // time
-      millis = guessTimestamp(millis, tz);
+    if (tz == null) {
+      tz = getDefaultTz();
     }
 
+    // Here be dragons: backend did not provide us the timezone, so we guess the actual point in
+    // time
+    millis = guessTimestamp(millis, tz);
+
     return convertToTime(millis, tz); // Ensure date part is 1970-01-01
   }
 
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.
    *
diff --git a/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java b/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java
index a77b4b42..c097f460 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java
@@ -51,6 +51,7 @@ public enum PSQLState {
   NOT_IMPLEMENTED("0A000"),
 
   DATA_ERROR("22000"),
+  STRING_DATA_RIGHT_TRUNCATION("22001"),
   NUMERIC_VALUE_OUT_OF_RANGE("22003"),
   BAD_DATETIME_FORMAT("22007"),
   DATETIME_OVERFLOW("22008"),
diff --git a/pom.xml b/pom.xml
index 8b64490b..cd1d2e35 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,13 +3,13 @@
   <parent>
     <groupId>org.postgresql</groupId>
     <artifactId>pgjdbc-versions</artifactId>
-    <version>1.1.2</version>
+    <version>1.1.3</version>
   </parent>
 
   <artifactId>pgjdbc-aggregate</artifactId>
   <packaging>pom</packaging>
   <name>PostgreSQL JDBC Driver aggregate</name>
-  <version>42.1.5-SNAPSHOT</version>
+  <version>42.2.0-SNAPSHOT</version>
   <description>PgJDBC aggregate project</description>
   <url>https://github.com/pgjdbc/pgjdbc</url>
 
diff --git a/ubenchmark/pom.xml b/ubenchmark/pom.xml
index b21c2bc1..5d7a89a7 100644
--- a/ubenchmark/pom.xml
+++ b/ubenchmark/pom.xml
@@ -31,14 +31,14 @@ POSSIBILITY OF SUCH DAMAGE.
   <parent>
     <groupId>org.postgresql</groupId>
     <artifactId>pgjdbc-versions</artifactId>
-    <version>1.1.2</version>
+    <version>1.1.3</version>
     <relativePath />
   </parent>
 
   <artifactId>pgjdbc-benchmark</artifactId>
   <packaging>jar</packaging>
   <name>PostgreSQL JDBC Driver - benchmarks</name>
-  <version>42.1.5-SNAPSHOT</version>
+  <version>42.2.0-SNAPSHOT</version>
   <description>PostgreSQL JDBC Driver - benchmarks</description>
   <url>https://github.com/pgjdbc/pgjdbc</url>
 


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/hostchooser/MultiHostTestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostTestSuite.java
index b18f9a5a..2f1b8174 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostTestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostTestSuite.java
@@ -31,6 +31,17 @@ public class MultiHostTestSuite extends TestSuite {
     return DriverManager.getConnection(TestUtil.getURL(getSlaveServer(), getSlavePort()), props);
   }
 
+  public static java.sql.Connection openSlaveDB2() throws Exception {
+    TestUtil.initDriver();
+
+    Properties props = new Properties();
+
+    props.setProperty("user", TestUtil.getUser());
+    props.setProperty("password", TestUtil.getPassword());
+
+    return DriverManager.getConnection(TestUtil.getURL(getSlaveServer2(), getSlavePort2()), props);
+  }
+
   /*
    * Returns the Test server
    */
@@ -46,6 +57,21 @@ public class MultiHostTestSuite extends TestSuite {
         .parseInt(System.getProperty("slavePort", String.valueOf(TestUtil.getPort() + 1)));
   }
 
+  /*
+   * Returns the Test server
+   */
+  public static String getSlaveServer2() {
+    return System.getProperty("slaveServer2", TestUtil.getServer());
+  }
+
+  /*
+   * Returns the Test port
+   */
+  public static int getSlavePort2() {
+    return Integer
+        .parseInt(System.getProperty("slavePort2", String.valueOf(TestUtil.getPort() + 2)));
+  }
+
   /*
    * The main entry point for JUnit
    */
@@ -55,6 +81,9 @@ public class MultiHostTestSuite extends TestSuite {
     try {
       Connection connection = openSlaveDB();
       TestUtil.closeDB(connection);
+
+      connection = openSlaveDB2();
+      TestUtil.closeDB(connection);
     } catch (PSQLException ex) {
       // replication instance is not available, but suite must have at lest one test case
       suite.addTestSuite(DummyTest.class);
diff --git a/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java
index 6d9d7941..04c38746 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java
@@ -12,7 +12,7 @@ import static org.postgresql.hostchooser.HostRequirement.any;
 import static org.postgresql.hostchooser.HostRequirement.master;
 import static org.postgresql.hostchooser.HostRequirement.preferSlave;
 import static org.postgresql.hostchooser.HostRequirement.slave;
-import static org.postgresql.hostchooser.HostStatus.ConnectFail;
+import static org.postgresql.hostchooser.HostStatus.Master;
 import static org.postgresql.hostchooser.HostStatus.Slave;
 import static org.postgresql.test.TestUtil.closeDB;
 
@@ -41,9 +41,12 @@ public class MultiHostsConnectionTest extends TestCase {
   static final String master1 = TestUtil.getServer() + ":" + TestUtil.getPort();
   static final String slave1 =
       MultiHostTestSuite.getSlaveServer() + ":" + MultiHostTestSuite.getSlavePort();
+  static final String slave2 =
+      MultiHostTestSuite.getSlaveServer2() + ":" + MultiHostTestSuite.getSlavePort2();
   static final String fake1 = "127.127.217.217:1";
   static String masterIp;
   static String slaveIp;
+  static String slaveIp2;
   static String fakeIp = fake1;
 
   static Connection con;
@@ -63,6 +66,10 @@ public class MultiHostsConnectionTest extends TestCase {
       slaveIp = getRemoteHostSpec();
       closeDB(con);
 
+      con = MultiHostTestSuite.openSlaveDB2();
+      slaveIp2 = getRemoteHostSpec();
+      closeDB(con);
+
     } catch (Exception e) {
       throw new RuntimeException(e);
     }
@@ -106,7 +113,8 @@ public class MultiHostsConnectionTest extends TestCase {
       sb.append(target).append(',');
     }
     sb.setLength(sb.length() - 1);
-    sb.append("/test");
+    sb.append("/");
+    sb.append(TestUtil.getDatabase());
 
     return con = DriverManager.getConnection(sb.toString(), props);
   }
@@ -166,9 +174,9 @@ public class MultiHostsConnectionTest extends TestCase {
 
     getConnection(master, false, fake1, slave1, master1);
     assertRemote(masterIp);
-    assertGlobalState(fake1, "ConnectFail");
-    assertGlobalState(master1, "Master");
-    assertGlobalState(slave1, "Slave");
+    assertGlobalState(fake1, "ConnectFail"); // cached
+    assertGlobalState(master1, "Master"); // connected to master
+    assertGlobalState(slave1, "Slave"); // was unknown, so tried to connect in order
   }
 
   public static void testConnectToSlave() throws SQLException {
@@ -180,9 +188,9 @@ public class MultiHostsConnectionTest extends TestCase {
 
     getConnection(slave, false, fake1, master1, slave1);
     assertRemote(slaveIp);
-    assertGlobalState(fake1, "ConnectFail");
-    assertGlobalState(slave1, "Slave");
-    assertGlobalState(master1, "Master");
+    assertGlobalState(fake1, "ConnectFail"); // cached
+    assertGlobalState(slave1, "Slave"); // connected
+    assertGlobalState(master1, "Master"); // tried as it was unknown
   }
 
   public static void testConnectToSlaveFirst() throws SQLException {
@@ -192,13 +200,13 @@ public class MultiHostsConnectionTest extends TestCase {
     assertGlobalState(slave1, "Slave");
     assertGlobalState(master1, null);
 
-    getConnection(preferSlave, false, fake1, master1, slave1);
-    assertRemote(masterIp);
+    getConnection(slave, false, fake1, master1, slave1);
+    assertRemote(slaveIp);
     assertGlobalState(fake1, "ConnectFail");
     assertGlobalState(slave1, "Slave");
-    assertGlobalState(master1, "Master");
+    assertGlobalState(master1, "Master"); // tried as it was unknown
 
-    getConnection(preferSlave, false, fake1, master1, slave1);
+    getConnection(preferSlave, true, fake1, master1, slave1);
     assertRemote(slaveIp);
     assertGlobalState(fake1, "ConnectFail");
     assertGlobalState(slave1, "Slave");
@@ -229,14 +237,75 @@ public class MultiHostsConnectionTest extends TestCase {
     assertTrue("Never tried to connect to fake node", fake1FoundTried);
   }
 
-  public static void testHostRechecks() throws SQLException, InterruptedException {
-    getConnection(master, true, fake1, master1, slave1);
+  public static void testLoadBalancing_preferSlave() throws SQLException {
+    Set<String> connectedHosts = new HashSet<String>();
+    Set<HostSpec> tryConnectedHosts = new HashSet<HostSpec>();
+    for (int i = 0; i < 20; ++i) {
+      getConnection(preferSlave, true, true, fake1, master1, slave1, slave2);
+      connectedHosts.add(getRemoteHostSpec());
+      tryConnectedHosts.addAll(hostStatusMap.keySet());
+      if (tryConnectedHosts.size() == 4) {
+        break;
+      }
+    }
+    assertEquals("Never connected to all slave hosts", new HashSet<String>(asList(slaveIp, slaveIp2)),
+        connectedHosts);
+    assertEquals("Never tried to connect to fake node",4, tryConnectedHosts.size());
+
+    getConnection(preferSlave, false, true, fake1, master1, slave1);
+    assertRemote(slaveIp);
+    connectedHosts.clear();
+    for (int i = 0; i < 20; ++i) {
+      getConnection(preferSlave, false, true, fake1, master1, slave1, slave2);
+      connectedHosts.add(getRemoteHostSpec());
+      if (connectedHosts.size() == 2) {
+        break;
+      }
+    }
+    assertEquals("Never connected to all slave hosts", new HashSet<String>(asList(slaveIp, slaveIp2)),
+        connectedHosts);
+
+    // connect to master when there's no slave
+    getConnection(preferSlave, true, true, fake1, master1);
     assertRemote(masterIp);
-    assertGlobalState(fake1, "ConnectFail");
-    assertGlobalState(slave1, null);
 
-    GlobalHostStatusTracker.reportHostStatus(hostSpec(master1), ConnectFail);
-    assertGlobalState(master1, "ConnectFail");
+    getConnection(preferSlave, false, true, fake1, master1);
+    assertRemote(masterIp);
+  }
+
+  public static void testLoadBalancing_slave() throws SQLException {
+    Set<String> connectedHosts = new HashSet<String>();
+    Set<HostSpec> tryConnectedHosts = new HashSet<HostSpec>();
+    for (int i = 0; i < 20; ++i) {
+      getConnection(slave, true, true, fake1, master1, slave1, slave2);
+      connectedHosts.add(getRemoteHostSpec());
+      tryConnectedHosts.addAll(hostStatusMap.keySet());
+      if (tryConnectedHosts.size() == 4) {
+        break;
+      }
+    }
+    assertEquals("Never connected to all salve hosts", new HashSet<String>(asList(slaveIp, slaveIp2)),
+        connectedHosts);
+    assertEquals("Never tried to connect to maste and fake node", 4, tryConnectedHosts.size());
+
+    getConnection(preferSlave, false, true, fake1, master1, slave1);
+    assertRemote(slaveIp);
+    connectedHosts.clear();
+    for (int i = 0; i < 20; ++i) {
+      getConnection(slave, false, true, fake1, master1, slave1, slave2);
+      connectedHosts.add(getRemoteHostSpec());
+      if (connectedHosts.size() == 2) {
+        break;
+      }
+    }
+    assertEquals("Never connected to all slave hosts", new HashSet<String>(asList(slaveIp, slaveIp2)),
+        connectedHosts);
+  }
+
+  public static void testHostRechecks() throws SQLException, InterruptedException {
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(master1), Slave);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(slave1), Master);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(fake1), Slave);
 
     try {
       getConnection(master, false, fake1, slave1, master1);
@@ -244,6 +313,10 @@ public class MultiHostsConnectionTest extends TestCase {
     } catch (SQLException ex) {
     }
 
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(master1), Slave);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(slave1), Master);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(fake1), Slave);
+
     SECONDS.sleep(3);
 
     getConnection(master, false, slave1, fake1, master1);
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/AutoRollbackTestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/AutoRollbackTestSuite.java
index 18c1a4c9..0f2e5a6c 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/AutoRollbackTestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/AutoRollbackTestSuite.java
@@ -31,9 +31,11 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.EnumSet;
 import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
 
 @RunWith(Parameterized.class)
 public class AutoRollbackTestSuite extends BaseTest4 {
+  private static final AtomicInteger counter = new AtomicInteger();
 
   private enum FailMode {
     /**
@@ -343,7 +345,7 @@ public class AutoRollbackTestSuite extends BaseTest4 {
       con.setAutoCommit(false);
       Statement st = con.createStatement();
       st.executeUpdate(
-          "insert into rollbacktest(a, str) values (42, '" + System.currentTimeMillis() + "')");
+          "insert into rollbacktest(a, str) values (42, '" + System.currentTimeMillis() + "," + counter.getAndIncrement() + "')");
       st.close();
     }
     con.commit();
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/jdbc2/TimezoneTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java
index b436d5ae..e660f052 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java
@@ -189,10 +189,10 @@ public class TimezoneTest {
       // 1970-01-01 15:00:00 +0300 -> 1970-01-01 07:00:00 -0500
       assertEquals(43200000L, ts.getTime());
       ts = rs.getTimestamp(4, cGMT13);
-      // 1970-01-01 15:00:00 +0300 -> 1970-01-02 01:00:00 +1300 (CHECK ME)
-      assertEquals(-43200000L, ts.getTime());
-      str = rs.getString(3);
-      assertEquals("timetz -> getString" + format, "15:00:00", str);
+      // 1970-01-01 15:00:00 +0300 -> 1970-01-02 01:00:00 +1300
+      assertEquals(43200000L, ts.getTime());
+      str = rs.getString(4);
+      assertEquals("timetz -> getString" + format, "15:00:00+03", str);
 
       // date: 2005-01-01
       ts = rs.getTimestamp(5);
@@ -299,7 +299,7 @@ public class TimezoneTest {
       assertEquals(43200000L, t.getTime());
       t = rs.getTime(1, cGMT13);
       // 2005-01-02 01:00:00 +1300 -> 1970-01-01 01:00:00 +1300
-      assertEquals(-43200000L, t.getTime());
+      assertEquals(43200000L, t.getTime());
 
       // timestamp: 2005-01-01 15:00:00
       t = rs.getTime(2);
@@ -335,7 +335,7 @@ public class TimezoneTest {
       t = rs.getTime(4, cGMT05);
       assertEquals(43200000L, t.getTime()); // 1970-01-01 07:00:00 -0500
       t = rs.getTime(4, cGMT13);
-      assertEquals(-43200000L, t.getTime()); // 1970-01-01 01:00:00 +1300
+      assertEquals(43200000L, t.getTime()); // 1970-01-01 01:00:00 +1300
       rs.close();
     }
   }
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java
index 6e6f74a8..36a6e440 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java
@@ -12,8 +12,10 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import org.postgresql.PGStatement;
 import org.postgresql.test.TestUtil;
 import org.postgresql.test.jdbc2.BaseTest4;
+import org.postgresql.util.PSQLState;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,16 +68,19 @@ public class GeneratedKeysTest extends BaseTest4 {
   private final ReturningInQuery returningInQuery;
   private final String returningClause;
 
-  public GeneratedKeysTest(ReturningInQuery returningInQuery) throws Exception {
+  public GeneratedKeysTest(ReturningInQuery returningInQuery, BinaryMode binaryMode) throws Exception {
     this.returningInQuery = returningInQuery;
     this.returningClause = returningInQuery.getClause();
+    setBinaryMode(binaryMode);
   }
 
-  @Parameterized.Parameters(name = "returningInQuery = {0}")
+  @Parameterized.Parameters(name = "returningInQuery = {0}, binary = {1}")
   public static Iterable<Object[]> data() {
     Collection<Object[]> ids = new ArrayList<Object[]>();
     for (ReturningInQuery returningInQuery : ReturningInQuery.values()) {
-      ids.add(new Object[]{returningInQuery});
+      for (BinaryMode binaryMode : BinaryMode.values()) {
+        ids.add(new Object[]{returningInQuery, binaryMode});
+      }
     }
     return ids;
   }
@@ -83,7 +88,7 @@ public class GeneratedKeysTest extends BaseTest4 {
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    TestUtil.createTempTable(con, "genkeys", "a serial, b text, c int");
+    TestUtil.createTempTable(con, "genkeys", "a serial, b varchar(5), c int");
   }
 
   @Override
@@ -447,5 +452,36 @@ public class GeneratedKeysTest extends BaseTest4 {
     s.close();
   }
 
+  @Test
+  public void breakDescribeOnFirstServerPreparedExecution() throws SQLException {
+    // Test code is adapted from https://github.com/pgjdbc/pgjdbc/issues/811#issuecomment-352468388
+
+    PreparedStatement ps =
+        con.prepareStatement("insert into genkeys(b) values(?)" + returningClause,
+            Statement.RETURN_GENERATED_KEYS);
+    ps.setString(1, "TEST");
+
+    // The below "prepareThreshold - 1" executions ensure that bind failure would happen
+    // exactly on prepareThreshold execution (the first one when server flips to server-prepared)
+    int prepareThreshold = ps.unwrap(PGStatement.class).getPrepareThreshold();
+    for (int i = 0; i < prepareThreshold - 1; i++) {
+      ps.executeUpdate();
+    }
+    try {
+      // Send a value that's too long on the 5th request
+      ps.setString(1, "TESTTESTTEST");
+      ps.executeUpdate();
+    } catch (SQLException e) {
+      // Expected error: org.postgresql.util.PSQLException: ERROR: value
+      // too long for type character varying(10)
+      if (!PSQLState.STRING_DATA_RIGHT_TRUNCATION.getState().equals(e.getSQLState())) {
+        throw e;
+      }
+    }
+    // Send a valid value on the next request
+    ps.setString(1, "TEST");
+    ps.executeUpdate();
+  }
+
 }
 
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
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java
index da43767f..654d9133 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java
@@ -30,6 +30,7 @@ import java.time.OffsetDateTime;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
 import java.time.chrono.IsoEra;
+import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoField;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -103,10 +104,6 @@ public class SetObject310Test {
     insert(data, columnName, null);
   }
 
-  private void insertWithType(OffsetDateTime data, String columnName) throws SQLException {
-    insert(data, columnName, Types.TIMESTAMP_WITH_TIMEZONE);
-  }
-
   private <T> T insertThenReadWithoutType(Object data, String columnName, Class<T> expectedType) throws SQLException {
     PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?"));
     try {
@@ -178,7 +175,9 @@ public class SetObject310Test {
       ZoneId zone = ZoneId.of(zoneId);
       for (String date : datesToTest) {
         LocalDateTime localDateTime = LocalDateTime.parse(date);
-        String expected = date.replace('T', ' ');
+        String expected = localDateTime.atZone(zone)
+            .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
+            .replace('T', ' ');
         localTimestamps(zone, localDateTime, expected);
       }
     }
@@ -241,37 +240,42 @@ public class SetObject310Test {
   private void localTimestamps(ZoneId zoneId, LocalDateTime localDateTime, String expected) throws SQLException {
     TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
     String readBack = insertThenReadStringWithoutType(localDateTime, "timestamp_without_time_zone_column");
-    assertEquals(expected, readBack);
+    assertEquals(
+        "LocalDateTime=" + localDateTime + ", with TimeZone.default=" + zoneId + ", setObject(int, Object)",
+        expected, readBack);
     deleteRows();
 
     readBack = insertThenReadStringWithType(localDateTime, "timestamp_without_time_zone_column");
-    assertEquals(expected, readBack);
+    assertEquals(
+        "LocalDateTime=" + localDateTime + ", with TimeZone.default=" + zoneId + ", setObject(int, Object, TIMESTAMP)",
+        expected, readBack);
     deleteRows();
   }
 
   private void offsetTimestamps(ZoneId dataZone, LocalDateTime localDateTime, String expected, List<TimeZone> storeZones) throws SQLException {
     OffsetDateTime data = localDateTime.atZone(dataZone).toOffsetDateTime();
-    for (TimeZone storeZone : storeZones) {
-      TimeZone.setDefault(storeZone);
-      insertWithoutType(data, "timestamp_with_time_zone_column");
-
-      String readBack = readString("timestamp_with_time_zone_column");
-      OffsetDateTime o = OffsetDateTime.parse(readBack.replace(' ', 'T') + ":00");
-      assertEquals(data.toInstant(), o.toInstant());
-
-      deleteRows();
-    }
-
-    for (TimeZone storeZone : storeZones) {
-      TimeZone.setDefault(storeZone);
-      insertWithType(data, "timestamp_with_time_zone_column");
-
-      String readBack = readString("timestamp_with_time_zone_column");
-      OffsetDateTime o = OffsetDateTime.parse(readBack.replace(' ', 'T') + ":00");
-
-      assertEquals(data.toInstant(), o.toInstant());
-
-      deleteRows();
+    try (PreparedStatement ps = con.prepareStatement(
+        "select ?::timestamp with time zone, ?::timestamp with time zone")) {
+      for (TimeZone storeZone : storeZones) {
+        TimeZone.setDefault(storeZone);
+        ps.setObject(1, data);
+        ps.setObject(2, data, Types.TIMESTAMP_WITH_TIMEZONE);
+        try (ResultSet rs = ps.executeQuery()) {
+          rs.next();
+          String noType = rs.getString(1);
+          OffsetDateTime noTypeRes = OffsetDateTime.parse(noType.replace(' ', 'T') + ":00");
+          assertEquals(
+              "OffsetDateTime=" + data + " (with ZoneId=" + dataZone + "), with TimeZone.default="
+                  + storeZone + ", setObject(int, Object)", data.toInstant(),
+              noTypeRes.toInstant());
+          String withType = rs.getString(1);
+          OffsetDateTime withTypeRes = OffsetDateTime.parse(withType.replace(' ', 'T') + ":00");
+          assertEquals(
+              "OffsetDateTime=" + data + " (with ZoneId=" + dataZone + "), with TimeZone.default="
+                  + storeZone + ", setObject(int, Object, TIMESTAMP_WITH_TIMEZONE)",
+              data.toInstant(), withTypeRes.toInstant());
+        }
+      }
     }
   }