Repository: pgjdbc/pgjdbc (java)

Issue:
N+1 SELECT caused by PgDatabaseMetaData#getTypeInfo()
This is to report a performance issue in PG installations with lots of types in pg_catalog.pg_type.

  <!--- What kind of an issue is this? Put an `x` in all the boxes that apply: -->
- [x] bug report
- [ ] feature request
    

**Describe the issue**
When calling `PgDatabaseMetaData#getTypeInfo()`, N+1 SELECTs are issued, as for each type entry `TypeInfoCache.getSQLType(String)` is called, which again issues a query againt pg_type.

**Java Version**
Any Java version.

**OS Version**
Any OS version.

**PostgreSQL Version**
Any PG version. Observed the issue with driver version 42.0.0, but from looking at the latest driver code, the issue still appears to be present with driver 42.2.5.

**To Reproduce**
Steps to reproduce the behaviour:

Have a pg_type table with lots of entries, e.g. 100K. Call `PgDatabaseMetaData#getTypeInfo()`. Observe how this will take several minutes. E.g. a Debezium user with nearly 200K entries reported 24 min on their system.

**Expected behaviour**

`PgDatabaseMetaData#getTypeInfo()` should return after a reasonable time also for databases with lots of custom types, e.g. in < 1 sec. This could be achieved by querying all type entries with a single query to populate the caches in `TypeInfoCache` at once. The following is what we use in Debezium for the time being instead of calling the driver API:

```
SELECT DISTINCT ON (typname) typname, typinput='array_in'::regproc, typtype, sp.r, pg_type.oid
  FROM pg_catalog.pg_type
  LEFT
  JOIN (select ns.oid as nspoid, ns.nspname, r.r
          from pg_namespace as ns
                // -- go with older way of unnesting array to be compatible with 8.0
          join ( select s.r, (current_schemas(false))[s.r] as nspname
                   from generate_series(1, array_upper(current_schemas(false), 1)) as s(r) ) as r
         using ( nspname )
       ) as sp
    ON sp.nspoid = typnamespace
 ORDER BY typname, sp.r, pg_type.oid;
```
**Logs**
n/a

Pull request description:
perf: ignore tables for PgDatabaseMetaData.getTypeInfo
postgres considers tables to be composite types which
in very large schemas with lots of tables slows down
getTypeInfo.
This fix filters out tables from types

fixes #1301 

### All Submissions:

* [ ] Have you followed the guidelines in our [Contributing](CONTRIBUTING.md) document?
* [ ] Have you checked to ensure there aren't other open [Pull Requests](../../pulls) for the same update/change?

<!-- You can erase any parts of this template not applicable to your Pull Request. -->

### New Feature Submissions:

1. [ ] Does your submission pass tests?
2. [ ] Does mvn checkstyle:check pass ?

### Changes to Existing Features:

* [ ] Does this break existing behaviour? If so please explain.
* [ ] Have you added an explanation of what your changes do and why you'd like us to include them?
* [ ] Have you written new tests for your core changes, as applicable?
* [ ] Have you successfully run tests with your changes locally?

