Repository: pgjdbc/pgjdbc
Pull request #913 (2017-08-08 17:06:29)

refactor: use TypeInfo getPGArrayType instead of munging type name
The purpose of the getPGArrayType method is to get the array type for
a given element type. Let the TypeInfo implementation figure out how
to do that rather than appending "[]" to the element type name in
PgPreparedStatement setArray.

vlsi: @grzm ,  Is it something that can be tested?
grzm: @vlsi There's already coverage of this functionality elsewhere in the code base. Anywhere that tests exercises `PgPreparedStatement.setArray` confirms that it works. One in particular is jdbc4.ArrayTest:

https://github.com/pgjdbc/pgjdbc/blob/master/pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java#L105
codecov-io: # [Codecov](https://codecov.io/gh/pgjdbc/pgjdbc/pull/913?src=pr&el=h1) Report
> Merging [#913](https://codecov.io/gh/pgjdbc/pgjdbc/pull/913?src=pr&el=desc) into [master](https://codecov.io/gh/pgjdbc/pgjdbc/commit/50d5dd3e708a92602e04d6b4aa0822ad3f110a78?src=pr&el=desc) will **increase** coverage by `0.03%`.
> The diff coverage is `100%`.



```diff
@@             Coverage Diff              @@
##             master     #913      +/-   ##
============================================
+ Coverage     65.85%   65.88%   +0.03%     
- Complexity     3546     3549       +3     
============================================
  Files           166      166              
  Lines         15230    15230              
  Branches       2470     2470              
============================================
+ Hits          10029    10034       +5     
+ Misses         4022     4020       -2     
+ Partials       1179     1176       -3
```

vlsi: What I mean is: can a new test be created to test this change?
grzm: I'm all for tests supporting code changes. In this case I don't see what a test would look like other than call what is already in the test suite. What would you like to see such a test do other than call `setArray` as other locations already are?
vlsi: @grzm , do you know if there are performance implications and/or performance improvements?
jorsol: The main difference I can tell is  that it additionally looks for an alias, then it use the same getPGType method:
```java
  public int getPGArrayType(String elementTypeName) throws SQLException {
    elementTypeName = getTypeForAlias(elementTypeName);
    return getPGType(elementTypeName + "[]");
  }
```
grzm: I haven't run the benchmark test suite, so I really can't say. I wouldn't expect any significant change. As @jorsol points out, it does do an additional alias check, but that list is fixed and small. I'm more than happy to run them to confirm. If you could point me to documentation on how to set up and run the benchmarks, I'd appreciate it. I'm using IntelliJ if that makes a difference.
davecramer: @grzm there is a subproject called ubenchmark which has the benchmarks in it.
grzm: @davecramer Looking at the existing ubenchmark tests, I didn't see anything that would exercise this specifically, so I added some. Here's how I ran the benchmarks (from the root of the pgjdbc checkout):

    mvn clean package -DskipTests && java -classpath pgjdbc/target/postgresql-42.1.5-SNAPSHOT.jar:ubenchmark/target/benchmarks.jar -Duser=grzm -Dport=5496 org.postgresql.benchmark.statement.BindArray

If there's a better way to run these, please let me know. The documentation I referred to was in a comment in the FinalizeConnection class.

mvn clean package -DskipTests && java -classpath pgjdbc/target/postgresql-42.1.5-SNAPSHOT.jar:ubenchmark/target/benchmarks.jar -Duser=grzm -Dport=5496 org.postgresql.benchmark.statement.BindArray

I'm not well-versed in reading the output of the test results, so here's what I see:

HEAD (9813c685cae2cbfbc561a6220ba388cef08f34b0)
```
# JMH 1.17.4 (released 285 days ago, please consider updating!)
# VM version: JDK 1.8.0_45, VM 25.45-b02
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/bin/java
# VM options: -Duser=grzm -Dport=5496
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.postgresql.benchmark.statement.BindArray.setArray

# Run progress: 0.00% complete, ETA 00:00:20
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration   1: 1650.381 ns/op
# Warmup Iteration   2: 1316.504 ns/op
# Warmup Iteration   3: 1542.541 ns/op
# Warmup Iteration   4: 3197.746 ns/op
# Warmup Iteration   5: 966.529 ns/op
Iteration   1: 953.238 ns/op
                 ·gc.alloc.rate:               777.107 MB/sec
                 ·gc.alloc.rate.norm:          1168.000 B/op
                 ·gc.churn.PS_Eden_Space:      747.590 MB/sec
                 ·gc.churn.PS_Eden_Space.norm: 1123.636 B/op
                 ·gc.count:                    2.000 counts
                 ·gc.time:                     1.000 ms

Iteration   2: 913.015 ns/op
                 ·gc.alloc.rate:                   813.470 MB/sec
                 ·gc.alloc.rate.norm:              1168.000 B/op
                 ·gc.churn.PS_Eden_Space:          680.211 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     976.664 B/op
                 ·gc.churn.PS_Survivor_Space:      0.042 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.060 B/op
                 ·gc.count:                        2.000 counts
                 ·gc.time:                         1.000 ms

Iteration   3: 860.148 ns/op
                 ·gc.alloc.rate:                   859.812 MB/sec
                 ·gc.alloc.rate.norm:              1168.000 B/op
                 ·gc.churn.PS_Eden_Space:          903.178 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     1226.910 B/op
                 ·gc.churn.PS_Survivor_Space:      0.062 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.084 B/op
                 ·gc.count:                        3.000 counts
                 ·gc.time:                         2.000 ms

Iteration   4: 1143.256 ns/op
                 ·gc.alloc.rate:                   649.123 MB/sec
                 ·gc.alloc.rate.norm:              1168.000 B/op
                 ·gc.churn.PS_Eden_Space:          782.311 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     1407.654 B/op
                 ·gc.churn.PS_Survivor_Space:      0.041 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.074 B/op
                 ·gc.count:                        3.000 counts
                 ·gc.time:                         1.000 ms

Iteration   5: 1131.766 ns/op
                 ·gc.alloc.rate:                   652.394 MB/sec
                 ·gc.alloc.rate.norm:              1168.001 B/op
                 ·gc.churn.PS_Eden_Space:          462.736 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     828.451 B/op
                 ·gc.churn.PS_Survivor_Space:      0.021 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.037 B/op
                 ·gc.count:                        2.000 counts
                 ·gc.time:                         1.000 ms


Result "org.postgresql.benchmark.statement.BindArray.setArray":
  1000.284 ±(99.9%) 499.084 ns/op [Average]
  (min, avg, max) = (860.148, 1000.284, 1143.256), stdev = 129.611
  CI (99.9%): [501.200, 1499.369] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.alloc.rate":
  750.381 ±(99.9%) 367.956 MB/sec [Average]
  (min, avg, max) = (649.123, 750.381, 859.812), stdev = 95.557
  CI (99.9%): [382.425, 1118.337] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.alloc.rate.norm":
  1168.001 ±(99.9%) 0.001 B/op [Average]
  (min, avg, max) = (1168.000, 1168.001, 1168.001), stdev = 0.001
  CI (99.9%): [1168.000, 1168.001] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.churn.PS_Eden_Space":
  715.205 ±(99.9%) 626.399 MB/sec [Average]
  (min, avg, max) = (462.736, 715.205, 903.178), stdev = 162.674
  CI (99.9%): [88.806, 1341.604] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.churn.PS_Eden_Space.norm":
  1112.663 ±(99.9%) 859.877 B/op [Average]
  (min, avg, max) = (828.451, 1112.663, 1407.654), stdev = 223.307
  CI (99.9%): [252.786, 1972.540] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.churn.PS_Survivor_Space":
  0.033 ±(99.9%) 0.091 MB/sec [Average]
  (min, avg, max) = (≈ 0, 0.033, 0.062), stdev = 0.024
  CI (99.9%): [≈ 0, 0.124] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.churn.PS_Survivor_Space.norm":
  0.051 ±(99.9%) 0.130 B/op [Average]
  (min, avg, max) = (≈ 0, 0.051, 0.084), stdev = 0.034
  CI (99.9%): [≈ 0, 0.181] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.count":
  12.000 ±(99.9%) 0.001 counts [Sum]
  (min, avg, max) = (2.000, 2.400, 3.000), stdev = 0.548
  CI (99.9%): [12.000, 12.000] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.time":
  6.000 ±(99.9%) 0.001 ms [Sum]
  (min, avg, max) = (1.000, 1.200, 2.000), stdev = 0.447
  CI (99.9%): [6.000, 6.000] (assumes normal distribution)
```

With this patch:
```
# JMH 1.17.4 (released 285 days ago, please consider updating!)
# VM version: JDK 1.8.0_45, VM 25.45-b02
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/bin/java
# VM options: -Duser=grzm -Dport=5496
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.postgresql.benchmark.statement.BindArray.setArray

# Run progress: 0.00% complete, ETA 00:00:20
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration   1: 1967.354 ns/op
# Warmup Iteration   2: 1257.025 ns/op
# Warmup Iteration   3: 1245.982 ns/op
# Warmup Iteration   4: 939.770 ns/op
# Warmup Iteration   5: 932.599 ns/op
Iteration   1: 942.383 ns/op
                 ·gc.alloc.rate:                   737.533 MB/sec
                 ·gc.alloc.rate.norm:              1096.000 B/op
                 ·gc.churn.PS_Eden_Space:          745.200 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     1107.394 B/op
                 ·gc.churn.PS_Survivor_Space:      0.042 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.062 B/op
                 ·gc.count:                        3.000 counts
                 ·gc.time:                         2.000 ms

Iteration   2: 887.995 ns/op
                 ·gc.alloc.rate:                   784.515 MB/sec
                 ·gc.alloc.rate.norm:              1096.000 B/op
                 ·gc.churn.PS_Eden_Space:          844.316 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     1179.544 B/op
                 ·gc.churn.PS_Survivor_Space:      0.083 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.116 B/op
                 ·gc.count:                        4.000 counts
                 ·gc.time:                         3.000 ms

Iteration   3: 838.363 ns/op
                 ·gc.alloc.rate:                   829.052 MB/sec
                 ·gc.alloc.rate.norm:              1096.000 B/op
                 ·gc.churn.PS_Eden_Space:          861.607 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     1139.037 B/op
                 ·gc.churn.PS_Survivor_Space:      0.041 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.055 B/op
                 ·gc.count:                        5.000 counts
                 ·gc.time:                         2.000 ms

Iteration   4: 1441.615 ns/op
                 ·gc.alloc.rate:                   483.515 MB/sec
                 ·gc.alloc.rate.norm:              1096.001 B/op
                 ·gc.churn.PS_Eden_Space:          434.611 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     985.148 B/op
                 ·gc.churn.PS_Survivor_Space:      0.042 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.094 B/op
                 ·gc.count:                        3.000 counts
                 ·gc.time:                         2.000 ms

Iteration   5: 1029.038 ns/op
                 ·gc.alloc.rate:                   676.309 MB/sec
                 ·gc.alloc.rate.norm:              1096.001 B/op
                 ·gc.churn.PS_Eden_Space:          717.857 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     1163.332 B/op
                 ·gc.churn.PS_Survivor_Space:      0.042 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.067 B/op
                 ·gc.count:                        6.000 counts
                 ·gc.time:                         3.000 ms


Result "org.postgresql.benchmark.statement.BindArray.setArray":
  1027.879 ±(99.9%) 931.292 ns/op [Average]
  (min, avg, max) = (838.363, 1027.879, 1441.615), stdev = 241.854
  CI (99.9%): [96.586, 1959.171] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.alloc.rate":
  702.185 ±(99.9%) 518.799 MB/sec [Average]
  (min, avg, max) = (483.515, 702.185, 829.052), stdev = 134.731
  CI (99.9%): [183.385, 1220.984] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.alloc.rate.norm":
  1096.001 ±(99.9%) 0.001 B/op [Average]
  (min, avg, max) = (1096.000, 1096.001, 1096.001), stdev = 0.001
  CI (99.9%): [1096.000, 1096.001] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.churn.PS_Eden_Space":
  720.718 ±(99.9%) 660.223 MB/sec [Average]
  (min, avg, max) = (434.611, 720.718, 861.607), stdev = 171.458
  CI (99.9%): [60.495, 1380.941] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.churn.PS_Eden_Space.norm":
  1114.891 ±(99.9%) 298.264 B/op [Average]
  (min, avg, max) = (985.148, 1114.891, 1179.544), stdev = 77.458
  CI (99.9%): [816.627, 1413.155] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.churn.PS_Survivor_Space":
  0.050 ±(99.9%) 0.071 MB/sec [Average]
  (min, avg, max) = (0.041, 0.050, 0.083), stdev = 0.019
  CI (99.9%): [≈ 0, 0.121] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.churn.PS_Survivor_Space.norm":
  0.079 ±(99.9%) 0.098 B/op [Average]
  (min, avg, max) = (0.055, 0.079, 0.116), stdev = 0.026
  CI (99.9%): [≈ 0, 0.177] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.count":
  21.000 ±(99.9%) 0.001 counts [Sum]
  (min, avg, max) = (3.000, 4.200, 6.000), stdev = 1.304
  CI (99.9%): [21.000, 21.000] (assumes normal distribution)

Secondary result "org.postgresql.benchmark.statement.BindArray.setArray:·gc.time":
  12.000 ±(99.9%) 0.001 ms [Sum]
  (min, avg, max) = (2.000, 2.400, 3.000), stdev = 0.548
  CI (99.9%): [12.000, 12.000] (assumes normal distribution)
```

grzm: Updated benchmarks with Fork=1, and warmup and measurement set to 10. I'm just including the ns/op result summary. Happy to include the full results upon request.

HEAD
```
# JMH 1.17.4 (released 285 days ago, please consider updating!)
# VM version: JDK 1.8.0_45, VM 25.45-b02
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/bin/java
# VM options: -Xmx128m -Duser=grzm -Dport=5496
# Warmup: 10 iterations, 1 s each
# Measurement: 10 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.postgresql.benchmark.statement.BindArray.setArray

# Run progress: 0.00% complete, ETA 00:00:40
# Fork: 1 of 1
# Preparing profilers: FlightRecorderProfiler 
...
Result "org.postgresql.benchmark.statement.BindArray.setArray":
  1015.380 ±(99.9%) 195.903 ns/op [Average]
  (min, avg, max) = (851.901, 1015.380, 1284.863), stdev = 129.578
  CI (99.9%): [819.477, 1211.283] (assumes normal distribution)
```

patched
```
# JMH 1.17.4 (released 285 days ago, please consider updating!)
# VM version: JDK 1.8.0_45, VM 25.45-b02
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/bin/java
# VM options: -Xmx128m -Duser=grzm -Dport=5496
# Warmup: 10 iterations, 1 s each
# Measurement: 10 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.postgresql.benchmark.statement.BindArray.setArray

# Run progress: 0.00% complete, ETA 00:00:40
# Fork: 1 of 1
...
Result "org.postgresql.benchmark.statement.BindArray.setArray":
  1122.271 ±(99.9%) 449.082 ns/op [Average]
  (min, avg, max) = (846.299, 1122.271, 1647.941), stdev = 297.040
  CI (99.9%): [673.190, 1571.353] (assumes normal distribution)
```

Patch:
diff --git a/.travis.yml b/.travis.yml
index 7a3d80c9..0b11bc35 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -62,6 +62,11 @@ cache:
 
 matrix:
   fast_finish: true
+  allow_failures:
+    - env: # REMOVE FAILURE extendedCacheEverything
+        - PG_VERSION=9.4
+        - QUERY_MODE=extendedCacheEverything
+        - COVERAGE=Y
   include:
     - jdk: oraclejdk8
       env: RUN_CHECKSTYLE=true
@@ -110,13 +115,11 @@ matrix:
         - XA=true
         - REPLICATION=Y
     - jdk: oraclejdk8
-      sudo: required
       addons:
         postgresql: "9.4"
       env:
         - PG_VERSION=9.4
-        - XA=true
-        - REPLICATION=Y
+        - QUERY_MODE=extendedCacheEverything
         - COVERAGE=Y
     - jdk: openjdk7
       sudo: required
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 00000000..02d10616
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,31 @@
+# appveyor.yml
+init: 
+- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
+- ps: Add-Content "c:\program files\postgresql\9.6\data\postgresql.conf" "wal_level=logical"
+- ps: Add-Content "c:\program files\postgresql\9.6\data\postgresql.conf" "max_wal_senders=3"
+- ps: Add-Content "c:\program files\postgresql\9.6\data\postgresql.conf" "wal_keep_segments=10"
+- ps: Add-Content "c:\program files\postgresql\9.6\data\postgresql.conf" "wal_sender_timeout=5s"
+- ps: Add-Content "c:\program files\postgresql\9.6\data\postgresql.conf" "max_replication_slots=10"
+- ps: Add-Content "c:\program files\postgresql\9.6\data\pg_hba.conf" "host    replication     all             127.0.0.1/32            trust"
+
+services: 
+  - postgresql96
+
+before_build:
+  - SET PATH=C:\Program Files\PostgreSQL\9.6\bin\;%PATH% 
+  - SET PGUSER=postgres 
+  - SET PGPASSWORD=Password12! 
+  - createuser -U postgres test
+  - psql -U postgres -c "alter user test with password 'test'" postgres 
+  - createdb -U postgres -O test test
+
+build_script:
+  - mvn clean package -DskipTests
+
+test_script:
+  - echo redirect escape ^> foo.bar
+  - echo privilegedPassword=Password12!>c:\projects\pgjdbc\build.local.properties
+  - mvn package
+
+cache:
+  - C:\Users\appveyor\.m2
diff --git a/pgjdbc/src/main/java/org/postgresql/core/PGStream.java b/pgjdbc/src/main/java/org/postgresql/core/PGStream.java
index c65bb18d..99fe247d 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/PGStream.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/PGStream.java
@@ -14,6 +14,7 @@ import java.io.BufferedOutputStream;
 import java.io.Closeable;
 import java.io.EOFException;
 import java.io.FilterOutputStream;
+import java.io.Flushable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -30,7 +31,7 @@ import javax.net.SocketFactory;
  * In general, instances of PGStream are not threadsafe; the caller must ensure that only one thread
  * at a time is accessing a particular PGStream instance.
  */
-public class PGStream implements Closeable {
+public class PGStream implements Closeable, Flushable {
   private final SocketFactory socketFactory;
   private final HostSpec hostSpec;
 
@@ -502,6 +503,7 @@ public class PGStream implements Closeable {
    *
    * @throws IOException if an I/O error occurs
    */
+  @Override
   public void flush() throws IOException {
     if (encodingWriter != null) {
       encodingWriter.flush();
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 41b01eca..2716aac5 100644
--- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
+++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
@@ -1075,6 +1075,15 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     return url.toString();
   }
 
+  /**
+   * Generates a {@link DriverManager} URL from the other properties supplied.
+   *
+   * @return {@link DriverManager} URL from the other properties supplied
+   */
+  public String getURL() {
+    return getUrl();
+  }
+
   /**
    * Sets properties from a {@link DriverManager} URL.
    *
@@ -1089,6 +1098,16 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     }
   }
 
+  /**
+   * Sets properties from a {@link DriverManager} URL.
+   * Added to follow convention used in other DBMS.
+   *
+   * @param url properties to set
+   */
+  public void setURL(String url) {
+    setUrl(url);
+  }
+
   public String getProperty(String name) throws SQLException {
     PGProperty pgProperty = PGProperty.forName(name);
     if (pgProperty != null) {
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
index e7b5c447..f35fea27 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
@@ -2541,7 +2541,41 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
 
   public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern)
       throws SQLException {
-    return getProcedures(catalog, schemaPattern, functionNamePattern);
+
+    // The pg_get_function_result only exists 8.4 or later
+    boolean pgFuncResultExists = connection.haveMinimumServerVersion(ServerVersion.v8_4);
+
+    // Use query that support pg_get_function_result to get function result, else unknown is defaulted
+    String funcTypeSql = DatabaseMetaData.functionResultUnknown + " ";
+    if (pgFuncResultExists) {
+      funcTypeSql = " CASE "
+              + "   WHEN (format_type(p.prorettype, null) = 'unknown') THEN " + DatabaseMetaData.functionResultUnknown
+              + "   WHEN "
+              + "     (substring(pg_get_function_result(p.oid) from 0 for 6) = 'TABLE') OR "
+              + "     (substring(pg_get_function_result(p.oid) from 0 for 6) = 'SETOF') THEN " + DatabaseMetaData.functionReturnsTable
+              + "   ELSE " + DatabaseMetaData.functionNoTable
+              + " END ";
+    }
+
+    // Build query and result
+    String sql;
+    sql = "SELECT current_database() AS FUNCTION_CAT, n.nspname AS FUNCTION_SCHEM, p.proname AS FUNCTION_NAME, "
+        + " d.description AS REMARKS, "
+        + funcTypeSql + " AS FUNCTION_TYPE, "
+        + " p.proname || '_' || p.oid AS SPECIFIC_NAME "
+        + "FROM pg_catalog.pg_proc p "
+        + "INNER JOIN pg_catalog.pg_namespace n ON p.pronamespace=n.oid "
+        + "LEFT JOIN pg_catalog.pg_description d ON p.oid=d.objoid "
+        + "WHERE pg_function_is_visible(p.oid) ";
+    if (schemaPattern != null && !schemaPattern.isEmpty()) {
+      sql += " AND n.nspname LIKE " + escapeQuotes(schemaPattern);
+    }
+    if (functionNamePattern != null && !functionNamePattern.isEmpty()) {
+      sql += " AND p.proname LIKE " + escapeQuotes(functionNamePattern);
+    }
+    sql += " ORDER BY FUNCTION_SCHEM, FUNCTION_NAME, p.oid::text ";
+
+    return createMetaDataStatement().executeQuery(sql);
   }
 
   public ResultSet getFunctionColumns(String catalog, String schemaPattern,
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
index 864cd926..517e96b6 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
@@ -1096,10 +1096,8 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
     // literal from Array.toString(), such as the implementation we return
     // from ResultSet.getArray(). Eventually we need a proper implementation
     // here that works for any Array implementation.
-
-    // Add special suffix for array identification
-    String typename = x.getBaseTypeName() + "[]";
-    int oid = connection.getTypeInfo().getPGType(typename);
+    String typename = x.getBaseTypeName();
+    int oid = connection.getTypeInfo().getPGArrayType(typename);
     if (oid == Oid.UNSPECIFIED) {
       throw new PSQLException(GT.tr("Unknown type {0}.", typename),
           PSQLState.INVALID_PARAMETER_TYPE);
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSetMetaData.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSetMetaData.java
index d075b236..cdf25f40 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSetMetaData.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSetMetaData.java
@@ -8,6 +8,7 @@ package org.postgresql.jdbc;
 import org.postgresql.PGResultSetMetaData;
 import org.postgresql.core.BaseConnection;
 import org.postgresql.core.Field;
+import org.postgresql.core.ServerVersion;
 import org.postgresql.util.GT;
 import org.postgresql.util.JdbcBlackHole;
 import org.postgresql.util.LruCache;
@@ -233,9 +234,14 @@ public class PgResultSetMetaData implements ResultSetMetaData, PGResultSetMetaDa
 
     StringBuilder sql = new StringBuilder(
         "SELECT c.oid, a.attnum, a.attname, c.relname, n.nspname, "
-            + "a.attnotnull OR (t.typtype = 'd' AND t.typnotnull), "
-            + "pg_catalog.pg_get_expr(d.adbin, d.adrelid) LIKE '%nextval(%' "
-            + "FROM pg_catalog.pg_class c "
+            + "a.attnotnull OR (t.typtype = 'd' AND t.typnotnull), ");
+
+    if ( connection.haveMinimumServerVersion(ServerVersion.v10)) {
+      sql.append("a.attidentity != '' OR pg_catalog.pg_get_expr(d.adbin, d.adrelid) LIKE '%nextval(%' ");
+    } else {
+      sql.append("pg_catalog.pg_get_expr(d.adbin, d.adrelid) LIKE '%nextval(%' ");
+    }
+    sql.append( "FROM pg_catalog.pg_class c "
             + "JOIN pg_catalog.pg_namespace n ON (c.relnamespace = n.oid) "
             + "JOIN pg_catalog.pg_attribute a ON (c.oid = a.attrelid) "
             + "JOIN pg_catalog.pg_type t ON (a.atttypid = t.oid) "
diff --git a/pgjdbc/src/main/java/org/postgresql/largeobject/LargeObject.java b/pgjdbc/src/main/java/org/postgresql/largeobject/LargeObject.java
index 1ec39244..b486ea40 100644
--- a/pgjdbc/src/main/java/org/postgresql/largeobject/LargeObject.java
+++ b/pgjdbc/src/main/java/org/postgresql/largeobject/LargeObject.java
@@ -41,7 +41,11 @@ import java.sql.SQLException;
  * @see java.sql.PreparedStatement#setBinaryStream
  * @see java.sql.PreparedStatement#setUnicodeStream
  */
-public class LargeObject {
+public class LargeObject
+    //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1"
+    implements AutoCloseable
+    //#endif
+    /* hi, checkstyle */ {
   /**
    * Indicates a seek from the begining of a file
    */
diff --git a/pgjdbc/src/main/java/org/postgresql/replication/PGReplicationStream.java b/pgjdbc/src/main/java/org/postgresql/replication/PGReplicationStream.java
index 698d1c75..ea07a985 100644
--- a/pgjdbc/src/main/java/org/postgresql/replication/PGReplicationStream.java
+++ b/pgjdbc/src/main/java/org/postgresql/replication/PGReplicationStream.java
@@ -17,7 +17,11 @@ import java.sql.SQLException;
  * It means that process wal record should be fast as possible, because during process wal record
  * lead to disconnect by timeout from server.
  */
-public interface PGReplicationStream {
+public interface PGReplicationStream
+    //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1"
+    extends AutoCloseable
+    //#endif
+    /* hi, checkstyle */ {
 
   /**
    * <p>Read next wal record from backend. It method can be block until new message will not get
diff --git a/ubenchmark/src/main/java/org/postgresql/benchmark/statement/BindArray.java b/ubenchmark/src/main/java/org/postgresql/benchmark/statement/BindArray.java
new file mode 100644
index 00000000..2882b2f1
--- /dev/null
+++ b/ubenchmark/src/main/java/org/postgresql/benchmark/statement/BindArray.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.benchmark.statement;
+
+import org.postgresql.benchmark.profilers.FlightRecorderProfiler;
+import org.postgresql.util.ConnectionUtil;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.profile.GCProfiler;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import java.sql.Array;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+@Fork(value = 1, jvmArgsPrepend = "-Xmx128m")
+@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@State(Scope.Thread)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+public class BindArray {
+  private Connection connection;
+  private PreparedStatement ps;
+
+  Integer[] ints;
+
+  @Param({"1", "5", "10", "50", "100", "1000"})
+  int arraySize;
+
+  @Setup(Level.Trial)
+  public void setUp() throws SQLException {
+    Properties props = ConnectionUtil.getProperties();
+
+    connection = DriverManager.getConnection(ConnectionUtil.getURL(), props);
+    ps = connection.prepareStatement("SELECT ?");
+    ints = new Integer[arraySize];
+    for (int i = 0; i < arraySize; i++) {
+      ints[i] = i + 1;
+    }
+  }
+
+  @TearDown(Level.Trial)
+  public void tearDown() throws SQLException {
+    ps.close();
+    connection.close();
+  }
+
+  @Benchmark
+  public Statement setObject() throws SQLException {
+    Array sqlInts = connection.createArrayOf("int", ints);
+    ps.setObject(1, sqlInts, Types.ARRAY);
+    return ps;
+  }
+
+  @Benchmark
+  public Statement setArray() throws SQLException {
+    Array sqlInts = connection.createArrayOf("int", ints);
+    ps.setArray(1, sqlInts);
+    return ps;
+  }
+
+  public static void main(String[] args) throws RunnerException {
+    Options opt = new OptionsBuilder()
+        .include(BindArray.class.getSimpleName())
+        .addProfiler(GCProfiler.class)
+        .addProfiler(FlightRecorderProfiler.class)
+        .detectJvmArgs()
+        .build();
+
+    new Runner(opt).run();
+  }
+}


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java
index 39649b43..98f9b78c 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java
@@ -10,9 +10,11 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import org.postgresql.PGResultSetMetaData;
+import org.postgresql.core.ServerVersion;
 import org.postgresql.jdbc.PreferQueryMode;
 import org.postgresql.test.TestUtil;
 
+import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
 
@@ -38,6 +40,11 @@ public class ResultSetMetaDataTest extends BaseTest4 {
 
     TestUtil.dropSequence(conn, "serialtest_a_seq");
     TestUtil.dropSequence(conn, "serialtest_b_seq");
+
+    if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v10)) {
+      TestUtil.createTable(conn, "identitytest", "id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY");
+    }
+
     TestUtil.createTable(conn, "serialtest", "a serial, b bigserial, c int");
     TestUtil.createTable(conn, "alltypes",
         "bool boolean, i2 int2, i4 int4, i8 int8, num numeric(10,2), re real, fl float, ch char(3), vc varchar(3), tx text, d date, t time without time zone, tz time with time zone, ts timestamp without time zone, tsz timestamp with time zone, bt bytea");
@@ -52,6 +59,9 @@ public class ResultSetMetaDataTest extends BaseTest4 {
     TestUtil.dropTable(conn, "rsmd1");
     TestUtil.dropTable(conn, "timetest");
     TestUtil.dropTable(conn, "serialtest");
+    if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v10)) {
+      TestUtil.dropTable(conn, "identitytest");
+    }
     TestUtil.dropTable(conn, "alltypes");
     TestUtil.dropTable(conn, "sizetest");
     TestUtil.dropSequence(conn, "serialtest_a_seq");
@@ -248,6 +258,16 @@ public class ResultSetMetaDataTest extends BaseTest4 {
     assertEquals("rsmd1", rsmd.getColumnTypeName(1));
   }
 
+  @Test
+  public void testIdentityColumn() throws Exception {
+    assumeMinimumServerVersion(ServerVersion.v10);
+    assumePreparedStatementMetadataSupported();
+    PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM identitytest");
+    ResultSet rs = pstmt.executeQuery();
+    ResultSetMetaData rsmd = pstmt.getMetaData();
+    Assert.assertTrue(rsmd.isAutoIncrement(1));
+  }
+
   private void assumePreparedStatementMetadataSupported() {
     Assume.assumeTrue("prepared statement metadata is not supported for simple protocol",
         preferQueryMode.compareTo(PreferQueryMode.EXTENDED_FOR_PREPARED) >= 0);
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java
index fe59de08..00c5d1a0 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java
@@ -17,6 +17,7 @@ import org.junit.runners.Suite;
 @RunWith(Suite.class)
 @Suite.SuiteClasses({SimpleDataSourceTest.class,
         SimpleDataSourceWithUrlTest.class,
+        SimpleDataSourceWithSetURLTest.class,
         ConnectionPoolTest.class,
         PoolingDataSourceTest.class,
         CaseOptimiserDataSourceTest.class})
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithSetURLTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithSetURLTest.java
new file mode 100644
index 00000000..f9bbd5a9
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithSetURLTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.jdbc2.optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.postgresql.Driver.parseURL;
+
+import org.postgresql.PGProperty;
+import org.postgresql.jdbc2.optional.SimpleDataSource;
+import org.postgresql.test.TestUtil;
+
+import org.junit.Test;
+
+import java.util.Properties;
+
+/**
+ * Performs the basic tests defined in the superclass. Just adds the configuration logic.
+ */
+public class SimpleDataSourceWithSetURLTest extends BaseDataSourceTest {
+  /**
+   * Creates and configures a new SimpleDataSource using setURL method.
+   */
+  @Override
+  protected void initializeDataSource() {
+    if (bds == null) {
+      bds = new SimpleDataSource();
+      bds.setURL(String.format("jdbc:postgresql://%s:%d/%s?prepareThreshold=%d&loggerLevel=%s", TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getPrepareThreshold(),
+              TestUtil.getLogLevel()));
+      bds.setUser(TestUtil.getUser());
+      bds.setPassword(TestUtil.getPassword());
+      bds.setProtocolVersion(TestUtil.getProtocolVersion());
+    }
+  }
+
+  @Test
+  public void testGetURL() throws Exception {
+    con = getDataSourceConnection();
+
+    String url = bds.getURL();
+    Properties properties = parseURL(url, null);
+
+    assertEquals(TestUtil.getServer(), properties.getProperty(PGProperty.PG_HOST.getName()));
+    assertEquals(Integer.toString(TestUtil.getPort()), properties.getProperty(PGProperty.PG_PORT.getName()));
+    assertEquals(TestUtil.getDatabase(), properties.getProperty(PGProperty.PG_DBNAME.getName()));
+    assertEquals(Integer.toString(TestUtil.getPrepareThreshold()), properties.getProperty(PGProperty.PREPARE_THRESHOLD.getName()));
+    assertEquals(TestUtil.getLogLevel(), properties.getProperty(PGProperty.LOGGER_LEVEL.getName()));
+  }
+
+  @Test
+  public void testSetURL() throws Exception {
+    initializeDataSource();
+
+    assertEquals(TestUtil.getServer(), bds.getServerName());
+    assertEquals(TestUtil.getPort(), bds.getPortNumber());
+    assertEquals(TestUtil.getDatabase(), bds.getDatabaseName());
+    assertEquals(TestUtil.getPrepareThreshold(), bds.getPrepareThreshold());
+    assertEquals(TestUtil.getLogLevel(), bds.getLoggerLevel());
+  }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java
index 734ace44..1a03543c 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java
@@ -5,8 +5,11 @@
 
 package org.postgresql.test.jdbc4;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import org.postgresql.core.ServerVersion;
@@ -20,6 +23,10 @@ import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 public class DatabaseMetaDataTest {
 
@@ -80,4 +87,149 @@ public class DatabaseMetaDataTest {
     assertNull(rs.getString("TABLE_CATALOG"));
     assertTrue(!rs.next());
   }
+
+  @Test
+  public void testGetFunctionsWithBlankPatterns() throws SQLException {
+    int minFuncCount = 1000;
+    DatabaseMetaData dbmd = _conn.getMetaData();
+    ResultSet rs = dbmd.getFunctions("", "", "");
+    int count = assertGetFunctionRS(rs);
+    assertThat(count > minFuncCount, is(true));
+
+    // Should be same as blank pattern
+    ResultSet rs2 = dbmd.getFunctions(null, null, null);
+    int count2 = assertGetFunctionRS(rs2);
+    assertThat(count2 > minFuncCount, is(true));
+    assertThat(count2, is(count));
+
+    // Catalog parameter has no affect on our getFunctions filtering
+    ResultSet rs3 = dbmd.getFunctions("ANYTHING_WILL_WORK", null, null);
+    int count3 = assertGetFunctionRS(rs3);
+    assertThat(count3 > minFuncCount, is(true));
+    assertThat(count3, is(count));
+
+    // Filter by schema
+    ResultSet rs4 = dbmd.getFunctions("", "pg_catalog", null);
+    int count4 = assertGetFunctionRS(rs4);
+    assertThat(count4 > minFuncCount, is(true));
+
+    // Filter by schema and function name
+    ResultSet rs5 = dbmd.getFunctions("", "pg_catalog", "abs");
+    int count5 = assertGetFunctionRS(rs5);
+    assertThat(count5 >= 1, is(true));
+
+    // Filter by function name only
+    rs5 = dbmd.getFunctions("", "", "abs");
+    count5 = assertGetFunctionRS(rs5);
+    assertThat(count5 >= 1, is(true));
+
+    rs.close();
+    rs2.close();
+    rs3.close();
+    rs4.close();
+    rs5.close();
+  }
+
+  /** Assert some basic result from ResultSet of a GetFunctions method. Return the total row count. */
+  private int assertGetFunctionRS(ResultSet rs) throws SQLException {
+    // There should be at least one row
+    assertThat(rs.next(), is(true));
+    assertThat(rs.getString("FUNCTION_CAT"), is(System.getProperty("database")));
+    assertThat(rs.getString("FUNCTION_SCHEM"), notNullValue());
+    assertThat(rs.getString("FUNCTION_NAME"), notNullValue());
+    assertThat(rs.getShort("FUNCTION_TYPE") >= 0, is(true));
+    assertThat(rs.getString("SPECIFIC_NAME"), notNullValue());
+
+    // Ensure there is enough column and column value retrieve by index should be same as column name (ordered)
+    assertThat(rs.getMetaData().getColumnCount(), is(6));
+    assertThat(rs.getString(1), is(rs.getString("FUNCTION_CAT")));
+    assertThat(rs.getString(2), is(rs.getString("FUNCTION_SCHEM")));
+    assertThat(rs.getString(3), is(rs.getString("FUNCTION_NAME")));
+    assertThat(rs.getString(4), is(rs.getString("REMARKS")));
+    assertThat(rs.getShort(5), is(rs.getShort("FUNCTION_TYPE")));
+    assertThat(rs.getString(6), is(rs.getString("SPECIFIC_NAME")));
+
+    // Get all result and assert they are ordered per javadoc spec:
+    //   FUNCTION_CAT, FUNCTION_SCHEM, FUNCTION_NAME and SPECIFIC_NAME
+    List<String> result = new ArrayList<String>();
+    do {
+      result.add(rs.getString("FUNCTION_CAT")
+              + " "
+              + rs.getString("FUNCTION_SCHEM")
+              + " "
+              + rs.getString("FUNCTION_NAME")
+              + " "
+              + rs.getString("SPECIFIC_NAME"));
+    } while (rs.next());
+
+    List<String> orderedResult = new ArrayList<String>(result);
+    Collections.sort(orderedResult);
+    assertThat(result, is(orderedResult));
+
+    return result.size();
+  }
+
+  @Test
+  public void testGetFunctionsWithSpecificTypes() throws SQLException {
+    // These function creation are borrow from jdbc2/DatabaseMetaDataTest
+    // We modify to ensure new function created are returned by getFunctions()
+
+    DatabaseMetaData dbmd = _conn.getMetaData();
+    if (TestUtil.haveMinimumServerVersion(_conn, ServerVersion.v8_4)) {
+      Statement stmt = _conn.createStatement();
+      stmt.execute(
+              "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL");
+      ResultSet rs = dbmd.getFunctions("", "", "getfunc_f1");
+      assertThat(rs.next(), is(true));
+      assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f1"));
+      assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionNoTable));
+      assertThat(rs.next(), is(false));
+      rs.close();
+      stmt.execute("DROP FUNCTION getfunc_f1(int, varchar)");
+
+      stmt.execute(
+              "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql");
+      rs = dbmd.getFunctions("", "", "getfunc_f3");
+      assertThat(rs.next(), is(true));
+      assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f3"));
+      assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionNoTable));
+      assertThat(rs.next(), is(false));
+      rs.close();
+      stmt.execute("DROP FUNCTION getfunc_f3(int, varchar)");
+
+      // RETURNS TABLE requires PostgreSQL 8.4+
+      stmt.execute(
+              "CREATE OR REPLACE FUNCTION getfunc_f5() RETURNS TABLE (i int) LANGUAGE sql AS 'SELECT 1'");
+
+      rs = dbmd.getFunctions("", "", "getfunc_f5");
+      assertThat(rs.next(), is(true));
+      assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f5"));
+      assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionReturnsTable));
+      assertThat(rs.next(), is(false));
+      rs.close();
+      stmt.execute("DROP FUNCTION getfunc_f5()");
+    } else {
+      // For PG 8.3 or 8.2 it will resulted in unknown function type
+      Statement stmt = _conn.createStatement();
+      stmt.execute(
+              "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL");
+      ResultSet rs = dbmd.getFunctions("", "", "getfunc_f1");
+      assertThat(rs.next(), is(true));
+      assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f1"));
+      assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionResultUnknown));
+      assertThat(rs.next(), is(false));
+      rs.close();
+      stmt.execute("DROP FUNCTION getfunc_f1(int, varchar)");
+
+      stmt.execute(
+              "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql");
+      rs = dbmd.getFunctions("", "", "getfunc_f3");
+      assertThat(rs.next(), is(true));
+      assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f3"));
+      assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionResultUnknown));
+      assertThat(rs.next(), is(false));
+      rs.close();
+      stmt.execute("DROP FUNCTION getfunc_f3(int, varchar)");
+    }
+  }
 }
diff --git a/pgjdbc/src/test/java/org/postgresql/test/util/ServerVersionTest.java b/pgjdbc/src/test/java/org/postgresql/test/util/ServerVersionTest.java
index 67435030..3e320890 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/util/ServerVersionTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/util/ServerVersionTest.java
@@ -19,6 +19,7 @@ public class ServerVersionTest {
         Assert.assertTrue(prev + " should be less than " + serverVersion,
             prev.getVersionNum() < serverVersion.getVersionNum());
       }
+      prev = serverVersion;
     }
   }
 }