Search code examples
postgresqljpajdbcprepared-statement

Postgres bytea error when binding null to prepared statements


I am working with a Java application which uses JPA and a Postgres database, and I am trying to create a flexible prepared statement which can handle a variable number of input parameters. An example query would best explain this:

SELECT *
FROM my_table
WHERE
    (string_col = :param1 OR :param1 IS NULL) AND
    (double_col = :param2 OR :param2 IS NULL);

The idea behind this "trick" is that if a user specifies only one parameter, say :param1, we can just bind null to :param2, and the WHERE clause would then behave as if only the first parameter were even being checked. This approach lets us handle, in theory, any number of input parameters using a single prepared statement, instead of needing to maintain many different statements.

I have gotten a simple POC working locally using pure JDBC prepared statements. However, doing so required casting the parameter before comparing it to NULL, e.g.

WHERE (double_col = ? OR ?::numeric IS NULL)
                         ^^ does not work without cast

However, my actual application is using JPA, and I keep getting the following persistent error:

Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: double precision = bytea
  Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.

The problem does not occur with string/text columns, but only with columns which are double precision in my Postgres table. I have tried all combinations of casting, and nothing works:

(double_col = :param2 OR CAST(:param2 AS double precision) IS NULL);
(CAST(double_col AS double precision) = :param2 OR :param2 IS NULL);
(CAST(double_col AS double precision) = :param2 OR CAST(:param2 AS double precision) IS NULL);

The error seems to be saying that JDBC is sending Postgres a bytea type for the double columns, and then Postgres is rolling over because it can't find a way to cast byte to double precision.

The Java code looks something like:

Query query = entityManager.createNativeQuery(sqlString, MyEntity.class);
query.setParameter("param1", "some value");
// bind other parameters here
List<MyEntity> = query.getResultList();

For reference, here are the versions of everything I am using:

Hibernate version         | 4.3.7.Final
Spring data JPA vesion    | 1.7.1.RELEASE
Postgres driver version   | 42.2.2
Postgres database version | 9.6.10
Java version              | 1.8.0_171

Solution

  • Not having received any feedback in the form of answers or even a comment, I was getting ready to give up, when I stumbled onto this excellent blog post:

    How to bind custom Hibernate parameter types to JPA queries

    The post gives two options for controlling the types which JPA passes through the driver to Postgres (or whatever the underlying database actually is). I went with the approach using TypedParameterValue. Here is what my code looks like continuing with the example given above:

    Query query = entityManager.createNativeQuery(sqlString, MyEntity.class);
    query.setParameter("param1", new TypedParameterValue(StringType.INSTANCE, null));
    query.setParameter("param2", new TypedParameterValue(DoubleType.INSTANCE, null));
    List<MyEntity> = query.getResultList();
    

    Of course, it is trivial to be passing null for every parameter in the query, but I am doing this mainly to show the syntax for the text and double columns. In practice, we would expect at least a few of the parameters to be non null, but the above syntax handles all values, null or otherwise.