Patch:
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/SimpleParameterList.java b/pgjdbc/src/main/java/org/postgresql/core/v3/SimpleParameterList.java
index 1a1ddeea..20283973 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/SimpleParameterList.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/SimpleParameterList.java
@@ -188,10 +188,16 @@ class SimpleParameterList implements V3ParameterList {
 
         case Oid.FLOAT4:
           float f = ByteConverter.float4((byte[]) paramValues[index], 0);
+          if (Float.isNaN(f)) {
+            return "'NaN'::real";
+          }
           return Float.toString(f);
 
         case Oid.FLOAT8:
           double d = ByteConverter.float8((byte[]) paramValues[index], 0);
+          if (Double.isNaN(d)) {
+            return "'NaN'::double precision";
+          }
           return Double.toString(d);
 
         case Oid.UUID:
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 268d936f..80c1349a 100644
--- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
+++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
@@ -1110,7 +1110,9 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     Properties p = org.postgresql.Driver.parseURL(url, null);
 
     for (PGProperty property : PGProperty.values()) {
-      setProperty(property, property.get(p));
+      if (!this.properties.containsKey(property.getName())) {
+        setProperty(property, property.get(p));
+      }
     }
   }
 
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
index 4abab216..f317b234 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
@@ -2224,7 +2224,9 @@ public class PgDatabaseMetaData implements DatabaseMetaData {
     String sql;
     sql = "SELECT t.typname,t.oid FROM pg_catalog.pg_type t"
           + " JOIN pg_catalog.pg_namespace n ON (t.typnamespace = n.oid) "
-          + " WHERE n.nspname  != 'pg_toast'";
+          + " WHERE n.nspname  != 'pg_toast'"
+          + " AND "
+          + " (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))";
 
     Statement stmt = connection.createStatement();
     ResultSet rs = stmt.executeQuery(sql);
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
index d4a6c907..fd9964f9 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
@@ -185,8 +185,8 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
         return getLong(columnIndex);
       case Types.NUMERIC:
       case Types.DECIMAL:
-        return getBigDecimal(columnIndex,
-            (field.getMod() == -1) ? -1 : ((field.getMod() - 4) & 0xffff));
+        return getNumeric(columnIndex,
+            (field.getMod() == -1) ? -1 : ((field.getMod() - 4) & 0xffff), true);
       case Types.REAL:
         return getFloat(columnIndex);
       case Types.FLOAT:
@@ -2325,6 +2325,10 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
 
   public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {
     connection.getLogger().log(Level.FINEST, "  getBigDecimal columnIndex: {0}", columnIndex);
+    return (BigDecimal) getNumeric(columnIndex, scale, false);
+  }
+
+  private Number getNumeric(int columnIndex, int scale, boolean allowNaN) throws SQLException {
     checkResultSet(columnIndex);
     if (wasNullFlag) {
       return null;
@@ -2352,11 +2356,15 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
         BigDecimal res = getFastBigDecimal(columnIndex);
         res = scaleBigDecimal(res, scale);
         return res;
-      } catch (NumberFormatException ex) {
+      } catch (NumberFormatException ignore) {
       }
     }
 
-    return toBigDecimal(getFixedString(columnIndex), scale);
+    String stringValue = getFixedString(columnIndex);
+    if (allowNaN && "NaN".equalsIgnoreCase(stringValue)) {
+      return Double.NaN;
+    }
+    return toBigDecimal(stringValue, scale);
   }
 
   /**
diff --git a/pgjdbc/src/main/java/org/postgresql/replication/fluent/AbstractCreateSlotBuilder.java b/pgjdbc/src/main/java/org/postgresql/replication/fluent/AbstractCreateSlotBuilder.java
index 54f946b4..c55f65b8 100644
--- a/pgjdbc/src/main/java/org/postgresql/replication/fluent/AbstractCreateSlotBuilder.java
+++ b/pgjdbc/src/main/java/org/postgresql/replication/fluent/AbstractCreateSlotBuilder.java
@@ -5,10 +5,22 @@
 
 package org.postgresql.replication.fluent;
 
+import org.postgresql.core.BaseConnection;
+import org.postgresql.core.ServerVersion;
+import org.postgresql.util.GT;
+
+import java.sql.SQLFeatureNotSupportedException;
+
 public abstract class AbstractCreateSlotBuilder<T extends ChainedCommonCreateSlotBuilder<T>>
     implements ChainedCommonCreateSlotBuilder<T> {
 
   protected String slotName;
+  protected boolean temporaryOption = false;
+  protected BaseConnection connection;
+
+  protected AbstractCreateSlotBuilder(BaseConnection connection) {
+    this.connection = connection;
+  }
 
   protected abstract T self();
 
@@ -17,4 +29,17 @@ public abstract class AbstractCreateSlotBuilder<T extends ChainedCommonCreateSlo
     this.slotName = slotName;
     return self();
   }
+
+  @Override
+  public T withTemporaryOption() throws SQLFeatureNotSupportedException {
+
+    if (!connection.haveMinimumServerVersion(ServerVersion.v10)) {
+      throw new SQLFeatureNotSupportedException(
+          GT.tr("Server does not support temporary replication slots")
+      );
+    }
+
+    this.temporaryOption = true;
+    return self();
+  }
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/replication/fluent/ChainedCommonCreateSlotBuilder.java b/pgjdbc/src/main/java/org/postgresql/replication/fluent/ChainedCommonCreateSlotBuilder.java
index 78a4565f..4076a4b7 100644
--- a/pgjdbc/src/main/java/org/postgresql/replication/fluent/ChainedCommonCreateSlotBuilder.java
+++ b/pgjdbc/src/main/java/org/postgresql/replication/fluent/ChainedCommonCreateSlotBuilder.java
@@ -6,6 +6,7 @@
 package org.postgresql.replication.fluent;
 
 import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
 
 /**
  * Fluent interface for specify common parameters for create Logical and Physical replication slot.
@@ -22,6 +23,17 @@ public interface ChainedCommonCreateSlotBuilder<T extends ChainedCommonCreateSlo
    */
   T withSlotName(String slotName);
 
