#Enumerated Mapping with Postgresql Enum - postgresql

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.

Related

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 eclipselink sum of integer in JPQL are not integers?

I'm using Eclipselink with postrgresql.
My entity is
public class PedaneMovimenti extends EntityBaseGest implements Serializable {
private static final long serialVersionUID = 1L;
...
#Column(name = "importo", nullable = false)
private Integer importo = 0;
...
In my JPQL Named query I sum the column importo, then use this value in a costructor of a class.
I have two constructor for the class used as projection:
public SaldoPedaneCliente(AnagraficaPGF anagrafica, TipoBancale tipo, Integer saldo);
public SaldoPedaneCliente(AnagraficaPGF anagrafica, TipoBancale tipo, Long saldo);
The JPQL query is
SELECT new com.path.SaldoPedaneCliente(
mov.mastro.anagrafica,
mov.tipobancale,
(
COALESCE(
SELECT SUM(m.importo)
FROM PedaneMovimenti m
WHERE m.mastro.anagrafica = mov.mastro.anagrafica AND m.tipobancale = mov.tipobancale
AND m.verso = com.bsssrl.bssstdgest.enums.VersoMovimento.IN
, 0)
))
from PedaneMovimenti mov WHERE mov.mastro.anagrafica IS NOT NULL
GROUP BY mov.mastro.anagrafica, mov.tipobancale
The query is ok, it works.
I've a type mismatch in the costructor:
javax.persistence.PersistenceException: java.lang.IllegalArgumentException: argument type mismatch
If I change the subquery with a constant (1 for example), it works fine, but if I use the sum, I've the exception.
So, does the sum on Integers is not an Integers or a Long?
EDIT: I've tryed also SELECT SUM(1) but I've the same error.
The sum of "Integer"s is a "Long" !
I've changed the order of the constructors: first the Long version, then the Integer version and it works.

Postgres insert record with Sequence generates error - org.postgresql.util.PSQLException: ERROR: relation "dual" does not exist

I am new to Postgres database.
I have a Java Entity class with the below column for ID:
#Entity
#Table(name = "THE_RULES")
public class TheRulesEntity {
/** The id. */
#Column(name = "TEST_NO", precision = 8)
#SequenceGenerator(name = "test_no_seq", sequenceName = "TEST_NO_SEQ")
#GeneratedValue(generator = "test_no_seq", strategy = GenerationType.AUTO)
#Id
private Long id;
/** The test val. */
#Column(name = "TEST_VAL", nullable = false, length = 3)
private String testVal;
Code:
rulesRepository.saveAndFlush(theRulesEntity)
Table:
CREATE TABLE THE_RULES
(
TEST_NO INT NOT NULL,
TEST_VAL VARCHAR(3) NOT NULL
)
CREATE SEQUENCE "TEST_NO_SEQ" START WITH 1000 INCREMENT BY 1;
When I try to insert a new record into the postgres database from my application (the ID value is null in Java code during Debug mode), then I get the below error:
Caused by: org.postgresql.util.PSQLException: ERROR: relation "dual" does not exist
But If I insert the record manually into database table and then update the record from my application, then the record is updated successfully (Probably because the application uses the same ID value so no need to refer to the Sequence TEST_NO_SEQ value anymore)
Looks like the database is not able to access the sequence from dual table.
Could anyone help me how to fix this?
Thanks.
Thanks to Joop and a_horse_with_no_name, the issue is resolved
I have used Oracle driver which is wrong. I have updated my code to use Postgres driver
I created the Sequence again in the database with same name but without the Quotes
I used all capital-case letters in my Java entity class to refer to the sequence correctly

Ebean Annotations - Using sequences to generate IDs in DB2

I'm trying to use sequences to generate incremented IDs for my tables in DB2. It works when I send SQL statements directly to the database, but when using ebean the statement fails. Here's the field in Java:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TABLENAME_IDNAME_TRIG")
#SequenceGenerator(name = "TABLENAME_IDNAME_TRIG", sequenceName = "TABLENAME_IDNAME_SEQ")
#Column(name = "IDNAME")
private Long id;
Here's the column in SQL (From TOAD):
Name Data type Not Null Default Generated Bit Data Scope Identity
IDNAME INTEGER Yes No No
And here's the sequence definition in SQL:
CREATE OR REPLACE SEQUENCE SCHEMA.TABLENAME_IDNAME_SEQ
AS INTEGER CACHE 50 ORDER;
And the trigger:
CREATE OR REPLACE TRIGGER SCHEMA.TABLENAME_IDNAME_TRIG
NO CASCADE BEFORE INSERT
ON TABLENAME
REFERENCING
NEW AS OBJ
FOR EACH ROW
BEGIN
SET obj.IDNAME=NEXT VALUE FOR SCHEMA.TABLENAME_IDNAME_SEQ;
END;
What is the issue with my annotations here? As a(n important) side note - when I set GenerationType to AUTO, TABLE, or IDENTITY, it works, even though it shouldn't, because I'm also using this object to represent a parallel oracle table which also uses sequences for ID generation.
Edited to include error message:
javax.persistence.PersistenceException: Error getting sequence nextval
...
Caused by: com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-348, SQLSTATE=428F9, SQLERRMC=NEXTVAL FOR SCHEMA.TABLENAME_IDNAME_SEQ, DRIVER=4.19.49
EDIT 2: The specific Sql statement that is failing is:
values nextval for QA_CONNECTION_ICONNECTIONI_SEQ union values nextval for QA_CONNECTION_ICONNECTIONI_SEQ union values nextval for QA_CONNECTION_ICONNECTIONI_SEQ
Which is SQL generated by Ebean. This is a smaller version of the real statement, which is repeated 20 times, so I'm guessing something screws up when generating the caching query.
EDIT 3: I believe this might be a bug in Ebean's use of DB2 sequences. This function generates SQl that returns an error for me when used with db2
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
super(be, ds, seqName, batchSize);
this.baseSql = "values nextval for " + seqName;
this.unionBaseSql = " union " + baseSql;
}
EDIT 4: Based on this SO link I think it is a bug.
Can't insert multiple values into DB2 by using UNION ALL and generate IDs from sequence
The correct class probably looks like this? Though I haven't ever tried building the library, so I couldn't test it. Time to learn how to open a defect I guess.
public class DB2SequenceIdGenerator extends SequenceIdGenerator {
private final String baseSql;
private final String unionBaseSql;
private final String startSql;
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
super(be, ds, seqName, batchSize);
this.startSql = "values "
this.baseSql = "(nextval for " + seqName);
this.unionBaseSql = ", " + baseSql;
}
public String getSql(int batchSize) {
StringBuilder sb = new StringBuilder();
sb.append(startSql);
sb.append(baseSql);
for (int i = 1; i < batchSize; i++) {
sb.append(unionBaseSql);
}
return sb.toString();
}
}
Temporary workaround for those interested: in ebean.properties, set
ebean.databaseSequenceBatchSize=1

