EclipseLink + PostgreSQL: UUID stopped to work after the upgrade to 4.0.0 - postgresql

I'm using the UUID as a primary key in my solution and it worked perfectly on the older EclipseLink version 2.7.3, however, when I tried to upgrade to 4.0.0 I started getting an error during the invocation of the find method.
PK is defined as:
#Id
#Column(name="ID", columnDefinition="UUID")
#Convert(converter = UuidConverter.class)
protected UUID id;
Converter:
#Converter(autoApply = true)
public class UuidConverter implements AttributeConverter<UUID, UUID> {
#Override
public UUID convertToDatabaseColumn(UUID attribute) {
return attribute;
}
#Override
public UUID convertToEntityAttribute(UUID dbData) {
return dbData;
}
}
PostgreSQL trick to bypass the cast issue (please see https://www.eclipse.org/forums/index.php/t/1073632/
for the details):
create or replace function uuid(_text character varying) returns uuid language sql as 'select uuid_in(_text::cstring)';
create cast (character varying as uuid) with function uuid(character varying) as assignment;
Find method:
public T find(Object id) {
return getEntityManager().find(entityClass, id);
}
Error:
Internal Exception: org.postgresql.util.PSQLException: ERROR: operator does not exist: uuid = character varying
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
Position: 96
So, the cast issue has returned despite that both the function and the cast were defined as listed above.
Entities are being stored in the DB fine, the problem appears only when I try to fetch them.

Related

Hibernate 6+ Unable to map java enum to postgres enum

After upgrading to hibernate 6 I am having issues with postgres being able to save enum types. I am no longer able to use #TypeDef annotation as it was removed.
#Enumerated(value = EnumType.STRING)
#Type(MyPSQLType.class)
private MyType myType;
The postgres type is defined as
public class MyPSQLType extends org.hibernate.type.EnumType<MyType> {
#Override
public void nullSafeSet(PreparedStatement st, MyType value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
st.setObject(index, value != null ? value.name() : null, Types.OTHER);
}
}
This is the error I am now getting. Do I have to now register the custom type in a different way now that TypeDef is no longer applicable?
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: my_type = character varying
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
Position: 187
I don't know how the PostgreSQL JDBC driver expects the values to be passed in, or how you did this before, but I guess you will have to use this instead:
if ( value == null ) {
st.setNull(index, Types.OTHER, "my_type");
}
else {
PGObject object = new PGObject();
object.setType("my_type");
object.setValue(value.name());
st.setObject(index, object, Types.OTHER);
}

JPA Repository findByEnum does not cast the Argument Enum to String or Postgres Cast not working as expected

I have a Spring Boot (2.5.4) backend pointing to a Postgres (9.6) database. I have an entity in Spring that makes use of the #Enumerated(EnumType.String) annotation on a field of an Enum type. Persisting this entity works as expected and converts the Enum into a String. In Postgres, I have the respective enum casted to character varying. Things are working to this point except invoking a custom findBy "Enum" method in the JPA Repository interface. Now in Spring and Postgres I have defined the following:
Enum:
public enum EnumExampleType {
TYPE1, TYPE2
}
Entity:
#Entity
#Table(name = "enumexampletable")
#Data
#NoArgsConstructor
public class EnumExampleTable {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "enum_example_table_id", columnDefinition="serial primary key")
private int enumExampleTableId;
#Column(unique = true, name="enum_example_type")
#Enumerated(EnumType.STRING)
public EnumExampleType enumExampleType;
}
Repo:
public interface EnumExampleTableRepo extends JpaRepository<EnumExampleTable, Integer> {
EnumExampleTable findByEnumExampleType(EnumExampleType enumExampleType);
}
Working Code as Expected
EnumExampleTable ex1 = new EnumExampleTable();
EnumExampleTable ex2 = new EnumExampleTable();
ex1.setEnumExampleType(EnumExampleType.TYPE1);
ex2.setEnumExampleType(EnumExampleType.TYPE2);
enumExampleTableRepo.save(ex1);
enumExampleTableRepo.save(ex2);
RestController: (to invoke) (not working)
#Autowired
EnumExampleTableRepo enumExampleTableRepo;
#GetMapping("/findByTest")
public EnumExampleTable enumTest() {
return enumExampleTableRepo.findByEnumExampleType(EnumExampleType.TYPE1);
}
When calling this code the following error is received:
Blockquote org.postgresql.util.PSQLException: ERROR: operator does not exist: enumexampletype = character varying
Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Postgres Database:
drop table if exists enumexampletable;
drop type if exists enumexampletype cascade;
drop cast if exists (character varying as enumexampletype);
create type enumexampletype as enum('TYPE1', 'TYPE2');
CREATE CAST (character varying as enumexampletype) with inout as implicit;
create table enumexampletable (
enum_example_table_id serial primary key,
enum_example_type enumexampletype
);
This suggests to me that either:
A: The findByEnumExampleType method does not convert the enum to a string
B: Postgres does not invoke this cast in this particular call
Also to Note: (A hard coded native query will function properly, but this is not the dynamic functionality I need)
#Query(value="select * from enumexampletable e where e.emum_example_type = 'TYPE1'", nativeQuery=true)
EnumExampleTable testNQ();
Thoughts or suggestions?

