Repository: pgjdbc/pgjdbc
Pull request #1047 (2017-12-27 16:25:08)

refactor: simplify PgConnection.java
simplify constructor in PgConnection

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



```diff
@@             Coverage Diff              @@
##             master    #1047      +/-   ##
============================================
- Coverage     66.83%   66.82%   -0.01%     
  Complexity     3637     3637              
============================================
  Files           169      169              
  Lines         15593    15589       -4     
  Branches       2529     2528       -1     
============================================
- Hits          10422    10418       -4     
  Misses         3977     3977              
  Partials       1194     1194
```

davecramer: @AlexElin can you address @bokken issues please ?

Patch:
diff --git a/.gitattributes b/.gitattributes
index c9bd97fb..21bec18c 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -6,3 +6,4 @@
 *.xml text
 *.yaml text
 *.yml text
+/CHANGELOG.md merge=union
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b5e8de8..8c34e5e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Wrong results when single statement is used with different bind types[PR 1137](https://github.com/pgjdbc/pgjdbc/pull/1137)
 - Support generated keys for WITH queries that miss RETURNING [PR 1138](https://github.com/pgjdbc/pgjdbc/pull/1138)
 - Support generated keys when INSERT/UPDATE/DELETE keyword is followed by a comment [PR 1138](https://github.com/pgjdbc/pgjdbc/pull/1138)
+- Properly encode special symbols in passwords in BaseDataSource [PR 1201](https://github.com/pgjdbc/pgjdbc/pull/1201)
 
 ## [42.2.1] (2018-01-25)
 ### Known issues
diff --git a/pgjdbc/src/main/java/org/postgresql/Driver.java b/pgjdbc/src/main/java/org/postgresql/Driver.java
index 637af62c..0d222a09 100644
--- a/pgjdbc/src/main/java/org/postgresql/Driver.java
+++ b/pgjdbc/src/main/java/org/postgresql/Driver.java
@@ -6,7 +6,6 @@
 package org.postgresql;
 
 import org.postgresql.jdbc.PgConnection;
-
 import org.postgresql.util.DriverInfo;
 import org.postgresql.util.ExpressionProperties;
 import org.postgresql.util.GT;
@@ -14,12 +13,12 @@ import org.postgresql.util.HostSpec;
 import org.postgresql.util.PSQLException;
 import org.postgresql.util.PSQLState;
 import org.postgresql.util.SharedTimer;
+import org.postgresql.util.URLCoder;
 import org.postgresql.util.WriterHandler;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
-import java.net.URLDecoder;
 import java.security.AccessController;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
@@ -64,6 +63,8 @@ public class Driver implements java.sql.Driver {
   private static final Logger PARENT_LOGGER = Logger.getLogger("org.postgresql");
   private static final Logger LOGGER = Logger.getLogger("org.postgresql.Driver");
   private static SharedTimer sharedTimer = new SharedTimer();
+  private static final String DEFAULT_PORT =
+      /*$"\""+mvn.project.property.template.default.pg.port+"\";"$*//*-*/"5431";
 
   static {
     try {
@@ -561,7 +562,7 @@ public class Driver implements java.sql.Driver {
       if (slash == -1) {
         return null;
       }
-      urlProps.setProperty("PGDBNAME", URLDecoder.decode(l_urlServer.substring(slash + 1)));
+      urlProps.setProperty("PGDBNAME", URLCoder.decode(l_urlServer.substring(slash + 1)));
 
       String[] addresses = l_urlServer.substring(0, slash).split(",");
       StringBuilder hosts = new StringBuilder();
@@ -580,7 +581,7 @@ public class Driver implements java.sql.Driver {
           ports.append(portStr);
           hosts.append(address.subSequence(0, portIdx));
         } else {
-          ports.append("/*$mvn.project.property.template.default.pg.port$*/");
+          ports.append(DEFAULT_PORT);
           hosts.append(address);
         }
         ports.append(',');
@@ -596,13 +597,13 @@ public class Driver implements java.sql.Driver {
        then set it to default
       */
       if (defaults == null || !defaults.containsKey("PGPORT")) {
-        urlProps.setProperty("PGPORT", "/*$mvn.project.property.template.default.pg.port$*/");
+        urlProps.setProperty("PGPORT", DEFAULT_PORT);
       }
       if (defaults == null || !defaults.containsKey("PGHOST")) {
         urlProps.setProperty("PGHOST", "localhost");
       }
       if (defaults == null || !defaults.containsKey("PGDBNAME")) {
-        urlProps.setProperty("PGDBNAME", URLDecoder.decode(l_urlServer));
+        urlProps.setProperty("PGDBNAME", URLCoder.decode(l_urlServer));
       }
     }
 
@@ -616,7 +617,7 @@ public class Driver implements java.sql.Driver {
       if (l_pos == -1) {
         urlProps.setProperty(token, "");
       } else {
-        urlProps.setProperty(token.substring(0, l_pos), URLDecoder.decode(token.substring(l_pos + 1)));
+        urlProps.setProperty(token.substring(0, l_pos), URLCoder.decode(token.substring(l_pos + 1)));
       }
     }
 
diff --git a/pgjdbc/src/main/java/org/postgresql/copy/CopyManager.java b/pgjdbc/src/main/java/org/postgresql/copy/CopyManager.java
index 1ea38388..50dfaf69 100644
--- a/pgjdbc/src/main/java/org/postgresql/copy/CopyManager.java
+++ b/pgjdbc/src/main/java/org/postgresql/copy/CopyManager.java
@@ -3,10 +3,6 @@
  * See the LICENSE file in the project root for more information.
  */
 
-/**
- * Bulk data copy for PostgreSQL
- */
-
 package org.postgresql.copy;
 
 import org.postgresql.core.BaseConnection;
diff --git a/pgjdbc/src/main/java/org/postgresql/core/Parser.java b/pgjdbc/src/main/java/org/postgresql/core/Parser.java
index cc4a2393..de26262a 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/Parser.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/Parser.java
@@ -5,7 +5,7 @@
 
 package org.postgresql.core;
 
-import org.postgresql.jdbc.EscapedFunctions;
+import org.postgresql.jdbc.EscapedFunctions2;
 import org.postgresql.util.GT;
 import org.postgresql.util.PSQLException;
 import org.postgresql.util.PSQLState;
@@ -1179,25 +1179,7 @@ public class Parser {
 
         case ESC_FUNCTION:
           // extract function name
-          String functionName;
-          int posArgs;
-          for (posArgs = i; posArgs < len && p_sql[posArgs] != '('; posArgs++) {
-            ;
-          }
-          if (posArgs < len) {
-            functionName = new String(p_sql, i, posArgs - i).trim();
-            // extract arguments
-            i = posArgs + 1;// we start the scan after the first (
-            StringBuilder args = new StringBuilder();
-            i = parseSql(p_sql, i, args, false, stdStrings);
-            // translate the function and parse arguments
-            newsql.append(escapeFunction(functionName, args.toString(), stdStrings));
-          }
-          // go to the end of the function copying anything found
-          i++;
-          while (i < len && p_sql[i] != '}') {
-            newsql.append(p_sql[i++]);
-          }
+          i = escapeFunction(p_sql, i, newsql, stdStrings);
           state = SqlParseState.IN_SQLCODE; // end of escaped function (or query)
           break;
         case ESC_DATE:
@@ -1216,6 +1198,14 @@ public class Parser {
     return i;
   }
 
+  private static int findOpenBrace(char[] p_sql, int i) {
+    int posArgs;
+    for (posArgs = i; posArgs < p_sql.length && p_sql[posArgs] != '('; posArgs++) {
+      ;
+    }
+    return posArgs;
+  }
+
   private static void checkParsePosition(int i, int len, int i0, char[] p_sql,
       String message)
       throws PSQLException {
@@ -1227,54 +1217,71 @@ public class Parser {
         PSQLState.SYNTAX_ERROR);
   }
 
+  private static int escapeFunction(char[] p_sql, int i, StringBuilder newsql, boolean stdStrings) throws SQLException {
+    String functionName;
+    int argPos = findOpenBrace(p_sql, i);
+    if (argPos < p_sql.length) {
+      functionName = new String(p_sql, i, argPos - i).trim();
+      // extract arguments
+      i = argPos + 1;// we start the scan after the first (
+      i = escapeFunctionArguments(newsql, functionName, p_sql, i, stdStrings);
+    }
+    // go to the end of the function copying anything found
+    i++;
+    while (i < p_sql.length && p_sql[i] != '}') {
+      newsql.append(p_sql[i++]);
+    }
+    return i;
+  }
+
   /**
    * generate sql for escaped functions
    *
+   * @param newsql destination StringBuilder
    * @param functionName the escaped function name
-   * @param args the arguments for this function
+   * @param p_sql input SQL text (containing arguments of a function call with possible JDBC escapes)
+   * @param i position in the input SQL
    * @param stdStrings whether standard_conforming_strings is on
-   * @return the right postgreSql sql
+   * @return the right PostgreSQL sql
    * @throws SQLException if something goes wrong
    */
-  private static String escapeFunction(String functionName, String args, boolean stdStrings)
+  private static int escapeFunctionArguments(StringBuilder newsql, String functionName, char[] p_sql, int i,
+      boolean stdStrings)
       throws SQLException {
-    // parse function arguments
-    int len = args.length();
-    char[] argChars = args.toCharArray();
-    int i = 0;
-    ArrayList<StringBuilder> parsedArgs = new ArrayList<StringBuilder>();
-    while (i < len) {
+    // Maximum arity of functions in EscapedFunctions is 3
+    List<CharSequence> parsedArgs = new ArrayList<CharSequence>(3);
+    while (true) {
       StringBuilder arg = new StringBuilder();
       int lastPos = i;
-      i = parseSql(argChars, i, arg, true, stdStrings);
-      if (lastPos != i) {
+      i = parseSql(p_sql, i, arg, true, stdStrings);
+      if (i != lastPos) {
         parsedArgs.add(arg);
       }
+      if (i >= p_sql.length // should not happen
+          || p_sql[i] != ',') {
+        break;
+      }
       i++;
     }
-    // we can now translate escape functions
+    Method method = EscapedFunctions2.getFunction(functionName);
+    if (method == null) {
+      newsql.append(functionName);
+      EscapedFunctions2.appendCall(newsql, "(", ",", ")", parsedArgs);
+      return i;
+    }
     try {
-      Method escapeMethod = EscapedFunctions.getFunction(functionName);
-      return (String) escapeMethod.invoke(null, parsedArgs);
+      method.invoke(null, newsql, parsedArgs);
     } catch (InvocationTargetException e) {
-      if (e.getTargetException() instanceof SQLException) {
-        throw (SQLException) e.getTargetException();
+      Throwable targetException = e.getTargetException();
+      if (targetException instanceof SQLException) {
+        throw (SQLException) targetException;
       } else {
-        throw new PSQLException(e.getTargetException().getMessage(), PSQLState.SYSTEM_ERROR);
-      }
-    } catch (Exception e) {
-      // by default the function name is kept unchanged
-      StringBuilder buf = new StringBuilder();
-      buf.append(functionName).append('(');
-      for (int iArg = 0; iArg < parsedArgs.size(); iArg++) {
-        buf.append(parsedArgs.get(iArg));
-        if (iArg != (parsedArgs.size() - 1)) {
-          buf.append(',');
-        }
+        throw new PSQLException(targetException.getMessage(), PSQLState.SYSTEM_ERROR);
       }
-      buf.append(')');
-      return buf.toString();
+    } catch (IllegalAccessException e) {
+      throw new PSQLException(e.getMessage(), PSQLState.SYSTEM_ERROR);
     }
+    return i;
   }
 
   private static final char[] QUOTE_OR_ALPHABETIC_MARKER = {'\"', '0'};
@@ -1308,23 +1315,28 @@ public class Parser {
       this.replacementKeyword = replacementKeyword;
     }
 
-    private int getMatchedPosition(char[] p_sql, int pos) {
-      int newPos = pos;
-
+    private boolean startMatches(char[] p_sql, int pos) {
       // check for the keyword
       for (char c : escapeKeyword) {
-        if (newPos >= p_sql.length) {
-          return 0;
+        if (pos >= p_sql.length) {
+          return false;
         }
-        char curr = p_sql[newPos++];
+        char curr = p_sql[pos++];
         if (curr != c && curr != Character.toUpperCase(c)) {
-          return 0;
+          return false;
         }
       }
-      if (newPos >= p_sql.length) {
+      return pos < p_sql.length;
+    }
+
+    private int getMatchedPosition(char[] p_sql, int pos) {
+      // check for the keyword
+      if (!startMatches(p_sql, pos)) {
         return 0;
       }
 
+      int newPos = pos + escapeKeyword.length;
+
       // check for the beginning of the value
       char curr = p_sql[newPos];
       // ignore any in-between whitespace
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 e15e57e3..e438f171 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
@@ -1731,14 +1731,17 @@ public class QueryExecutorImpl extends QueryExecutorBase {
         : "Queries that might contain ; must be executed with QueryExecutor.QUERY_EXECUTE_AS_SIMPLE mode. "
         + "Given query is " + query.getNativeSql();
 
-    // As per "46.2. Message Flow" documentation (quote from 9.1):
-    // If successfully created, a named portal object lasts till the end of the current transaction, unless explicitly destroyed
-    //
-    // That is named portals do not require to use named statements.
+    // Per https://www.postgresql.org/docs/current/static/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
+    // A Bind message can use the unnamed prepared statement to create a named portal.
+    // If the Bind is successful, an Execute message can reference that named portal until either
+    //      the end of the current transaction
+    //   or the named portal is explicitly destroyed
 
     boolean noResults = (flags & QueryExecutor.QUERY_NO_RESULTS) != 0;
     boolean noMeta = (flags & QueryExecutor.QUERY_NO_METADATA) != 0;
     boolean describeOnly = (flags & QueryExecutor.QUERY_DESCRIBE_ONLY) != 0;
+    // extended queries always use a portal
+    // the usePortal flag controls whether or not we use a *named* portal
     boolean usePortal = (flags & QueryExecutor.QUERY_FORWARD_CURSOR) != 0 && !noResults && !noMeta
         && fetchSize > 0 && !describeOnly;
     boolean oneShot = (flags & QueryExecutor.QUERY_ONESHOT) != 0;
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 0d5261a3..0d6aff84 100644
--- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
+++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
@@ -12,6 +12,7 @@ import org.postgresql.util.ExpressionProperties;
 import org.postgresql.util.GT;
 import org.postgresql.util.PSQLException;
 import org.postgresql.util.PSQLState;
+import org.postgresql.util.URLCoder;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -25,7 +26,6 @@ import java.sql.SQLException;
 import java.util.Properties;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-
 import javax.naming.NamingException;
 import javax.naming.RefAddr;
 import javax.naming.Reference;
@@ -1069,17 +1069,17 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     if (portNumber != 0) {
       url.append(":").append(portNumber);
     }
-    url.append("/").append(databaseName);
+    url.append("/").append(URLCoder.encode(databaseName));
 
     StringBuilder query = new StringBuilder(100);
-    for (PGProperty property : PGProperty.values()) {
+    for (PGProperty property: PGProperty.values()) {
       if (property.isPresent(properties)) {
         if (query.length() != 0) {
           query.append("&");
         }
         query.append(property.getName());
         query.append("=");
-        query.append(property.get(properties));
+        query.append(URLCoder.encode(property.get(properties)));
       }
     }
 
@@ -1216,11 +1216,9 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
       portNumber = Integer.parseInt(port);
     }
     serverName = getReferenceProperty(ref, "serverName");
-    user = getReferenceProperty(ref, "user");
-    password = getReferenceProperty(ref, "password");
 
     for (PGProperty property : PGProperty.values()) {
-      property.set(properties, getReferenceProperty(ref, property.getName()));
+      setProperty(property, getReferenceProperty(ref, property.getName()));
     }
   }
 
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/EscapedFunctions.java b/pgjdbc/src/main/java/org/postgresql/jdbc/EscapedFunctions.java
index ca6c2fc7..76970f7e 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/EscapedFunctions.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/EscapedFunctions.java
@@ -20,7 +20,9 @@ import java.util.Map;
  * This class stores supported escaped function
  *
  * @author Xavier Poinsard
+ * @deprecated see {@link EscapedFunctions2}
  */
+@Deprecated
 public class EscapedFunctions {
   // numeric functions names
   public static final String ABS = "abs";
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/EscapedFunctions2.java b/pgjdbc/src/main/java/org/postgresql/jdbc/EscapedFunctions2.java
new file mode 100644
index 00000000..79373253
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/EscapedFunctions2.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.jdbc;
+
+import org.postgresql.util.GT;
+import org.postgresql.util.PSQLException;
+import org.postgresql.util.PSQLState;
+
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * This class stores supported escaped function.
+ * Note: this is a pgjdbc-internal class, so it is not supposed to be used outside of the driver.
+ */
+public final class EscapedFunctions2 {
+  // constants for timestampadd and timestampdiff
+  private static final String SQL_TSI_ROOT = "SQL_TSI_";
+  private static final String SQL_TSI_DAY = "SQL_TSI_DAY";
+  private static final String SQL_TSI_FRAC_SECOND = "SQL_TSI_FRAC_SECOND";
+  private static final String SQL_TSI_HOUR = "SQL_TSI_HOUR";
+  private static final String SQL_TSI_MINUTE = "SQL_TSI_MINUTE";
+  private static final String SQL_TSI_MONTH = "SQL_TSI_MONTH";
+  private static final String SQL_TSI_QUARTER = "SQL_TSI_QUARTER";
+  private static final String SQL_TSI_SECOND = "SQL_TSI_SECOND";
+  private static final String SQL_TSI_WEEK = "SQL_TSI_WEEK";
+  private static final String SQL_TSI_YEAR = "SQL_TSI_YEAR";
+
+  /**
+   * storage for functions implementations
+   */
+  private static final ConcurrentMap<String, Method> FUNCTION_MAP = createFunctionMap("sql");
+
+  private static ConcurrentMap<String, Method> createFunctionMap(String prefix) {
+    Method[] methods = EscapedFunctions2.class.getMethods();
+    ConcurrentMap<String, Method> functionMap = new ConcurrentHashMap<String, Method>(methods.length * 2);
+    for (Method method : methods) {
+      if (method.getName().startsWith(prefix)) {
+        functionMap.put(method.getName().substring(prefix.length()).toLowerCase(Locale.US), method);
+      }
+    }
+    return functionMap;
+  }
+
+  /**
+   * get Method object implementing the given function
+   *
+   * @param functionName name of the searched function
+   * @return a Method object or null if not found
+   */
+  public static Method getFunction(String functionName) {
+    Method method = FUNCTION_MAP.get(functionName);
+    if (method != null) {
+      return method;
+    }
+    String nameLower = functionName.toLowerCase(Locale.US);
+    if (nameLower == functionName) {
+      // Input name was in lower case, the function is not there
+      return null;
+    }
+    method = FUNCTION_MAP.get(nameLower);
+    if (method != null && FUNCTION_MAP.size() < 1000) {
+      // Avoid OutOfMemoryError in case input function names are randomized
+      // The number of methods is finite, however the number of upper-lower case combinations
+      // is quite a few (e.g. substr, Substr, sUbstr, SUbstr, etc).
+      FUNCTION_MAP.putIfAbsent(functionName, method);
+    }
+    return method;
+  }
+
+  // ** numeric functions translations **
+
+  /**
+   * ceiling to ceil translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlceiling(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "ceil(", "ceiling", parsedArgs);
+  }
+
+  /**
+   * log to ln translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqllog(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "ln(", "log", parsedArgs);
+  }
+
+  /**
+   * log10 to log translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqllog10(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "log(", "log10", parsedArgs);
+  }
+
+  /**
+   * power to pow translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlpower(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    twoArgumentsFunctionCall(buf, "pow(", "power", parsedArgs);
+  }
+
+  /**
+   * truncate to trunc translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqltruncate(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    twoArgumentsFunctionCall(buf, "trunc(", "truncate", parsedArgs);
+  }
+
+  // ** string functions translations **
+
+  /**
+   * char to chr translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlchar(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "chr(", "char", parsedArgs);
+  }
+
+  /**
+   * concat translation
+   *
+   * @param parsedArgs arguments
+   */
+  public static void sqlconcat(StringBuilder buf, List<? extends CharSequence> parsedArgs) {
+    appendCall(buf, "(", "||", ")", parsedArgs);
+  }
+
+  /**
+   * insert to overlay translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlinsert(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() != 4) {
+      throw new PSQLException(GT.tr("{0} function takes four and only four argument.", "insert"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    buf.append("overlay(");
+    buf.append(parsedArgs.get(0)).append(" placing ").append(parsedArgs.get(3));
+    buf.append(" from ").append(parsedArgs.get(1)).append(" for ").append(parsedArgs.get(2));
+    buf.append(')');
+  }
+
+  /**
+   * lcase to lower translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqllcase(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "lower(", "lcase", parsedArgs);
+  }
+
+  /**
+   * left to substring translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlleft(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() != 2) {
+      throw new PSQLException(GT.tr("{0} function takes two and only two arguments.", "left"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    appendCall(buf, "substring(", " for ", ")", parsedArgs);
+  }
+
+  /**
+   * length translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqllength(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() != 1) {
+      throw new PSQLException(GT.tr("{0} function takes one and only one argument.", "length"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    appendCall(buf, "length(trim(trailing from ", "", "))", parsedArgs);
+  }
+
+  /**
+   * locate translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqllocate(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() == 2) {
+      appendCall(buf, "position(", " in ", ")", parsedArgs);
+    } else if (parsedArgs.size() == 3) {
+      String tmp = "position(" + parsedArgs.get(0) + " in substring(" + parsedArgs.get(1) + " from "
+          + parsedArgs.get(2) + "))";
+      buf.append("(")
+          .append(parsedArgs.get(2))
+          .append("*sign(")
+          .append(tmp)
+          .append(")+")
+          .append(tmp)
+          .append(")");
+    } else {
+      throw new PSQLException(GT.tr("{0} function takes two or three arguments.", "locate"),
+          PSQLState.SYNTAX_ERROR);
+    }
+  }
+
+  /**
+   * ltrim translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlltrim(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "trim(leading from ", "ltrim", parsedArgs);
+  }
+
+  /**
+   * right to substring translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlright(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() != 2) {
+      throw new PSQLException(GT.tr("{0} function takes two and only two arguments.", "right"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    buf.append("substring(");
+    buf.append(parsedArgs.get(0))
+        .append(" from (length(")
+        .append(parsedArgs.get(0))
+        .append(")+1-")
+        .append(parsedArgs.get(1));
+    buf.append("))");
+  }
+
+  /**
+   * rtrim translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlrtrim(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "trim(trailing from ", "rtrim", parsedArgs);
+  }
+
+  /**
+   * space translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlspace(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "repeat(' ',", "space", parsedArgs);
+  }
+
+  /**
+   * substring to substr translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlsubstring(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    int argSize = parsedArgs.size();
+    if (argSize != 2 && argSize != 3) {
+      throw new PSQLException(GT.tr("{0} function takes two or three arguments.", "substring"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    appendCall(buf, "substr(", ",", ")", parsedArgs);
+  }
+
+  /**
+   * ucase to upper translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlucase(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "upper(", "ucase", parsedArgs);
+  }
+
+  /**
+   * curdate to current_date translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlcurdate(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    zeroArgumentFunctionCall(buf, "current_date", "curdate", parsedArgs);
+  }
+
+  /**
+   * curtime to current_time translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlcurtime(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    zeroArgumentFunctionCall(buf, "current_time", "curtime", parsedArgs);
+  }
+
+  /**
+   * dayname translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqldayname(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() != 1) {
+      throw new PSQLException(GT.tr("{0} function takes one and only one argument.", "dayname"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    appendCall(buf, "to_char(", ",", ",'Day')", parsedArgs);
+  }
+
+  /**
+   * dayofmonth translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqldayofmonth(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "extract(day from ", "dayofmonth", parsedArgs);
+  }
+
+  /**
+   * dayofweek translation adding 1 to postgresql function since we expect values from 1 to 7
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqldayofweek(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() != 1) {
+      throw new PSQLException(GT.tr("{0} function takes one and only one argument.", "dayofweek"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    appendCall(buf, "extract(dow from ", ",", ")+1", parsedArgs);
+  }
+
+  /**
+   * dayofyear translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqldayofyear(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "extract(doy from ", "dayofyear", parsedArgs);
+  }
+
+  /**
+   * hour translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlhour(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "extract(hour from ", "hour", parsedArgs);
+  }
+
+  /**
+   * minute translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlminute(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "extract(minute from ", "minute", parsedArgs);
+  }
+
+  /**
+   * month translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlmonth(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "extract(month from ", "month", parsedArgs);
+  }
+
+  /**
+   * monthname translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlmonthname(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() != 1) {
+      throw new PSQLException(GT.tr("{0} function takes one and only one argument.", "monthname"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    appendCall(buf, "to_char(", ",", ",'Month')", parsedArgs);
+  }
+
+  /**
+   * quarter translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlquarter(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "extract(quarter from ", "quarter", parsedArgs);
+  }
+
+  /**
+   * second translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlsecond(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "extract(second from ", "second", parsedArgs);
+  }
+
+  /**
+   * week translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlweek(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "extract(week from ", "week", parsedArgs);
+  }
+
+  /**
+   * year translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlyear(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    singleArgumentFunctionCall(buf, "extract(year from ", "year", parsedArgs);
+  }
+
+  /**
+   * time stamp add
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqltimestampadd(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() != 3) {
+      throw new PSQLException(
+          GT.tr("{0} function takes three and only three arguments.", "timestampadd"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    buf.append('(');
+    appendInterval(buf, parsedArgs.get(0).toString(), parsedArgs.get(1).toString());
+    buf.append('+').append(parsedArgs.get(2)).append(')');
+  }
+
+  private static void appendInterval(StringBuilder buf, String type, String value) throws SQLException {
+    if (!isTsi(type)) {
+      throw new PSQLException(GT.tr("Interval {0} not yet implemented", type),
+          PSQLState.SYNTAX_ERROR);
+    }
+    if (appendSingleIntervalCast(buf, SQL_TSI_DAY, type, value, "day")
+        || appendSingleIntervalCast(buf, SQL_TSI_SECOND, type, value, "second")
+        || appendSingleIntervalCast(buf, SQL_TSI_HOUR, type, value, "hour")
+        || appendSingleIntervalCast(buf, SQL_TSI_MINUTE, type, value, "minute")
+        || appendSingleIntervalCast(buf, SQL_TSI_MONTH, type, value, "month")
+        || appendSingleIntervalCast(buf, SQL_TSI_WEEK, type, value, "week")
+        || appendSingleIntervalCast(buf, SQL_TSI_YEAR, type, value, "year")
+    ) {
+      return;
+    }
+    if (areSameTsi(SQL_TSI_QUARTER, type)) {
+      buf.append("CAST((").append(value).append("::int * 3) || ' month' as interval)");
+      return;
+    }
+    throw new PSQLException(GT.tr("Interval {0} not yet implemented", type),
+        PSQLState.NOT_IMPLEMENTED);
+  }
+
+  private static boolean appendSingleIntervalCast(StringBuilder buf, String cmp, String type, String value, String pgType) {
+    if (!areSameTsi(type, cmp)) {
+      return false;
+    }
+    buf.ensureCapacity(buf.length() + 5 + 4 + 14 + value.length() + pgType.length());
+    buf.append("CAST(").append(value).append("||' ").append(pgType).append("' as interval)");
+    return true;
+  }
+
+  /**
+   * Compares two TSI intervals. It is
+   * @param a first interval to compare
+   * @param b second interval to compare
+   * @return true when both intervals are equal (case insensitive)
+   */
+  private static boolean areSameTsi(String a, String b) {
+    return a.length() == b.length() && b.length() > SQL_TSI_ROOT.length()
+        && a.regionMatches(true, SQL_TSI_ROOT.length(), b, SQL_TSI_ROOT.length(), SQL_TSI_ROOT.length() - b.length());
+  }
+
+  /**
+   * Checks if given input starts with {@link #SQL_TSI_ROOT}
+   * @param interval input string
+   * @return true if interval.startsWithIgnoreCase(SQL_TSI_ROOT)
+   */
+  private static boolean isTsi(String interval) {
+    return interval.regionMatches(true, 0, SQL_TSI_ROOT, 0, SQL_TSI_ROOT.length());
+  }
+
+
+  /**
+   * time stamp diff
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqltimestampdiff(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    if (parsedArgs.size() != 3) {
+      throw new PSQLException(
+          GT.tr("{0} function takes three and only three arguments.", "timestampdiff"),
+          PSQLState.SYNTAX_ERROR);
+    }
+    buf.append("extract( ")
+        .append(constantToDatePart(buf, parsedArgs.get(0).toString()))
+        .append(" from (")
+        .append(parsedArgs.get(2))
+        .append("-")
+        .append(parsedArgs.get(1))
+        .append("))");
+  }
+
+  private static String constantToDatePart(StringBuilder buf, String type) throws SQLException {
+    if (!isTsi(type)) {
+      throw new PSQLException(GT.tr("Interval {0} not yet implemented", type),
+          PSQLState.SYNTAX_ERROR);
+    }
+    if (areSameTsi(SQL_TSI_DAY, type)) {
+      return "day";
+    } else if (areSameTsi(SQL_TSI_SECOND, type)) {
+      return "second";
+    } else if (areSameTsi(SQL_TSI_HOUR, type)) {
+      return "hour";
+    } else if (areSameTsi(SQL_TSI_MINUTE, type)) {
+      return "minute";
+    } else {
+      throw new PSQLException(GT.tr("Interval {0} not yet implemented", type),
+          PSQLState.SYNTAX_ERROR);
+    }
+    // See http://archives.postgresql.org/pgsql-jdbc/2006-03/msg00096.php
+    /*
+     * else if (SQL_TSI_MONTH.equalsIgnoreCase(shortType)) return "month"; else if
+     * (SQL_TSI_QUARTER.equalsIgnoreCase(shortType)) return "quarter"; else if
+     * (SQL_TSI_WEEK.equalsIgnoreCase(shortType)) return "week"; else if
+     * (SQL_TSI_YEAR.equalsIgnoreCase(shortType)) return "year";
+     */
+  }
+
+  /**
+   * database translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqldatabase(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    zeroArgumentFunctionCall(buf, "current_database()", "database", parsedArgs);
+  }
+
+  /**
+   * ifnull translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqlifnull(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    twoArgumentsFunctionCall(buf, "coalesce(", "ifnull", parsedArgs);
+  }
+
+  /**
+   * user translation
+   *
+   * @param parsedArgs arguments
+   * @throws SQLException if something wrong happens
+   */
+  public static void sqluser(StringBuilder buf, List<? extends CharSequence> parsedArgs) throws SQLException {
+    zeroArgumentFunctionCall(buf, "user", "user", parsedArgs);
+  }
+
+  private static void zeroArgumentFunctionCall(StringBuilder buf, String call, String functionName,
+      List<? extends CharSequence> parsedArgs) throws PSQLException {
+    if (!parsedArgs.isEmpty()) {
+      throw new PSQLException(GT.tr("{0} function doesn''t take any argument.", functionName),
+          PSQLState.SYNTAX_ERROR);
+    }
+    buf.append(call);
+  }
+
+  private static void singleArgumentFunctionCall(StringBuilder buf, String call, String functionName,
+      List<? extends CharSequence> parsedArgs) throws PSQLException {
+    if (parsedArgs.size() != 1) {
+      throw new PSQLException(GT.tr("{0} function takes one and only one argument.", functionName),
+          PSQLState.SYNTAX_ERROR);
+    }
+    CharSequence arg0 = parsedArgs.get(0);
+    buf.ensureCapacity(buf.length() + call.length() + arg0.length() + 1);
+    buf.append(call).append(arg0).append(')');
+  }
+
+  private static void twoArgumentsFunctionCall(StringBuilder buf, String call, String functionName,
+      List<? extends CharSequence> parsedArgs) throws PSQLException {
+    if (parsedArgs.size() != 2) {
+      throw new PSQLException(GT.tr("{0} function takes two and only two arguments.", functionName),
+          PSQLState.SYNTAX_ERROR);
+    }
+    appendCall(buf, call, ",", ")", parsedArgs);
+  }
+
+  /**
+   * Appends {@code begin arg0 separator arg1 separator end} sequence to the input {@link StringBuilder}
+   * @param sb destination StringBuilder
+   * @param begin begin string
+   * @param separator separator string
+   * @param end end string
+   * @param args arguments
+   */
+  public static void appendCall(StringBuilder sb, String begin, String separator,
+      String end, List<? extends CharSequence> args) {
+    int size = begin.length();
+    // Typically just-in-time compiler would eliminate Iterator in case foreach is used,
+    // however the code below uses indexed iteration to keep the conde independent from
+    // various JIT implementations (== avoid Iterator allocations even for not-so-smart JITs)
+    // see https://bugs.openjdk.java.net/browse/JDK-8166840
+    // see http://2016.jpoint.ru/talks/cheremin/ (video and slides)
+    int numberOfArguments = args.size();
+    for (int i = 0; i < numberOfArguments; i++) {
+      size += args.get(i).length();
+    }
+    size += separator.length() * (numberOfArguments - 1);
+    sb.ensureCapacity(sb.length() + size + 1);
+    sb.append(begin);
+    for (int i = 0; i < numberOfArguments; i++) {
+      if (i > 0) {
+        sb.append(separator);
+      }
+      sb.append(args.get(i));
+    }
+    sb.append(end);
+  }
+}
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
index caf3be8b..8feb8e6f 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
@@ -204,43 +204,12 @@ public class PgConnection implements BaseConnection {
       setReadOnly(true);
     }
 
-    boolean binaryTransfer = PGProperty.BINARY_TRANSFER.getBoolean(info);
-    // Formats that currently have binary protocol support
-    Set<Integer> binaryOids = new HashSet<Integer>();
-    if (binaryTransfer && queryExecutor.getProtocolVersion() >= 3) {
-      binaryOids.add(Oid.BYTEA);
-      binaryOids.add(Oid.INT2);
-      binaryOids.add(Oid.INT4);
-      binaryOids.add(Oid.INT8);
-      binaryOids.add(Oid.FLOAT4);
-      binaryOids.add(Oid.FLOAT8);
-      binaryOids.add(Oid.TIME);
-      binaryOids.add(Oid.DATE);
-      binaryOids.add(Oid.TIMETZ);
-      binaryOids.add(Oid.TIMESTAMP);
-      binaryOids.add(Oid.TIMESTAMPTZ);
-      binaryOids.add(Oid.INT2_ARRAY);
-      binaryOids.add(Oid.INT4_ARRAY);
-      binaryOids.add(Oid.INT8_ARRAY);
-      binaryOids.add(Oid.FLOAT4_ARRAY);
-      binaryOids.add(Oid.FLOAT8_ARRAY);
-      binaryOids.add(Oid.FLOAT8_ARRAY);
-      binaryOids.add(Oid.VARCHAR_ARRAY);
-      binaryOids.add(Oid.TEXT_ARRAY);
-      binaryOids.add(Oid.POINT);
-      binaryOids.add(Oid.BOX);
-      binaryOids.add(Oid.UUID);
-    }
-
-    binaryOids.addAll(getOidSet(PGProperty.BINARY_TRANSFER_ENABLE.get(info)));
-    binaryOids.removeAll(getOidSet(PGProperty.BINARY_TRANSFER_DISABLE.get(info)));
+    Set<Integer> binaryOids = getBinaryOids(info);
 
     // split for receive and send for better control
-    Set<Integer> useBinarySendForOids = new HashSet<Integer>();
-    useBinarySendForOids.addAll(binaryOids);
+    Set<Integer> useBinarySendForOids = new HashSet<Integer>(binaryOids);
 
-    Set<Integer> useBinaryReceiveForOids = new HashSet<Integer>();
-    useBinaryReceiveForOids.addAll(binaryOids);
+    Set<Integer> useBinaryReceiveForOids = new HashSet<Integer>(binaryOids);
 
     /*
      * Does not pass unit tests because unit tests expect setDate to have millisecond accuracy
@@ -301,14 +270,9 @@ public class PgConnection implements BaseConnection {
     }
     this.disableColumnSanitiser = PGProperty.DISABLE_COLUMN_SANITISER.getBoolean(info);
 
-    TypeInfo types1 = getTypeInfo();
     if (haveMinimumServerVersion(ServerVersion.v8_3)) {
-      types1.addCoreType("uuid", Oid.UUID, Types.OTHER, "java.util.UUID", Oid.UUID_ARRAY);
-    }
-
-    TypeInfo types = getTypeInfo();
-    if (haveMinimumServerVersion(ServerVersion.v8_3)) {
-      types.addCoreType("xml", Oid.XML, Types.SQLXML, "java.sql.SQLXML", Oid.XML_ARRAY);
+      _typeCache.addCoreType("uuid", Oid.UUID, Types.OTHER, "java.util.UUID", Oid.UUID_ARRAY);
+      _typeCache.addCoreType("xml", Oid.XML, Types.SQLXML, "java.sql.SQLXML", Oid.XML_ARRAY);
     }
 
     this._clientInfo = new Properties();
@@ -328,7 +292,40 @@ public class PgConnection implements BaseConnection {
     replicationConnection = PGProperty.REPLICATION.get(info) != null;
   }
 
-  private Set<Integer> getOidSet(String oidList) throws PSQLException {
+  private static Set<Integer> getBinaryOids(Properties info) throws PSQLException {
+    boolean binaryTransfer = PGProperty.BINARY_TRANSFER.getBoolean(info);
+    // Formats that currently have binary protocol support
+    Set<Integer> binaryOids = new HashSet<Integer>(32);
+    if (binaryTransfer) {
+      binaryOids.add(Oid.BYTEA);
+      binaryOids.add(Oid.INT2);
+      binaryOids.add(Oid.INT4);
+      binaryOids.add(Oid.INT8);
+      binaryOids.add(Oid.FLOAT4);
+      binaryOids.add(Oid.FLOAT8);
+      binaryOids.add(Oid.TIME);
+      binaryOids.add(Oid.DATE);
+      binaryOids.add(Oid.TIMETZ);
+      binaryOids.add(Oid.TIMESTAMP);
+      binaryOids.add(Oid.TIMESTAMPTZ);
+      binaryOids.add(Oid.INT2_ARRAY);
+      binaryOids.add(Oid.INT4_ARRAY);
+      binaryOids.add(Oid.INT8_ARRAY);
+      binaryOids.add(Oid.FLOAT4_ARRAY);
+      binaryOids.add(Oid.FLOAT8_ARRAY);
+      binaryOids.add(Oid.VARCHAR_ARRAY);
+      binaryOids.add(Oid.TEXT_ARRAY);
+      binaryOids.add(Oid.POINT);
+      binaryOids.add(Oid.BOX);
+      binaryOids.add(Oid.UUID);
+    }
+
+    binaryOids.addAll(getOidSet(PGProperty.BINARY_TRANSFER_ENABLE.get(info)));
+    binaryOids.removeAll(getOidSet(PGProperty.BINARY_TRANSFER_DISABLE.get(info)));
+    return binaryOids;
+  }
+
+  private static Set<Integer> getOidSet(String oidList) throws PSQLException {
     Set<Integer> oids = new HashSet<Integer>();
     StringTokenizer tokenizer = new StringTokenizer(oidList, ",");
     while (tokenizer.hasMoreTokens()) {
@@ -363,22 +360,23 @@ public class PgConnection implements BaseConnection {
    */
   protected Map<String, Class<?>> typemap;
 
-  public java.sql.Statement createStatement() throws SQLException {
+  @Override
+  public Statement createStatement() throws SQLException {
     // We now follow the spec and default to TYPE_FORWARD_ONLY.
-    return createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
-        java.sql.ResultSet.CONCUR_READ_ONLY);
+    return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
   }
 
-  public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException {
-    return prepareStatement(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY,
-        java.sql.ResultSet.CONCUR_READ_ONLY);
+  @Override
+  public PreparedStatement prepareStatement(String sql) throws SQLException {
+    return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
   }
 
-  public java.sql.CallableStatement prepareCall(String sql) throws SQLException {
-    return prepareCall(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY,
-        java.sql.ResultSet.CONCUR_READ_ONLY);
+  @Override
+  public CallableStatement prepareCall(String sql) throws SQLException {
+    return prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
   }
 
+  @Override
   public Map<String, Class<?>> getTypeMap() throws SQLException {
     checkClosed();
     return typemap;
@@ -407,10 +405,12 @@ public class PgConnection implements BaseConnection {
 
   }
 
+  @Override
   public ResultSet execSQLQuery(String s) throws SQLException {
     return execSQLQuery(s, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
   }
 
+  @Override
   public ResultSet execSQLQuery(String s, int resultSetType, int resultSetConcurrency)
       throws SQLException {
     BaseStatement stat = (BaseStatement) createStatement(resultSetType, resultSetConcurrency);
@@ -434,6 +434,7 @@ public class PgConnection implements BaseConnection {
     return stat.getResultSet();
   }
 
+  @Override
   public void execSQLUpdate(String s) throws SQLException {
     BaseStatement stmt = (BaseStatement) createStatement();
     if (stmt.executeWithFlags(s, QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS
@@ -537,6 +538,7 @@ public class PgConnection implements BaseConnection {
    *
    * @exception SQLException if value is not correct for this type
    */
+  @Override
   public Object getObject(String type, String value, byte[] byteValue) throws SQLException {
     if (typemap != null) {
       Class<?> c = typemap.get(type);
@@ -654,6 +656,7 @@ public class PgConnection implements BaseConnection {
    *
    * {@inheritDoc}
    */
+  @Override
   public void close() throws SQLException {
     if (queryExecutor == null) {
       // This might happen in case constructor throws an exception (e.g. host being not available).
@@ -665,6 +668,7 @@ public class PgConnection implements BaseConnection {
     openStackTrace = null;
   }
 
+  @Override
   public String nativeSQL(String sql) throws SQLException {
     checkClosed();
     CachedQuery cachedQuery = queryExecutor.createQuery(sql, false, true);
@@ -672,6 +676,7 @@ public class PgConnection implements BaseConnection {
     return cachedQuery.query.getNativeSql();
   }
 
+  @Override
   public synchronized SQLWarning getWarnings() throws SQLException {
     checkClosed();
     SQLWarning newWarnings = queryExecutor.getWarnings(); // NB: also clears them.
@@ -684,6 +689,7 @@ public class PgConnection implements BaseConnection {
     return firstWarning;
   }
 
+  @Override
   public synchronized void clearWarnings() throws SQLException {
     checkClosed();
     queryExecutor.getWarnings(); // Clear and discard.
@@ -691,6 +697,7 @@ public class PgConnection implements BaseConnection {
   }
 
 
+  @Override
   public void setReadOnly(boolean readOnly) throws SQLException {
     checkClosed();
     if (queryExecutor.getTransactionState() != TransactionState.IDLE) {
@@ -709,11 +716,13 @@ public class PgConnection implements BaseConnection {
     LOGGER.log(Level.FINE, "  setReadOnly = {0}", readOnly);
   }
 
+  @Override
   public boolean isReadOnly() throws SQLException {
     checkClosed();
     return readOnly;
   }
 
+  @Override
   public void setAutoCommit(boolean autoCommit) throws SQLException {
     checkClosed();
 
@@ -729,6 +738,7 @@ public class PgConnection implements BaseConnection {
     LOGGER.log(Level.FINE, "  setAutoCommit = {0}", autoCommit);
   }
 
+  @Override
   public boolean getAutoCommit() throws SQLException {
     checkClosed();
     return this.autoCommit;
@@ -754,6 +764,7 @@ public class PgConnection implements BaseConnection {
     }
   }
 
+  @Override
   public void commit() throws SQLException {
     checkClosed();
 
@@ -775,6 +786,7 @@ public class PgConnection implements BaseConnection {
   }
 
 
+  @Override
   public void rollback() throws SQLException {
     checkClosed();
 
@@ -1467,11 +1479,13 @@ public class PgConnection implements BaseConnection {
     throw org.postgresql.Driver.notImplemented(this.getClass(), "createQueryObject(Class<T>)");
   }
 
+  @Override
   public boolean isWrapperFor(Class<?> iface) throws SQLException {
     checkClosed();
     return iface.isAssignableFrom(getClass());
   }
 
+  @Override
   public <T> T unwrap(Class<T> iface) throws SQLException {
     checkClosed();
     if (iface.isAssignableFrom(getClass())) {
@@ -1653,18 +1667,21 @@ public class PgConnection implements BaseConnection {
     pgSavepoint.invalidate();
   }
 
+  @Override
   public Statement createStatement(int resultSetType, int resultSetConcurrency)
       throws SQLException {
     checkClosed();
     return createStatement(resultSetType, resultSetConcurrency, getHoldability());
   }
 
+  @Override
   public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
       throws SQLException {
     checkClosed();
     return prepareStatement(sql, resultSetType, resultSetConcurrency, getHoldability());
   }
 
+  @Override
   public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
       throws SQLException {
     checkClosed();
@@ -1691,6 +1708,7 @@ public class PgConnection implements BaseConnection {
         PSQLState.NOT_IMPLEMENTED);
   }
 
+  @Override
   public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
     if (columnNames != null && columnNames.length == 0) {
       return prepareStatement(sql);
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
index 62261672..e8c0a45d 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
@@ -3027,7 +3027,7 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
    * @param bytes The bytes of the numeric field.
    * @param oid The oid of the field.
    * @param minVal the minimum value allowed.
-   * @param minVal the maximum value allowed.
+   * @param maxVal the maximum value allowed.
    * @param targetType The target type. Used for error reporting.
    * @return The value as long.
    * @throws PSQLException If the field type is not supported numeric type or if the value is out of
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java b/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
index 20492383..1d75784d 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
@@ -820,6 +820,8 @@ public class TimestampUtils {
   /**
    * 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)}
+   * @param localDateTime The local date to format as a String
+   * @return The formatted local date
    */
   public synchronized String toString(LocalDateTime localDateTime) {
     if (localDateTime.isAfter(MAX_LOCAL_DATETIME)) {
diff --git a/pgjdbc/src/main/java/org/postgresql/replication/PGReplicationConnection.java b/pgjdbc/src/main/java/org/postgresql/replication/PGReplicationConnection.java
index ab659880..9e1a04ea 100644
--- a/pgjdbc/src/main/java/org/postgresql/replication/PGReplicationConnection.java
+++ b/pgjdbc/src/main/java/org/postgresql/replication/PGReplicationConnection.java
@@ -39,6 +39,7 @@ public interface PGReplicationConnection {
 
   /**
    * @param slotName not null replication slot name exists in database that should be drop
+   * @throws SQLException if the replication slot cannot be dropped.
    */
   void dropReplicationSlot(String slotName) throws SQLException;
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/replication/fluent/ChainedCommonStreamBuilder.java b/pgjdbc/src/main/java/org/postgresql/replication/fluent/ChainedCommonStreamBuilder.java
index 0d6d8b75..bc03468e 100644
--- a/pgjdbc/src/main/java/org/postgresql/replication/fluent/ChainedCommonStreamBuilder.java
+++ b/pgjdbc/src/main/java/org/postgresql/replication/fluent/ChainedCommonStreamBuilder.java
@@ -20,6 +20,7 @@ public interface ChainedCommonStreamBuilder<T extends ChainedCommonStreamBuilder
    * rows which could cause a recovery conflict even when the standby is disconnected.
    *
    * @param slotName not null replication slot already exists on server.
+   * @return this instance as a fluent interface
    */
   T withSlotName(String slotName);
 
diff --git a/pgjdbc/src/main/java/org/postgresql/sspi/NTDSAPI.java b/pgjdbc/src/main/java/org/postgresql/sspi/NTDSAPI.java
index e83f8c93..3e4b0def 100644
--- a/pgjdbc/src/main/java/org/postgresql/sspi/NTDSAPI.java
+++ b/pgjdbc/src/main/java/org/postgresql/sspi/NTDSAPI.java
@@ -36,7 +36,8 @@ interface NTDSAPI extends StdCallLibrary {
    *        terminator (out)
    * @param spn SPN buffer (in/out)
    * @return Error code ERROR_SUCCESS, ERROR_BUFFER_OVERFLOW or ERROR_INVALID_PARAMETER
-   * @see "http://msdn.microsoft.com/en-us/library/ms676007(v=vs.85).aspx"
+   * @see <a href="https://msdn.microsoft.com/en-us/library/ms676007(v=vs.85).aspx">
+   *     https://msdn.microsoft.com/en-us/library/ms676007(v=vs.85).aspx</a>
    */
   int DsMakeSpnW(WString serviceClass, /* in */
       WString serviceName, /* in */
diff --git a/pgjdbc/src/main/java/org/postgresql/sspi/NTDSAPIWrapper.java b/pgjdbc/src/main/java/org/postgresql/sspi/NTDSAPIWrapper.java
index cd8acd4a..46cc8db7 100644
--- a/pgjdbc/src/main/java/org/postgresql/sspi/NTDSAPIWrapper.java
+++ b/pgjdbc/src/main/java/org/postgresql/sspi/NTDSAPIWrapper.java
@@ -24,8 +24,8 @@ public class NTDSAPIWrapper {
    * @param referrer See MSDN
    * @return SPN generated
    * @throws LastErrorException If buffer too small or parameter incorrect
-   * @see <a href="http://msdn.microsoft.com/en-us/library/ms676007(v=vs.85).aspx">http://msdn.
-   *      microsoft.com/en-us/library/ms676007</a>
+   * @see <a href="https://msdn.microsoft.com/en-us/library/ms676007(v=vs.85).aspx">
+   *     https://msdn.microsoft.com/en-us/library/ms676007(v=vs.85).aspx</a>
    */
   public String DsMakeSpn(String serviceClass, String serviceName, String instanceName,
       short instancePort, String referrer) throws LastErrorException {
diff --git a/pgjdbc/src/main/java/org/postgresql/util/Base64.java b/pgjdbc/src/main/java/org/postgresql/util/Base64.java
index 35fb7f5d..0caf0161 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/Base64.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/Base64.java
@@ -126,7 +126,7 @@ public class Base64 {
       (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/'
   };
 
-  /** Determine which ALPHABET to use. */
+  /* Determine which ALPHABET to use. */
   static {
     byte[] __bytes;
     try {
diff --git a/pgjdbc/src/main/java/org/postgresql/util/MD5Digest.java b/pgjdbc/src/main/java/org/postgresql/util/MD5Digest.java
index fec1fa73..2677db7b 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/MD5Digest.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/MD5Digest.java
@@ -5,15 +5,14 @@
 
 package org.postgresql.util;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
 /**
  * MD5-based utility function to obfuscate passwords before network transmission.
  *
  * @author Jeremy Wohl
  */
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
 public class MD5Digest {
   private MD5Digest() {
   }
diff --git a/pgjdbc/src/main/java/org/postgresql/util/URLCoder.java b/pgjdbc/src/main/java/org/postgresql/util/URLCoder.java
new file mode 100644
index 00000000..accbc233
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/util/URLCoder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+/**
+ * This class helps with URL encoding and decoding. UTF-8 encoding is used by default to make
+ * encoding consistent across the driver, and encoding might be changed via {@code
+ * postgresql.url.encoding} property
+ *
+ * Note: this should not be used outside of PostgreSQL source, this is not a public API of the
+ * driver.
+ */
+public final class URLCoder {
+  private static final String ENCODING_FOR_URL =
+      System.getProperty("postgresql.url.encoding", "UTF-8");
+
+  /**
+   * Decodes {@code x-www-form-urlencoded} string into Java string.
+   *
+   * @param encoded encoded value
+   * @return decoded value
+   * @see URLDecoder#decode(String, String)
+   */
+  public static String decode(String encoded) {
+    try {
+      return URLDecoder.decode(encoded, ENCODING_FOR_URL);
+    } catch (UnsupportedEncodingException e) {
+      throw new IllegalStateException(
+          "Unable to decode URL entry via " + ENCODING_FOR_URL + ". This should not happen", e);
+    }
+  }
+
+  /**
+   * Encodes Java string into {@code x-www-form-urlencoded} format
+   *
+   * @param plain input value
+   * @return encoded value
+   * @see URLEncoder#encode(String, String)
+   */
+  public static String encode(String plain) {
+    try {
+      return URLEncoder.encode(plain, "UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      throw new IllegalStateException(
+          "Unable to encode URL entry via " + ENCODING_FOR_URL + ". This should not happen", e);
+    }
+  }
+}
diff --git a/pgjdbc/src/main/java/org/postgresql/util/WriterHandler.java b/pgjdbc/src/main/java/org/postgresql/util/WriterHandler.java
index 822f9536..a86770fa 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/WriterHandler.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/WriterHandler.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
  * distributed with this work for additional information
@@ -17,7 +17,6 @@
  * under the License.
  */
 
-
 package org.postgresql.util;
 
 import java.io.Writer;
diff --git a/pgjdbc/src/main/java/org/postgresql/xa/PGXAConnection.java b/pgjdbc/src/main/java/org/postgresql/xa/PGXAConnection.java
index fdf123e2..06f59313 100644
--- a/pgjdbc/src/main/java/org/postgresql/xa/PGXAConnection.java
+++ b/pgjdbc/src/main/java/org/postgresql/xa/PGXAConnection.java
@@ -152,9 +152,6 @@ public class PGXAConnection extends PGPooledConnection implements XAConnection,
     }
   }
 
-
-  /**** XAResource interface ****/
-
   /**
    * Preconditions: 1. flags must be one of TMNOFLAGS, TMRESUME or TMJOIN 2. xid != null 3.
    * connection must not be associated with a transaction 4. the TM hasn't seen the xid before


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DriverTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DriverTest.java
index 317f4374..502cced1 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DriverTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DriverTest.java
@@ -16,6 +16,7 @@ import org.postgresql.Driver;
 import org.postgresql.PGProperty;
 import org.postgresql.test.TestUtil;
 import org.postgresql.util.NullOutputStream;
+import org.postgresql.util.URLCoder;
 import org.postgresql.util.WriterHandler;
 
 import org.junit.Test;
@@ -106,7 +107,9 @@ public class DriverTest {
 
     // Test with the username in the url
     con = DriverManager.getConnection(
-        TestUtil.getURL() + "&user=" + TestUtil.getUser() + "&password=" + TestUtil.getPassword());
+        TestUtil.getURL()
+            + "&user=" + URLCoder.encode(TestUtil.getUser())
+            + "&password=" + URLCoder.encode(TestUtil.getPassword()));
     assertNotNull(con);
     con.close();
 
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java
index 7d20eea4..7c5b5299 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java
@@ -16,6 +16,7 @@ import org.postgresql.PGProperty;
 import org.postgresql.ds.PGSimpleDataSource;
 import org.postgresql.ds.common.BaseDataSource;
 import org.postgresql.test.TestUtil;
+import org.postgresql.util.URLCoder;
 
 import org.junit.After;
 import org.junit.Before;
@@ -24,7 +25,6 @@ import org.junit.Test;
 import java.beans.BeanInfo;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
-import java.net.URLEncoder;
 import java.sql.DriverPropertyInfo;
 import java.util.ArrayList;
 import java.util.Map;
@@ -205,10 +205,10 @@ public class PGPropertyTest {
     String userName = "&u%ser";
     String password = "p%a&s^s#w!o@r*";
     String url = "jdbc:postgresql://"
-            + "localhost" + ":" + 5432 + "/"
-            + URLEncoder.encode(databaseName)
-            + "?user=" + URLEncoder.encode(userName)
-            + "&password=" + URLEncoder.encode(password);
+        + "localhost" + ":" + 5432 + "/"
+        + URLCoder.encode(databaseName)
+        + "?user=" + URLCoder.encode(userName)
+        + "&password=" + URLCoder.encode(password);
     Properties parsed = Driver.parseURL(url, new Properties());
     assertEquals("database", databaseName, PGProperty.PG_DBNAME.get(parsed));
     assertEquals("user", userName, PGProperty.USER.get(parsed));
@@ -257,4 +257,25 @@ public class PGPropertyTest {
       }
     }
   }
+
+  @Test
+  public void testEncodedUrlValuesFromDataSource() {
+    String databaseName = "d&a%ta+base";
+    String userName = "&u%ser";
+    String password = "p%a&s^s#w!o@r*";
+    String applicationName = "Laurel&Hardy=Best?Yes";
+    PGSimpleDataSource dataSource = new PGSimpleDataSource();
+
+    dataSource.setDatabaseName(databaseName);
+    dataSource.setUser(userName);
+    dataSource.setPassword(password);
+    dataSource.setApplicationName(applicationName);
+
+    Properties parsed = Driver.parseURL(dataSource.getURL(), new Properties());
+    assertEquals("database", databaseName, PGProperty.PG_DBNAME.get(parsed));
+    // datasources do not pass username and password as URL parameters
+    assertFalse("user", PGProperty.USER.isPresent(parsed));
+    assertFalse("password", PGProperty.PASSWORD.isPresent(parsed));
+    assertEquals("APPLICATION_NAME", applicationName, PGProperty.APPLICATION_NAME.get(parsed));
+  }
 }
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/StatementTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/StatementTest.java
index 70c8e06e..185da6b4 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/StatementTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/StatementTest.java
@@ -376,7 +376,7 @@ public class StatementTest {
     assertEquals(3, rs.getInt(1));
     // HOUR
     rs = stmt.executeQuery(
-        "select {fn timestampdiff(SQL_TSI_HOUR,{fn now()},{fn timestampadd(SQL_TSI_HOUR,3,{fn now()})})} ");
+        "select {fn timestampdiff(SQL_tsi_HOUR,{fn now()},{fn timestampadd(SQL_TSI_HOUR,3,{fn now()})})} ");
     assertTrue(rs.next());
     assertEquals(3, rs.getInt(1));
     // day
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java
index 36e970b0..f17be0ff 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java
@@ -5,6 +5,7 @@
 
 package org.postgresql.test.jdbc2.optional;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
@@ -197,6 +198,7 @@ public abstract class BaseDataSourceTest {
   public void testJndi() {
     initializeDataSource();
     BaseDataSource oldbds = bds;
+    String oldurl = bds.getURL();
     InitialContext ic = getInitialContext();
     try {
       ic.rebind(DATA_SOURCE_JNDI, bds);
@@ -207,9 +209,12 @@ public abstract class BaseDataSourceTest {
       fail(e.getMessage());
     }
     oldbds = bds;
+    String url = bds.getURL();
     testUseConnection();
     assertSame("Test should not have changed DataSource (" + bds + " != " + oldbds + ")!",
         oldbds , bds);
+    assertEquals("Test should not have changed DataSource URL",
+        oldurl, url);
   }
 
   /**