JPA/Hibernate custom query with list of Postgres enums - postgresql

I have an entity Company with type represented by enum CompanyType.
Database is Postgres and it is represented as enum type there.
I use JPA/Hibernate and its repositories.
Note that I am new to JPA, Hibernate and Kotlin.
I am trying to create custom #Query where I need to select companies where type (the enum) is in list of possible types. I am, however, encountering various error regarding type casting in SQL and/or syntax of the #Query.
Main part of the data class Company in Kotlin (did not copy any other attributes including id):
#Entity(name = "Company")
#Table(name = "company")
data class Company(
#Enumerated(EnumType.STRING)
#NotNull
#Column(name = "type", nullable = false, columnDefinition = "company_type")
val type: CompanyType = CompanyType.OTHER
) : Serializable
Enum CompanyType in Kotlin:
enum class CompanyType(value: Int) {
BUSINESS(1),
TOWN(2),
NONPROFIT(3),
RESEARCH(4),
OTHER(5)
}
Enum company_type in Postgres:
CREATE TYPE public.company_type AS ENUM (
'BUSINESS',
'TOWN',
'NONPROFIT',
'RESEARCH',
'OTHER'
);
CREATE CAST (character varying AS public.company_type) WITH INOUT AS ASSIGNMENT;
JPA Repository with my initial attempt:
#Repository
interface CompanyDAO : PagingAndSortingRepository<Company> {
#Query("SELECT c FROM #{#entityName} c " +
"WHERE c.type IN ?1"
)
fun findAllByTypeIn(types: List<CompanyType>, pageable: Pageable): Page<Company>
}
which compiles but once executed following error occurs:
ERROR: operator does not exist: company_type = character varying
So I tried to cast it, but do not know how exactly...
#Query("SELECT c FROM #{#entityName} c " +
"WHERE c.type IN cast(?1 AS company_type[])"
)
results in error in compile time:
antlr.MismatchedTokenException: expecting EOF, found ')'
and trying
#Query("SELECT c FROM #{#entityName} c " +
"WHERE c.type IN ?1\\:\\:company_type[]"
)
results in
org.hibernate.QueryException: unexpected char: '\'
How to create such query that takes list of enums and returns such entities that have the value equal to any of items in the list?

This error is coming from Postgres:
ERROR: operator does not exist: company_type = character varying
To fix this, you can create a custom operator for company_type in Postgres, and not change anything in your code. Like this:
CREATE FUNCTION ctype_compare(company_type, text)
RETURNS boolean
AS '
select cast($1 as text) = $2;
'
LANGUAGE sql IMMUTABLE;
CREATE OPERATOR = (
leftarg = company_type,
rightarg = text,
procedure = company_type_compare
);
With that, you can in fact remove the #Query and Hibernate will do the right thing. If you can't create custom operator, maybe because you don't have the right permissions, then you have to change your query to this:
#Query("SELECT c FROM #{#entityName} c " +
"WHERE cast (c.type as text) IN ?1")
And then you have to fix your argument type to String.
Page<Company> findAllByTypeIn(List<String> types, Pageable pageable);
And to call your DAO method, you pass the correct type:
List<String> types = new ArrayList();
types.add(CompanyType.OTHER.toString());
types.add(CompanyType.BUSINESS.toString());
Page<Company> companies = dao.findAllByTypeIn(types, Pageable.unpaged());
I did this in Java, not Kotlin. But it should work for you.

Would it help to create the cast as IMPLICIT instead of as ASSIGNMENT so that it is used in comparisons as well?
CREATE CAST (character varying as days) WITH INOUT AS IMPLICIT;

Related

Spring Data JPA with specification: postgres native function not found