JPA StoredProcedureQuery: pass UUID as a parameter

I use JPA 2.1 (Hibernate), Postgres 9.6, and I need to pass java.util.UUID as a parameter to StoredProcedureQuery like this:
StoredProcedureQuery proc = em.createStoredProcedureQuery(myProc)
.registerStoredProcedureParameter(0, UUID.class, ParameterMode.IN)
.registerStoredProcedureParameter(1, ...)
.setParameter(0, myUuid)
.setParameter(1, ...);
By default, the Java type UUID is interpreted as Postgres type bytea and I get something like:
ERROR: function my_function(bytea, ...) does not exist.
Of course it does not exist, because my function is:
my_function(UUID, ...)
So, is there any way to define explicitly, which database-level type must be used for a particular parameter?
Might be something like the one we use in entity classes with the annotation:
#Type(type="pg-uuid")
private UUID uuid;
One obvious workaround is to pass the value as a String, and then cast it to UUID inside the function, but...
With EclipseLink 2.7.6 and Postgres 11.8 it works, I expect it should work with Hibernate too; originally I ended with the "bytea" too. First I needed this trivial converter, I have no idea why I have to convert UUID to UUID, but it works.
import java.util.UUID;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class UUIDConverter implements AttributeConverter<UUID, UUID> {
#Override
public UUID convertToDatabaseColumn(final UUID uuid) {
return uuid;
}
#Override
public UUID convertToEntityAttribute(final UUID uuid) {
return uuid;
}
}
The function api is this:
create or replace function generate_namespace(idSubscription uuid) returns integer as
...
Just a side note - I wasn't able to return the whole record as managed entity, because JPA will not receive all required metadata and does not know what (and if) is the primary key etc. So instead of returns Namespace I return only it's primary key and then I call entityManager.find to get the managed entity generated by the function:
final StoredProcedureQuery query = manager.createStoredProcedureQuery("generate_namespace");
query.registerStoredProcedureParameter(1, UUID.class, ParameterMode.IN);
query.setParameter(1, idSubscription);
final Object[] result = (Object[]) query.getSingleResult();
return manager.find(Namespace.class, result[0]);

#Enumerated Mapping with Postgresql Enum