+  /**
+   * <p>Temporary slots are not saved to disk and are automatically dropped on error or when
+   * the session has finished.</p>
+   *
+   * <p>This feature is only supported by PostgreSQL versions &gt;= 10.</p>
+   *
+   * @return T a slot builder
+   * @throws SQLFeatureNotSupportedException thrown if PostgreSQL version is less than 10.
+   */
+  T withTemporaryOption() throws SQLFeatureNotSupportedException;
+
   /**
    * Create slot with specified parameters in database.
    * @throws SQLException on error
diff --git a/pgjdbc/src/main/java/org/postgresql/replication/fluent/logical/LogicalCreateSlotBuilder.java b/pgjdbc/src/main/java/org/postgresql/replication/fluent/logical/LogicalCreateSlotBuilder.java
index 0369976c..72443c18 100644
--- a/pgjdbc/src/main/java/org/postgresql/replication/fluent/logical/LogicalCreateSlotBuilder.java
+++ b/pgjdbc/src/main/java/org/postgresql/replication/fluent/logical/LogicalCreateSlotBuilder.java
@@ -15,10 +15,9 @@ public class LogicalCreateSlotBuilder
     extends AbstractCreateSlotBuilder<ChainedLogicalCreateSlotBuilder>
     implements ChainedLogicalCreateSlotBuilder {
   private String outputPlugin;
-  private BaseConnection connection;
 
   public LogicalCreateSlotBuilder(BaseConnection connection) {
-    this.connection = connection;
+    super(connection);
   }
 
   @Override
@@ -45,7 +44,12 @@ public class LogicalCreateSlotBuilder
 
     Statement statement = connection.createStatement();
     try {
-      statement.execute(String.format("CREATE_REPLICATION_SLOT %s LOGICAL %s", slotName, outputPlugin));
+      statement.execute(String.format(
+              "CREATE_REPLICATION_SLOT %s %s LOGICAL %s",
+              slotName,
+              temporaryOption ? "TEMPORARY" : "",
+              outputPlugin
+      ));
     } finally {
       statement.close();
     }
diff --git a/pgjdbc/src/main/java/org/postgresql/replication/fluent/physical/PhysicalCreateSlotBuilder.java b/pgjdbc/src/main/java/org/postgresql/replication/fluent/physical/PhysicalCreateSlotBuilder.java
index 9d99fa4e..1ae0ff0f 100644
--- a/pgjdbc/src/main/java/org/postgresql/replication/fluent/physical/PhysicalCreateSlotBuilder.java
+++ b/pgjdbc/src/main/java/org/postgresql/replication/fluent/physical/PhysicalCreateSlotBuilder.java
@@ -14,10 +14,9 @@ import java.sql.Statement;
 public class PhysicalCreateSlotBuilder
     extends AbstractCreateSlotBuilder<ChainedPhysicalCreateSlotBuilder>
     implements ChainedPhysicalCreateSlotBuilder {
-  private BaseConnection connection;
 
   public PhysicalCreateSlotBuilder(BaseConnection connection) {
-    this.connection = connection;
+    super(connection);
   }
 
   @Override
@@ -33,7 +32,11 @@ public class PhysicalCreateSlotBuilder
 
     Statement statement = connection.createStatement();
     try {
-      statement.execute(String.format("CREATE_REPLICATION_SLOT %s PHYSICAL", slotName));
+      statement.execute(String.format(
+              "CREATE_REPLICATION_SLOT %s %s PHYSICAL",
+              slotName,
+              temporaryOption ? "TEMPORARY" : ""
+      ));
     } finally {
       statement.close();
     }


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/replication/ReplicationSlotTest.java b/pgjdbc/src/test/java/org/postgresql/replication/ReplicationSlotTest.java
index 2e9b1d73..961075d2 100644
--- a/pgjdbc/src/test/java/org/postgresql/replication/ReplicationSlotTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/replication/ReplicationSlotTest.java
@@ -7,9 +7,13 @@ package org.postgresql.replication;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import org.postgresql.PGConnection;
 import org.postgresql.PGProperty;
+import org.postgresql.core.BaseConnection;
+import org.postgresql.core.ServerVersion;
 import org.postgresql.test.TestUtil;
 import org.postgresql.test.util.rules.ServerVersionRule;
 import org.postgresql.test.util.rules.annotation.HaveMinimalServerVersion;
@@ -23,6 +27,7 @@ import org.junit.Test;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
 import java.sql.Statement;
 import java.util.Properties;
 
@@ -80,7 +85,67 @@ public class ReplicationSlotTest {
 
     boolean result = isPhysicalSlotExists(slotName);
 
-    assertThat(result, CoreMatchers.equalTo(true));
+    assertThat("Slot should exist", result, CoreMatchers.equalTo(true));
+
+    result = isSlotTemporary(slotName);
+
+    assertThat("Slot should not be temporary by default", result, CoreMatchers.equalTo(false));
+  }
+
+  @Test
+  public void testCreateTemporaryPhysicalSlotPg10AndHigher()
+      throws SQLException {
+    assumeTrue(TestUtil.haveMinimumServerVersion(replConnection, ServerVersion.v10));
+
+    BaseConnection baseConnection = (BaseConnection) replConnection;
+
+    String slotName = "pgjdbc_test_create_temporary_physical_replication_slot_pg_10_or_higher";
+
+    try {
+
+      baseConnection
+          .getReplicationAPI()
+          .createReplicationSlot()
+          .physical()
+          .withSlotName(slotName)
+          .withTemporaryOption()
+          .make();
+
+    } catch (SQLFeatureNotSupportedException e) {
+
+      fail("PostgreSQL >= 10 should support temporary replication slots");
+
+    }
+
+    boolean result = isSlotTemporary(slotName);
+
+    assertThat("Slot is not temporary", result, CoreMatchers.equalTo(true));
+  }
+
+  @Test
+  public void testCreateTemporaryPhysicalSlotPgLowerThan10()
+      throws SQLException {
+    assumeFalse(TestUtil.haveMinimumServerVersion(replConnection, ServerVersion.v10));
+
+    BaseConnection baseConnection = (BaseConnection) replConnection;
+
+    String slotName = "pgjdbc_test_create_temporary_physical_replication_slot_pg_lower_than_10";
+
+    try {
+
+      baseConnection
+          .getReplicationAPI()
+          .createReplicationSlot()
+          .physical()
+          .withSlotName(slotName)
+          .withTemporaryOption()
+          .make();
+
+      fail("PostgreSQL < 10 does not support temporary replication slots");
+
+    } catch (SQLFeatureNotSupportedException e) {
+      // success
+    }
   }
 
   @Test
@@ -151,7 +216,69 @@ public class ReplicationSlotTest {
 
     boolean result = isLogicalSlotExists(slotName);
 
-    assertThat(result, CoreMatchers.equalTo(true));
+    assertThat("Slot should exist", result, CoreMatchers.equalTo(true));
+
+    result = isSlotTemporary(slotName);
+
+    assertThat("Slot should not be temporary by default", result, CoreMatchers.equalTo(false));
+  }
+
+  @Test
+  public void testCreateTemporaryLogicalSlotPg10AndHigher()
+      throws SQLException {
+    assumeTrue(TestUtil.haveMinimumServerVersion(replConnection, ServerVersion.v10));
+
+    BaseConnection baseConnection = (BaseConnection) replConnection;
+
+    String slotName = "pgjdbc_test_create_temporary_logical_replication_slot_pg_10_or_higher";
+
+    try {
+
+      baseConnection
+          .getReplicationAPI()
+          .createReplicationSlot()
+          .logical()
+          .withSlotName(slotName)
+          .withOutputPlugin("test_decoding")
+          .withTemporaryOption()
+          .make();
+
+    } catch (SQLFeatureNotSupportedException e) {
+
+      fail("PostgreSQL >= 10 should support temporary replication slots");
+
+    }
+
+    boolean result = isSlotTemporary(slotName);
+
+    assertThat("Slot is not temporary", result, CoreMatchers.equalTo(true));
+  }
+
+  @Test
+  public void testCreateTemporaryLogicalSlotPgLowerThan10()
+      throws SQLException {
+    assumeFalse(TestUtil.haveMinimumServerVersion(replConnection, ServerVersion.v10));
+
+    BaseConnection baseConnection = (BaseConnection) replConnection;
+
+    String slotName = "pgjdbc_test_create_temporary_logical_replication_slot_pg_lower_than_10";
+
+    try {
+
+      baseConnection
+          .getReplicationAPI()
+          .createReplicationSlot()
+          .logical()
+          .withSlotName(slotName)
+          .withOutputPlugin("test_decoding")
+          .withTemporaryOption()
+          .make();
+
+      fail("PostgreSQL < 10 does not support temporary replication slots");
+
+    } catch (SQLFeatureNotSupportedException e) {
+      // success
+    }
   }
 
   @Test
@@ -205,6 +332,23 @@ public class ReplicationSlotTest {
     return result;
   }
 
+  private boolean isSlotTemporary(String slotName) throws SQLException {
+    if (!TestUtil.haveMinimumServerVersion(sqlConnection, ServerVersion.v10)) {
+      return false;
+    }
+
+    boolean result;
+
+    Statement st = sqlConnection.createStatement();
+    ResultSet resultSet = st.executeQuery(
+            "select 1 from pg_replication_slots where slot_name = '" + slotName
+                    + "' and temporary = true");
+    result = resultSet.next();
+    resultSet.close();
+    st.close();
+    return result;
+  }
+
   private void dropReplicationSlot() throws Exception {
     if (slotName != null) {
       TestUtil.dropReplicationSlot(sqlConnection, slotName);
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 f720e687..3558c57e 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java
@@ -9,12 +9,14 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import org.postgresql.Driver;
 import org.postgresql.PGProperty;
 import org.postgresql.ds.PGSimpleDataSource;
 import org.postgresql.ds.common.BaseDataSource;
+import org.postgresql.jdbc.AutoSave;
 import org.postgresql.test.TestUtil;
 import org.postgresql.util.URLCoder;
 
@@ -150,6 +152,18 @@ public class PGPropertyTest {
     }
   }
 
+  /**
+   * Test to make sure that setURL doesn't overwrite autosave
+   * more should be put in but this scratches the current itch
+   */
+  @Test
+  public void testOverWriteDSProperties() throws Exception {
+    PGSimpleDataSource dataSource = new PGSimpleDataSource();
+    dataSource.setAutosave(AutoSave.CONSERVATIVE);
+    dataSource.setURL("jdbc:postgresql://localhost:5432/postgres");
+    assertSame(dataSource.getAutosave(),AutoSave.CONSERVATIVE);
+  }
+
   /**
    * Test that {@link PGProperty#isPresent(Properties)} returns a correct result in all cases.
    */
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java
index 8c4d9901..ef851df8 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java
@@ -568,6 +568,62 @@ public class PreparedStatementTest extends BaseTest4 {
 
   }
 
