PostgreSQL JDBC Null String taken as a bytea - string

If entity.getHistory() is null following code snippet:
(getEntityManager() returns spring injected EntityManager, database field history type is: text or varchar2(2000)
Query query = getEntityManager().createNativeQuery("insert into table_name(..., history, ....) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
[...]
.setParameter(6, entity.getHistory())
[...]
query.executeUpdate();
Gives strange exception:
17/11/11 06:26:09:009 [pool-2-thread-1] WARN util.JDBCExceptionReporter:100 - SQL Error: 0, SQLState: 42804
17/11/11 06:26:09:009 [pool-2-thread-1] ERROR util.JDBCExceptionReporter:101 - ERROR: **column "history" is of type text but expression is of type bytea**
Hint: You will need to rewrite or cast the expression.
Problem occurred only in this configuration:
OS: CentOS release 5.6 (Final)
Java: 1.6.0_26
DB: PostgreSQL 8.1
JDBC Driver: postgresql-9.1-901.jdbc4
Application Server: apache-tomcat-6.0.28
Everything is working fine on few other configurations or when history is empty string.
Same statement executed from pgAdmin works fine.
I guess problem is in PostgreSQL JDBC driver, is there some sensible reason to treat null string as a bytea value? Maybe some strange changes between Postgres 8 and 9?

There's a simple fix: when you're building the query string, if a parameter is going to be null -- use "null" instead of "?".
As #araqnid says, Hibernate is incorrectly casting null values into type bytea because it doesn't know any better.

Since you're calling setParameter(int,Object) with a null value, at a guess the entity manager has no idea which persistence type to use to bind the parameter. Istr Hibernate having a predilection for using SerializableType, which would equate to a bytea.
Can you use the setParameter method that takes a Parameter object instead, so you can specify the type? I haven't really used JPA, so can't give a detailed pointer.
(IMHO this is your just desserts for abusing EntityManager's native-sql query interface to do inserts, rather than actually mapping the entity, or just dropping through to JDBC)

Using Hibernate specific Session API works as a workaround:
String sql = "INSERT INTO person (id, name) VALUES (:id, :name)";
Session session = em.unwrap(Session.class);
SQLQuery insert = session.createSQLQuery(sql);
sql.setInteger("id", 123);
sql.setString("name", null);
insert.executeUpdate();
I've also filed HHH-9165 to report this issue, if it is indeed a bug.

Most of above answers are meaningful and helped me to narrow down issue.
I fixed issue for nulls like this:
setParameter(8, getDate(), TemporalType.DATE);

You can cast the parameter to a proper type in the query. I think this solution should work for other databases, too.
Before:
#Query("SELECT person FROM people person"
+ " WHERE (?1 IS NULL OR person.name = ?1)")
List<Person> getPeopleWithName(final String name);
After:
#Query("SELECT person FROM people person"
+ " WHERE (?1 IS NULL OR person.name = cast(?1 as string))")
List<Person> getPeopleWithName(final String name);
Also works for LIKE statements:
person.name LIKE concat('%', cast(?1 as string), '%')
and with numbers (here of type Double):
person.height >= cast(cast(?1 as string) as double)

If you are willing to use the PreparedStatement class instead of Query:
if (entity.getHistory() == null)
stmt.setNull(6, Types.VARCHAR);
else
stmt.setString(6, entity.getHistory());
(It is possible that using ?::text in your query string would also work, but I've never done it that way myself.)

The JDBC setNull with sql type parameter is fallback for legacy JDBC drivers that do not pass the JDBC Driver Test Suite.
One can guard against null errors within the query as follows:
SELECT a FROM Author a WHERE :lastName IS NULL OR LOWER(a.lastName) = :lastName
This should work in Postgresql after the fix of this issue.

Had this issue in my springboot application, with postgres and hibernate.
I needed to supply date from/to parameters, with possible nulls in every combination.
Excerpt of the query :
...
WHERE f.idf = :idCat
AND f.supplier = TRUE
AND COALESCE (pc.datefrom, CAST('2000-1-1' AS DATE) ) <= COALESCE (:datefrom, now())
AND COALESCE (pc.dateto, CAST('2200-12-31' AS DATE) ) >= COALESCE (:dateto, now())
Dao call :
#Transactional(readOnly = true)
public List<PriceListDto> ReportCatPriceList(Long idk, Date dateFrom, Date dateTo) {
Query query = EM.createNativeQuery(queryReport, PersonProductsByDay.class)
.setParameter("idCat", idk)
.setParameter("datefrom", dateFrom, TemporalType.DATE)
.setParameter("dateto", dateTo, TemporalType.DATE);
return query.getResultList();
}

I had the same problem executing an UPDATE with Oracle Database.
Sometimes a few fields that are updated are null and this issue occurs. Apparently JPA is casting my null value into bytea as already spoke in this topic.
I give up of using the Query object to update my table and used the EntityManager.merge(Entity) method.
I simply create the Entity object with all informations and when I execute the merge(Entity), the JPA executes a SELECT, compares the information already persisted on the Data Base with my Entity object and executes the UPDATE correctly. That is, JPA create a "field = NULL" sentence for the Entity variables that are null.

Related

What are the returned data types from a Knex select() statement?

Hi everyone,
I am currently using Knex.js for a project and a question arise when I make a knex('table').select() function call.
What are the returned types from the query ? In particular, If I have a datetime column in my table, what is the return value for this field ?
I believe the query will return a value of type string for this column. But it is the case for any database (I use SQLite3) ? It is possible that the query returns a Date value ?
EXAMPLE :
the user table has this schema :
knex.schema.createTable('user', function (table) {
table.increments('id');
table.string('username', 256).notNullable().unique();
table.timestamps(true, true);
})
since I use SQLite3, table.timestamps(true, true); produces 2 datetime columns : created_at & modified_at.
when I make a query knex('user').select(), it returns a array of objects with the attributes : id, username, created_at, modified_at.
id is of type number
username is of type string
what will be the types of created_at & modified_at ?
Will it be always of string type ? If I use an other database like PostgreSQL, these columns will have the timestamptz SQL type. The returned type of knex will be also a string type ?
This is not in fact something that Knex is responsible for, but rather the underlying database library. So if you're using SQLite, it would be sqlite3. If you're using Postgres, pg is responsible and you could find more documentation here. Broadly, most libraries take the approach that types which have a direct JavaScript equivalent (booleans, strings, null, integers, etc.) are returned as those types; anything else is converted to a string.
Knex's job is to construct the SQL that the other libraries use to talk to the database, and receives the response that they return.
as I believe it will be object of strings or numbers

Error binding OffsetDateTime [operator does not exist: timestamp with time zone <= character varying]

We are trying to execute dml which deletes records based on ZonedDateTime. We are using following code but running into an error.
dsl.execute ("delete from fieldhistory where createddate <= ? and object = ?", beforeDate.toOffsetDateTime(), objName)
Where beforeDate is ZonedDateTime and objectName is string
We are getting following error from postgres.
org.jooq.exception.DataAccessException: SQL [delete from fieldhistory where createddate <= ? and object = ?]; ERROR: operator does not exist: timestamp with time zone <= character varying
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
Position: 56
at org.jooq_3.13.1.POSTGRES.debug(Unknown Source)
at org.jooq.impl.Tools.translate(Tools.java:2751)
at org.jooq.impl.DefaultExecuteContext.sqlException(DefaultExecuteContext.java:755)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:385)
at org.jooq.impl.DefaultDSLContext.execute(DefaultDSLContext.java:1144)
Questions is, how do we bind datetime value in Jooq?
For historic reasons, jOOQ binds all JSR-310 times as strings, not as the relevant object type. This is because until recently, JDBC drivers did not support the JSR-310 types natively, and as such, using a string was not a bad default.
Unfortunately, this leads to type ambiguities, which you would not have if either:
jOOQ didn't bind a string
you were using the code generator and thus type safe DSL API methods
As a workaround, you can do a few things, including:
Casting your bind variable explicitly
dsl.execute("delete from fieldhistory where createddate <= ?::timestamptz and object = ?",
beforeDate.toOffsetDateTime(),
objName)
Using the DSL API
dsl.deleteFrom(FIELDHISTORY)
.where(FIELDHISTORY.CREATEDDATE.lt(beforeDate.toOffsetDateTime()))
.and(FIELDHISTORY.OBJECT.eq(objName))
.execute();
By writing your own binding
You can write your own data type binding and attach that to generated code, or to your plain SQL query, in case of which you would be in control of how the bind variable is sent to the JDBC driver. See:
https://www.jooq.org/doc/latest/manual/sql-building/queryparts/custom-bindings/
For example:
DataType<OffsetDateTime> myType = SQLDataType.OFFSETDATETIME
.asConvertedDataType(new MyBinding());
dsl.execute ("delete from fieldhistory where createddate <= {0} and object = {1}",
val(beforeDate.toOffsetDateTime(), myType),
val(objName))
There will be a fix in the future for this, so this won't be necessary anymore: https://github.com/jOOQ/jOOQ/issues/9902

jOOQ Query OrderBy as String

I'm getting the order by clause as a String from the application configuration.
Example
String orderByString = "NAME DESC, NUMBER ASC";
Now I want to use this order by in jOOQ query:
Result<KampagneRecord> records = repository.dsl()
.selectFrom(KAMPAGNE)
.orderBy(orderByString)
.fetch();
Unfortunately orderBy does not accept a String.
Is there a way to add the order by clause to the query?
You could use the fact that jOOQ does not validate your plain SQL templating, and just wrap your string in a DSL.field(String):
Result<KampagneRecord> records = repository.dsl()
.selectFrom(KAMPAGNE)
.orderBy(field(orderByString))
.fetch();
Of course, you will have to make sure that syntactical correctness is guaranteed, and SQL injection is prevented.
Some edge cases that rely on jOOQ being able to transform your SQL's ORDER BY clause might stop working, but in your simple query example, this would not apply.
An alternative solution, in very simple cases, is to preprocess your string. It seems as though this would work:
String orderByString = "NAME DESC, NUMBER ASC";
List<SortField<?>> list =
Stream.of(orderByString.split(","))
.map(String::trim)
.map(s -> s.split(" +"))
.map(s -> {
Field<?> field = field(s[0]);
return s.length == 1
? field.sortDefault()
: field.sort("DESC".equalsIgnoreCase(s[1])
? SortOrder.DESC
: SortOrder.ASC
);
})
.collect(Collectors.toList());
System.out.println(list);
This list can now be passed to the orderBy() clause.

Setting a NULL value in a BoundStatement

I'm using Cassandra Driver 2.0.0-beta2 with Cassandra 2.0.1.
I want to set a NULL value to a column of type 'int', in a BoundStatement. I don't think I can with setInt.
This is the code I'm using:
String insertStatementString = "insert into subscribers(subscriber,start_date,subscriber_id)";
PreparedStatement insertStatement = session.prepare(insertStatementString);
BoundStatement bs = new BoundStatement(insertStatement);
bs.setString("subscriber",s.getSubscriberName());
bs.setDate("start_date",startDate);
bs.setInt("subscriber_id",s.getSubscriberID());
The last line throws a null pointer exception, which can be explained because s.getSubscriberID() return an Integer and the BoundStatement accepts only ints, so when the id is null, it can't be converted, thus the exception.
The definition in my opinion should change to:
BoundStatement.setInt(String name, Integer v);
The way it is right now, I can't set NULL values for numbers.
Or am I missing something?
Is there other way to achieve this?
In cqlsh, setting null to a column of type 'int' is possible.
There is no need to bind values where the value will be empty or null. Therefore a null check might be useful, e.g.,
if(null != s.getSubscriberID()){
bs.setInt("subscriber_id",s.getSubscriberID());
}
As to the question of multiple instantiations of BoundStatement, the creation of multiple BoundStatement will be cheap in comparison with PreparedStatements (see the CQL documentation on prepared statements). Therefore the benefit is more clear when you begin to reuse the PreparedStatement, e.g., with a loop
String insertStatementString = "insert into subscribers(subscriber,start_date,subscriber_id)";
PreparedStatement insertStatement = session.prepare(insertStatementString);
// Inside a loop for example
for(Subscriber s: subscribersCollection){
BoundStatement bs = new BoundStatement(insertStatement);
bs.setString("subscriber",s.getSubscriberName());
bs.setDate("start_date",startDate);
if(null != s.getSubscriberID()){
bs.setInt("subscriber_id",s.getSubscriberID());
}
session.execute(bs);
}
I decided not to set the value at all. By default, it is null. It's a weird workaround.
But now I have to instantiate the BoundStatement before every call, because otherwise I risk having a value different than null from a previous call.
It would be great if they added a more comprehensive 'null' support.

how to insert timeuuid into cassandra using datastax java driver OR Invalid version for TimeUUID

I have one column in the cassandra keyspace that is of type timeuuid. When I try to insert a reocord from the java code (using DataStax java driver1.0.3). I get the following exception
com.datastax.driver.core.exceptions.InvalidQueryException: Invalid version for TimeUUID type.
at com.datastax.driver.core.exceptions.InvalidQueryException.copy(InvalidQueryException.java:35)
at com.datastax.driver.core.ResultSetFuture.extractCauseFromExecutionException(ResultSetFuture.java:269)
at com.datastax.driver.core.ResultSetFuture.getUninterruptibly(ResultSetFuture.java:183)
at com.datastax.driver.core.Session.execute(Session.java:111)
Here is my sample code:
PreparedStatement statement = session.prepare("INSERT INTO keyspace:table " +
"(id, subscriber_id, transaction_id) VALUES (now(), ?, ?);");
BoundStatement boundStatement = new BoundStatement(statement);
Session.execute(boundStatement.bind(UUID.fromString(requestData.getsubscriberId()),
requestData.getTxnId()));
I have also tried to use UUIDs.timeBased() instead of now(). But I am gettting the same exception.
Any help on how to insert/read from timeuuid datatype would be appreciated.
By mistake I had created
id uuid
That is why when I try to insert timeuuid in the uuid type field I was getting that exception.
Now I have changed the type for id to timeuuid and everything is working fine.

Resources