I’m having difficulty using the CITEXT datatype in PostgreSQL using JPA and Hibernate. CITEXT is supposed to provide a case insensitive text datatype but when used with JPA/Hibernate it doesn’t behave in a case insensitive manner. Has anyone else had this issue or know a way around it? I have seen some mention (but very, very little) about a JDBC issue, but that went back at least a year and wasn’t very clear.
I have a ‘nickname’ column defined as citext in postgres 9.1. I just did a test to see if it could find a row using a named query as such:
create table test(
nickname citext
)
@NamedQuery(name = "Person.findByNickname",
query = "SELECT p
FROM Person p
WHERE p.nickname = :nickname")
Insert a nickname into the DB:
insert into test values('testNick')
Then run this code:
String nickname = "testNick";
Query q = em.createNamedQuery("Person.findByNickname");
q.setParameter("nickname", nickname);
if (q.getResultList().isEmpty()) {
return (false);
}
return (true);
This returns ‘true’ (i.e. there is already a ‘testNick’ in the database).
If I make this assignment
String nickname = "testnick"; //(lower case 'N')
and run it again it returns ‘false’.
Since the column is CITEXT, it should return ‘true’ again. i.e. case insensitive text.
Using JPA and Hibernate. Anyone have any thoughts?
In the meantime I’ve changed the column back to varchar and created a functional index for lowercase. And I have to create a native query now to search using database functions. Would like to find out if there is a way I can not have to do this to maintain the database abstraction.
Regards.
citextprovides case-insensitive operators for use within the database, with other citext values.What’s happening
At a guess, your JPA implementation is explicitly specifying the type of the parameter as
textwhen it creates the parameterized statement.citextdoesn’t define acitext = textoperator, so PostgreSQL casts thecitexttotextand uses the case-sensitivetext = textoperator. Effectively, comparingcitexttotextis case sensitive.Here’s what I think is happening. Given the dummy data:
… a comparison of citext to an unknown string literal will be interpreted as
citext=citextand be done case-insensitively:… but a comparison between
citextand an explicitlytexttyped literal will convert thecitextargument totextusingcitext‘s implicit cast to text, then do atext=textcase sensitive comparison:Or rather, what Hibernate is doing will be closer to:
where the type is specified as
textwhen binding a parameter, since Hibernate “knows” that Strings aretext.In other words, you need to get Hibernate to, via PgJDBC, explicitly specify the
citextdatatype as the parameter type to your query, resulting in something like:Note the explicit
citexttype parameter to the prepared statement. That will be … interesting … to do, especially since PgJDBC doesn’t know anything about thecitexttype. You’d have to write a custom data type handler for Hibernate that uses PgJDBC’ssetObject; even then you’ll have operator consistency issues between Java and Pg (see below).IMO you’ll be much better off using traditional case sensitive types and
lower(),ILIKE, etc.It’s also possible that Hibernate is relying on what PgJDBC tells it about column case sensitivity. At least as of 9.2-devel PgJDBC doesn’t know anything about the
citexttype, so it’ll always say “yup, that’s case sensitive” when asked.Tracing
It’s hard to be sure that’s what’s happening without seeing the actual queries run by JPA. Try setting
log_statement = 'all'inpostgresql.conf. ThenSIGHUPthe postmaster, usepg_ctl reload, or restart Pg to cause the change to take effect.Re-run your test and examine the logs. Test the queries you see in
psqlto observe the results. If you’re unsure what’s going on, update your question with them. If you update also include your Hibernate version and your PgJDBC version.It’s also possible that Hibernate is relying on what PgJDBC tells it about column case sensitivity. At least as of 9.2-devel PgJDBC doesn’t know anything about the
citexttype, so it’ll always say “yup, that’s case sensitive” when asked.Operator consistency difficulties
WARNING: The
citexttype cannot affect how Hibernate works with the text once it’s come out of the database. It won’t have any effect on theString.equalsmethod, for example. You would need to tell Hibernate you want it to treat the text as case insensitive. Otherwise if you have atextorvarcharprimary/foreign key you can get situations where the Hibernate asks for the key"FRED", it gets"FrEd"back, and is quite confused because the DB returned a key that isn’t equal – according to Hibernate – to the one it was asked for. Similar oddities will occur if you includecitext-backed strings inequalsandhashCodeimplementations in your entities.Unfortunately JPA doesn’t seem to specify annotation attributes in the
@Columnmapping for whether the column is case sensitive or not. Java doesn’t have the concept of a case-insensitive string data type anyway, so it wouldn’t do tons of good even if JPA did specify it.You’ll probably avoid confusing Hibernate too badly so long as you don’t use
citextfor keys or includecitextvalues inequalsandhashCode.