I've been trying to dynamically use a PostgreSQL 13 native query:
public interface TasksRepository extends JpaRepository<Task, Long>, JpaSpecificationExecutor<Task> {
}
#AllArgsConstructor
public class TaskSpecification implements Specification<Task> {
private final String entityCode;
private final UUID entityId;
#Override
public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
// see https://www.postgresql.org/docs/13/functions-json.html
// jsonb_path_exists ( target jsonb, path jsonpath [, vars jsonb [, silent boolean ]] ) → boolean
String template = "$[*] ? (#.entityCode == $code && #.entityId == $id)";
String variable = "{\"code\":\"?1\", \"id\":\"?2\"}"
.replace("?1", this.entityCode)
.replace("?2", this.entityId.toString());
return builder.isTrue(
builder.function("jsonb_path_exists", Boolean.class,
/* target */ root.<List<RelatedEntity>>get("taskTags"),
/* path */ builder.literal("'" + template + "'::jsonpath"), //DEBUG CAST
/* vars */ builder.literal("'" + variable + "'::jsonb"), //DEBUG CAST
/* silent */ builder.literal(Boolean.FALSE)
));
}
}
But ended up with traumatic errors, despite my casting attempt:
Hibernate:
select
task0_.id as id1_0_,
task0_.business_unit as business2_0_,
task0_.due_date as due_date3_0_,
task0_.is_urgent as is_urgen4_0_,
task0_.task_tags as task_tag5_0_,
task0_.task_text as task_tex6_0_,
task0_.task_type as task_typ7_0_
from
tasks_table task0_
where
jsonb_path_exists(task0_.task_tags,?,?,?)=true
binding parameter [1] as [VARCHAR] - ['$[*] ? (#.entityCode == $code && #.entityId == $id)'::jsonpath]
binding parameter [2] as [VARCHAR] - ['{"code":"ETY", "id":"bedb1903-3827-4507-883b-d41888d2ed68"}'::jsonb]
binding parameter [3] as [BOOLEAN] - [false]
SQL Error: 0, SQLState: 42883
ERROR: function jsonb_path_exists(jsonb, character varying, character varying, boolean) does not exist
Indice : No function matches the given name and argument types. You might need to add explicit type casts.
I've tried to cast the above inner query parameters, but I suspect that this is a JPA-level issue; but I couldn't find the corresponding types to cast (jsonpath, jsonb) in my dependencies for them to applied with builder/Expression#as
Maybe the function is not visible (with schema issue or something alike?)
Thanks for any help
Try removing the cast ::jsonpath and give it a try.
A json path is not a data type, so no need for the cast. Instead this part root.<List<RelatedEntity>>get("taskTags") shoud be a valid json object, I am not sure how this is rendered in the query.
To verify what is rendered and the values binded to the query, enable logging for hibernate as such in application.properties;
logging.level.org.hibernate.SQL=trace
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace
This will show you the query and the value passed in the query.

Forced PostgreSQL type convertion using JOOQ tool