+  @Test
+  public void testNaNLiteralsSimpleStatement() throws SQLException {
+    Statement stmt = con.createStatement();
+    ResultSet rs = stmt.executeQuery("select 'NaN'::numeric, 'NaN'::real, 'NaN'::double precision");
+    checkNaNLiterals(stmt, rs);
+  }
+
+  @Test
+  public void testNaNLiteralsPreparedStatement() throws SQLException {
+    PreparedStatement stmt = con.prepareStatement("select 'NaN'::numeric, 'NaN'::real, 'NaN'::double precision");
+    checkNaNLiterals(stmt, stmt.executeQuery());
+  }
+
+  private void checkNaNLiterals(Statement stmt, ResultSet rs) throws SQLException {
+    rs.next();
+    assertTrue("Double.isNaN((Double) rs.getObject", Double.isNaN((Double) rs.getObject(3)));
+    assertTrue("Double.isNaN(rs.getDouble", Double.isNaN(rs.getDouble(3)));
+    assertTrue("Float.isNaN((Float) rs.getObject", Float.isNaN((Float) rs.getObject(2)));
+    assertTrue("Float.isNaN(rs.getFloat", Float.isNaN(rs.getFloat(2)));
+    assertTrue("Double.isNaN((Double) rs.getObject", Double.isNaN((Double) rs.getObject(1)));
+    assertTrue("Double.isNaN(rs.getDouble", Double.isNaN(rs.getDouble(1)));
+    rs.close();
+    stmt.close();
+  }
+
+  @Test
+  public void testNaNSetDoubleFloat() throws SQLException {
+    PreparedStatement ps = con.prepareStatement("select ?, ?");
+    ps.setFloat(1, Float.NaN);
+    ps.setDouble(2, Double.NaN);
+
+    checkNaNParams(ps);
+  }
+
+  @Test
+  public void testNaNSetObject() throws SQLException {
+    PreparedStatement ps = con.prepareStatement("select ?, ?");
+    ps.setObject(1, Float.NaN);
+    ps.setObject(2, Double.NaN);
+
+    checkNaNParams(ps);
+  }
+
+  private void checkNaNParams(PreparedStatement ps) throws SQLException {
+    ResultSet rs = ps.executeQuery();
+    rs.next();
+
+    assertTrue("Float.isNaN((Float) rs.getObject", Float.isNaN((Float) rs.getObject(1)));
+    assertTrue("Float.isNaN(rs.getFloat", Float.isNaN(rs.getFloat(1)));
+    assertTrue("Double.isNaN(rs.getDouble", Double.isNaN(rs.getDouble(2)));
+    assertTrue("Double.isNaN(rs.getDouble", Double.isNaN(rs.getDouble(2)));
+
+    TestUtil.closeQuietly(rs);
+    TestUtil.closeQuietly(ps);
+  }
+
   @Test
   public void testBoolean() throws SQLException {
     testBoolean(0);