I created a simple entity called Agent that have an enumerated category. I already know that JPA will not map this enum with Postgresql type enum so I tried to force this mapping.
What I Have:
Java Parts: in the java part we've defined the Person.java entity and the category enumerated class.
Person.java
#Entity
public class Agentimplements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(length = 50, nullable = false)
private String code;
#Column(length = 50, nullable = false)
private String first_name;
#Column(length = 50, nullable = false)
private String family_name;
#Enumerated(EnumType.STRING)
#Column(nullable = false)
private TypeEntree category;
}
CategoryEn.java
public enum CategoryEn{
CUSTOMER,
PROVIDER,
DRIVER
}
Sql Forcing:
CREATE TYPE category_enum AS ENUM ('CUSTOMER','PROVIDER','DRIVER');
CREATE FUNCTION dummy_cast(varchar) RETURNS category_enum AS $$
SELECT CASE $1
WHEN 'CUSTOMER' THEN 'CUSTOMER'::category_enum
WHEN 'PROVIDER' THEN 'PROVIDER'::category_enum
WHEN 'DRIVER' THEN 'DRIVER'::category_enum
END;
$$ LANGUAGE SQL;
CREATE CAST (varchar AS category_enum) WITH FUNCTION dummy_cast(varchar) AS ASSIGNMENT;
ALTER TABLE public.agent
ALTER COLUMN category
SET DATA TYPE category_enum
USING agent::text::category_enum;
Until here, everything is working fine, but when I try to execute this query in the the AgentFacade:
String jpql ="SELECT a FROM Agent a"
+ " WHERE a.category = :cat";
Query query = em.createQuery(jpql);
query.setParameter("cat", CategoryEn.DRIVER);
I'm having the following error:
Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-4002]
(Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd):
org.eclipse.persistence.exceptions.DatabaseException Internal
Exception: org.postgresql.util.PSQLException: ERREUR: operator does not exist : category_enum= character varying
Indication :No operator matches the given name and argument type(s). You might need to add explicit type casts
My questions are:
Why I am having this error ?
Can I solve this error ? How?
Why the JPA doesn't have a tool that map automatically a Java enum to an Sql type enum ?
PS: I've already seen almost all the stackoverflow questions/answers that are similare to this topic
You are getting this error because your driver/ORM is likely casting that parameter to varchar.
You could create operator for that comparison:
CREATE OR REPLACE FUNCTION texteq(
category_enum,
text)
RETURNS boolean AS $q$ SELECT texteq($1::text, $2) $q$
LANGUAGE SQL IMMUTABLE STRICT
COST 1;
CREATE OPERATOR =(
PROCEDURE = texteq,
LEFTARG = category_enum,
RIGHTARG = text,
COMMUTATOR = =,
NEGATOR = <>,
RESTRICT = eqsel,
JOIN = eqjoinsel,
HASHES,
MERGES);
I didn't test if it actually works in JOIN merges/hashes, but simple comparison looks fine.

Ormlite and PostgreSQL - Error inserting text array with custom persister

I have been working to setup Ormlite as the primary data access layer between a PostgreSQL database and Java application. Everything has been fairly straightforward, until I started messing with PostgreSQL's array types. In my case, I have two tables that make use of text[] array type. Following the documentation, I created a custom data persister as below:
public class StringArrayPersister extends StringType {
private static final StringArrayPersister singleTon = new StringArrayPersister();
private StringArrayPersister() {
super(SqlType.STRING, new Class<?>[]{String[].class});
}
public static StringArrayPersister getSingleton() {
return singleTon;
}
#Override
public Object javaToSqlArg(FieldType fieldType, Object javaObject) {
String[] array = (String[]) javaObject;
if (array == null) {
return null;
} else {
String join = "";
for (String str : array) {
join += str +",";
}
return "'{" + join.substring(0,join.length() - 1) + "}'";
}
}
#Override
public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) {
String string = (String) sqlArg;
if (string == null) {
return null;
} else {
return string.replaceAll("[{}]","").split(",");
}
}
}
And then in my business object implementation, I set up the persister class on the column likeso:
#DatabaseField(columnName = TAGS_FIELD, persisterClass = StringArrayPersister.class)
private String[] tags;
When ever I try inserting a new record with the Dao.create statement, I get an error message saying tags is of type text[], but got character varying... However, when querying existing records from the database, the business object (and text array) load just fine.
Any ideas?
UPDATE:
PostGresSQL 9.2. The exact error message:
Caused by: org.postgresql.util.PSQLException: ERROR: column "tags" is
of type text[] but expression is of type character varying Hint: You
will need to rewrite or cast the expression.
I've not used ormlite before (I generally use MyBatis), however, I believe the proximal issue is this code:
private StringArrayPersister() {
super(SqlType.STRING, new Class<?>[]{String[].class});
}
SqlType.String is mapped to varchar in SQL in the ormlite code, and so therefore I believe is the proximal cause of the error you're getting. See ormlite SQL Data Types info for more detail on that.
Try changing it to this:
private StringArrayPersister() {
super(SqlType.OTHER, new Class<?>[]{String[].class});
}
There may be other tweaks necessary as well to get it fully up and running, but that should get you passed this particular error with the varchar type mismatch.