Is there a way to configure JOOQ tool to convert smallint to Boolean using 'forcedTypes' tag for PostgresSQL database, without providing org.jooq.Converter implementation?
This is how the current configuration looks like:
<forcedTypes>
<forcedType>
<name>BOOLEAN</name>
<types>smallint.*</types>
</forcedType>
<forcedTypes>
JOOQ v3.9.1 is being used.
PostgreSQL v9.6.6.
And unfortunately receives the next exception while storing information into the database:
Caused by: org.postgresql.util.PSQLException: ERROR: column "is_complete" is of type smallint but expression is of type boolean
Also tried with MySQL database and similar convertion from tinyint to Boolean works fine without any errors:
<forcedTypes>
<forcedType>
<name>BOOLEAN</name>
<types>tinyint.*</types>
</forcedType>
</forcedTypes>
No, this doesn't work as you're expecting (and it shouldn't). In jOOQ, the BOOLEAN data type is bound to JDBC as the native BOOLEAN type if the database supports it, e.g. PostgreSQL.
If the database doesn't support the type (e.g. MySQL / Oracle), then jOOQ will bind 0/1/NULL number values. But you cannot enforce this behaviour for a dialect that would otherwise support BOOLEAN types. But then again, why not just write that converter? It's really simple. Just add:
<forcedTypes>
<forcedType>
<userType>java.lang.Boolean</userType>
<converter>com.example.BooleanAsSmallintConverter</converter>
<!-- A bit risky. Are all smallints really booleans in your database? -->
<types>smallint.*</types>
</forcedType>
<forcedTypes>
And then:
class BooleanAsSmallintConverter extends AbstractConverter<Short, Boolean> {
public BooleanAsSmallintConverter() {
super(Short.class, Boolean.class);
}
#Override
public Boolean from(Short t) {
return t == null ? null : t.shortValue() != (short) 0;
}
#Override
public Short to(Boolean u) {
return u == null ? null : u ? Short.valueOf((short) 1) : Short.valueOf((short) 0);
}
}
You could do that, but I doubt that this is what you had in mind. You will have to create custom cast in PostgreSQL:
CREATE FUNCTION bool2int2(IN bool, OUT int2)
LANGUAGE SQL
AS $$
SELECT CASE WHEN $1 THEN 1::int2 WHEN $1 IS NOT NULL THEN 0::int2 END
$$;
DROP CAST IF EXISTS (bool AS int2);
CREATE CAST (bool AS int2)
WITH FUNCTION bool2int2(bool)
AS ASSIGNMENT;
Then this will work:
DROP TABLE IF EXISTS booltest;
CREATE TABLE booltest (id serial, boolval int2);
INSERT INTO booltest (boolval) VALUES(true),(false),(null::bool);
SELECT * FROM booltest;
id | boolval
----+---------
1 | 1
2 | 0
3 | (null);

Conditionally add query operator on properties defined in non-EDM base type, if inheriting

(C# code at end of question)
I have the following inheritance chain:
PreRecord <- Record <- (multiple entity types)
Record declares a property ID As Integer.
PreRecord and Record are not EDM types, and do not correspond to tables in the database.
I have a method that takes a generic parameter constrained to PreRecord and builds an EF query with the generic parameter as the element type. At runtime, in the event that T inherits not just from PreRecord but from Record, I would like add an OrderBy operator on ID:
'Sample 1
Function GetQuery(Of T As PreRecord)(row As T) As IQueryable(Of T)
Dim dcx = New MyDbContext
Dim qry = dcx.Set(Of T).AsQueryable
If TypeOf row Is RecordBase Then
'modify/rewrite the query here
End If
Return qry
End Function
If the parameter constraint were to Record I would have no problem applying query operators that use the ID property. How can I make use of a different (narrowing) generic constraint mid-method and still return an IQueryable(Of T) / IQueryable<T>, where T is still constrained to PreRecord?
I tried this:
'Sample 2
qry = dcx.Set(Of T).Cast(Of Record).OrderBy(Function(x) x.ID).Cast(Of PreRecord)()
which doesn't work:
LINQ to Entities only supports casting EDM primitive or enumeration types.
C# equivalent:
//Sample 1
public IQueryable<T> GetQuery<T>(T row) where T : PreRecord {
var dcx = new MyDbContext();
var qry = dcx.Set<T>.AsQueryable();
if (row is RecordBase) {
//modify/rewrite the query here
}
return qry;
}
and this doesn't work:
//Sample 2
qry = dcx.Set<T>.Cast<Record>.OrderBy(x => x.ID).Cast<PreRecord>()
The problem here is the fact that compiler checks queries already at compile time and PreRecord class does not have ID property. We cannot use simply Cast, because when it is used in definition of the query parser tries to convert it to sql - but there is no such thing that exists in sql. Sql supports only conversion of one column type to another - so on the .NET side it is supported only for primitive and enum types. To overcome compiler query checking we may use Expression class to build dynamic queries:
ParameterExpression e = Expression.Parameter(typeof(Record));
Expression body = Expression.Property(e, "ID");
Expression<Func<PreRecord, int>> orderByExpression = Expression.Lambda<Func<PreRecord, int>>(body, e);
And use your expression in the query:
qry = dcx.Set<T>.OrderBy(orderByExpression);
This way your linq query will not be validated during compile time but execution time. Here I assumed ID is of type int, if the type is different change it accordingly.

Error compiling Query with JPA (EL) and Enum

I have a weird problem with JPA (EclipseLink 2.3.2) and Enum in a Named Query.
I have a named query:
SELECT f FROM FILES f WHERE (:dataType IS NULL OR f.dataType = :dataType)
which results in
Error compiling the query ... invalid enum equal expression, cannot compare enum value of type [com.example.DataTypeEnum} with a non enum value of type [java.lang.Object]
Now i thought i might do an invalid operation, but if i try
SELECT f FROM FILES f WHERE (:dataType IS NULL)
or
SELECT f FROM FILES f WHERE (f.dataType = :dataType)
or even
SELECT f FROM FILES f WHERE (f.dataType = :dataType OR :dataType IS NULL)
the query compiles and runs fine. (but obviously not with the desired result).
Does anyone know what i'm doing wrong?
edit:
I have my entity annotated as such:
#Enumerated(EnumType.STRING)
public DataTypeEnum getDataType() {
return dataType;
}
That is odd. What version are you using? Did you try 2.4?

Using a flag in an Entity Framework Where clause

I have a class (built by EF from my database) that has a field that is a flag. The field is stored in the database as an int in a column named CategoryEnum. I have an enum that specifies the permissible values of the flag:
[Flags]
public enum RuleCategories
{
None = 0x0000,
ApplicantBased = 0x0001,
LocationBased = 0x0002,
PolicyBased = 0x0004,
PropertyBased = 0x0008
}
When I try to retrieve the objects using LINQ to Entities
var allRules = from r in context.Rules
where ((r.CategoryEnum & (int)categories) != 0)
select r;
I get this error:
Unable to create a constant value of type 'Closure type'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
or, if I try to cast the entity value to the enum
var allRules = from r in context.Rules
where (((RuleCategories)r.CategoryEnum & categories) != 0)
select r;
I get a different error:
Unable to cast the type 'System.Int32' to type RuleCategories'. LINQ to Entities only supports casting Entity Data Model primitive types.
How do I select entities based on a flag?
Thanks
I am going to guess and say that you are using the good old EF 3.5. This works without problems with EF 4.0 in VS2010. There is a problem with the 3.5 version, however, and you will have to use a workaround. Cast the categories variable to int before the query, and then use your int variable inside the query itself:
int preCastCategories = (int)categories;
var allRules = from r in context.Rules
where ((r.CategoryEnum & preCastCategories) != 0)
select r;