Mapping Java enum on Postgres enum with EclipseLink

I am making first attempts with JPA (EclipseLink implementation) and feel quite stuck:
In PostgreSQL I have the following db schema
CREATE TYPE mood AS ENUM ( 'sad', 'happy', 'enthusiastic' );
CREATE TABLE person (
pk BIGINT PRIMARY KEY,
name VARCHAR NOT NULL,
mood mood NOT NULL
);
CREATE SEQUENCE person_pk_seq INCREMENT BY 100 MINVALUE 100;
Which works pretty fine, as this insert shows INSERT INTO PERSON (PK, mood, name) VALUES (3, 'happy', 'Joe') (Committing the pk as String makes no difference.)
On the JPA side I wrote the following class:
package testdb;
import java.io.Serializable;
import javax.persistence.*;
import org.eclipse.persistence.annotations.*;
#Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
public enum Mood {
sad, happy, enthusiastic;
}
#Id
#SequenceGenerator(
name="PERSON_PK_GENERATOR",
sequenceName="PERSON_PK_SEQ",
allocationSize = 100
)
#GeneratedValue(
strategy=GenerationType.SEQUENCE,
generator="PERSON_PK_GENERATOR"
)
public Long pk;
#Enumerated( EnumType.STRING )
#Column( name = "mood" )
#ObjectTypeConverter( name = "moodConverter", objectType = Mood.class,
dataType = String.class, conversionValues = {
#ConversionValue( objectValue = "sad", dataValue = "sad" ),
#ConversionValue( objectValue = "happy", dataValue = "happy" ),
#ConversionValue( objectValue = "enthusiastic", dataValue = "enthusiastic" )
})
#Convert( "moodConverter" )
public Mood mood;
#Column( name = "name" )
public String name;
public static void main(String[] args) {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("TestDb.jpa.tests" );
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Person p = new Person();
em.persist( p );
System.out.println(p.pk);
p.name = "Joe";
p.mood = Mood.enthusiastic;
em.getTransaction().commit();
Query q = em.createQuery( "select p from Person p" );
Person x = (Person)q.getResultList().get(0);
System.out.println( x.pk + " :: " +x.mood );
em.close();
}
}
However, this example is not working and I have no clue what the problem is:
[EL Warning]: 2012-06-05 15:28:20.646--UnitOfWork(845463623)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.BatchUpdateException: Batch-Eintrag 0 INSERT INTO PERSON (PK, mood, name) VALUES ('801', 'enthusiastic', 'Joe') wurde abgebrochen. Rufen Sie 'getNextException' auf, um die Ursache zu erfahren.
Error Code: 0
Call: INSERT INTO PERSON (PK, mood, name) VALUES (?, ?, ?)
bind => [3 parameters bound]
When I alter the column type of table person to varchar and remove the annotations #Convert and #ObjectTypeConverter everything is working as expected, as well.
Any ideas?
Why do you use a #ObjectTypeConverter, you can map Enumerations out of the box with eclipse link as shown here. #Enumerated is part of JSR-220 whereas #ObjectTypeConverter is a proprietary extension from EclipseLink JPA Extensions.
#Enumerated(EnumType.STRING)
#Column(name = "mood")
private Mood mood;
Try removing the #Enumerated( EnumType.STRING ) as it might be overriding the converter settings.
What is the mood type? This is not a standard JDBC type, so this is the reason for your error.
How does Postgres require this type to be bound through JDBC? It seems odd it does not auto convert varchar values.
I did a little looking, and it seems to return this type as PGObject, so you will need to own custom Converter that converts between your Java enum, and a Postgres enum. You will also need to set the jdbcType on the DatabaseField in your converters init method to OTHER.
Please log a bug on EclipseLink to have support added to the Postgres platform for this type.
I think disabling parameter binding would also work.
I'm 10 years late but...
Adding stringtype=unspecified to the connection string will do the trick.
I use connection strings like:
jdbc:postgresql://localhost:5432/mydatabase?stringtype=unspecified
And then, all fields annotated with #Enumerated(STRING) are automatically converted to the pg enum type.