Spring Data JPA with specification: postgres native function not found - postgresql

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.

Related

JPA/Hibernate custom query with list of Postgres enums

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;

Error passing parameters and retrieving key value result set

I'm having problems understanding how MyBatis maps parameters passed in, and then marshalling the results back to the caller. I'd like to pass a POJO in, use properties set in there as part of the query and pass the results back in a map form.
My Mapping file function (without using the parameters):
<select id="getTotalUniqueUserDetails"
resultType="map"
parameterType="RequestFilter">
select KEY,sum(SINGLE_VALUE) as TOTAL from (
select schut.user_type_name as KEY, schuc.usage_count AS SINGLE_VALUE
from usage_count schuc
left join users schu
on schuc.user_key=schu.user_key
left join user_types schut
on schut.user_type_id=schu.user_type_id
)
GROUP by KEY;
</select>
My Mapping file function (with the parameters)
<select id="getTotalUniqueUserDetails"
resultType="map"
parameterType="RequestFilter">
select KEY,sum(SINGLE_VALUE) as TOTAL from (
select schut.user_type_name as KEY, schuc.usage_count AS SINGLE_VALUE
from usage_count schuc
left join users schu
on schuc.user_key=schu.user_key
left join user_types schut
on schut.user_type_id=schu.user_type_id
where schuc.year_month between
to_date('#{fromDate}','yyyy-mm-dd')
and to_date('#{toDate}','yyyy-mm-dd')
)
GROUP by KEY;
</select>
My mapper interface
public interface TotalUniqueUsers {
Object getTotalUniqueUserDetails(RequestFilter filter);
}
public class RequestFilter {
private String fromDate;
private String toDate;
... getters and setters for both above
}
The code that calls the query:
SqlSession sqlSession = DBConnection.getInstance().getSqlSession();
System.out.println("####"+filter.getFromDate());
System.out.println("####"+filter.getToDate());
TotalUniqueUsers testMapper = sqlSession.getMapper(TotalUniqueUsers.class);
return testMapper.getTotalUniqueUserDetails(filter);
With no parameters used in mapping file I get this error:
## The error occurred while setting parameters
### SQL: select KEY,sum(SINGLE_VALUE) as TOTAL from ( select schut.user_type_name as KEY, schuc.usage_count AS SINGLE_VALUE from usage_count schuc left join users schu on schuc.user_key=schu.user_key left join user_types schut on schut.user_type_id=schu.user_type_id ) GROUP by KEY;
### Cause: java.sql.SQLSyntaxErrorException: ORA-00933: SQL command not properly ended
With parameters referenced in mapping file I get this error:
### Cause: org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='fromDate', mode=IN, javaType=class java.lang.String, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #1 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.sql.SQLException: Invalid column index
If you've read this much, thank you from the Woodsman.
In short, if I use the parameter passed in the object, it says it can't use a null value. If I don't then it gives me some other error.
The result set should be something like
KEY TOTAL
GROUP1 33
GROUP2 55
I was hoping I could just let it stuff the result set into a map, with the keys being "GROUP1", "GROUP2", with their respective values.
Please tell me how to properly refer to the properties in the object in the map and how to marshall it back. Should I use a custom object?
You need to remove...
The semicolon at the end of the statement. Although not all drivers reject it, it is a common cause for ORA-00933 with Oracle's JDBC driver.
The single quotes surrounding the parameter placeholders e.g. '#{toDate}' to #{toDate}.
With single quotes, the first argument of to_date function becomes a literal instead of a java.sql.PreparedStatement placeholder (e.g. to_date('?', 'yyyy-mm-dd') instead of to_date(?, 'yyyy-mm-dd')). When MyBatis tries to call PreparedStatement#setString(int parameterIndex, String parameterValue), there is no placeholder in the statement and the driver throws the exception.

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.

Entity Framework and Stored Procedure Function Import with nullable parameters

I noticed that when Entity Framework generates a method for a stored procedure (function import), it tests to see if the parameter is null, and makes a decision like this:
if (contactID.HasValue)
{
contactIDParameter = new ObjectParameter("contactID", contactID);
}
else
{
contactIDParameter = new ObjectParameter("contactID", typeof(global::System.Int32));
}
I don't understand what its trying to do by passing the Type of the parameter as a parameter when the parameter is null? Exactly how does the stored procedure/function get executed in this case?
I did a test myself with SQL Profiler, and noticed that when I intentionally pass null as a parameter (by calling something like context.MyProcedure(null) ), null is simply passed as the parameter to the SQL server's stored procedure.
Some clarifications on this behavior would be appreciated.
I was interested in this question so I made some investigation.
ObjectParameter has two overloads - one for passing value and one for passing the type. The second is used if you pass null as the parameter value because EF internally need this. The reason is that function import must be called with ObjectParameters, not with plain parameters you are passing to the wrapping method.
Internally EF calls:
private EntityCommand CreateEntityCommandForFunctionImport(string functionName, out EdmFunction functionImport, params ObjectParameter[] parameters)
{
...
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i] == null)
{
throw EntityUtil.InvalidOperation(Strings.ObjectContext_ExecuteFunctionCalledWithNullParameter(i));
}
}
...
this.PopulateFunctionEntityCommandParameters(parameters, functionImport, command);
return command;
}
As you can see even null value must be represented as ObjectParameter because you can't simply pass null - it will throw exception. The PopulateFunctionEntityCommandParameters uses information about the type to create correct DbParameter for calling the stored procedure. The value of that parameter is DBNull.Value.
So you don't have to deal with it. It is just infrastructure.
When you watch the code of the class ObjectParameter constructors
public ObjectParameter (string name, object value)
public ObjectParameter (string name, Type type)
You can see that ObjectParameter has 3 important private fields:
_name (name of parameter, not null and immutable), _type (CLR type of the parameter, not null and immutable), _value (value of the parameter, can be changed and nullable)
When the first constructor is used, these fields are all initialized. With the second constructor, the _value field is left to be null.
In the ExecuteFunction of EF, a private method CreateEntityCommandForFunctionImport is used which calls another even deeper private method PopulateFunctionImportEntityCommandParameters which attaches the entity parameters.
Inside PopulateFunctionImportEntityCommandParameters, an instance of EntityParameter which represents a parameter in EntityCommand will be mapped to the name and value's properties of ObjectParameter.
This instruction explains it all:
entityParameter.Value = objectParameter.Value ?? DBNull.Value;
We pass the DBNull to EF if no value was specified as a parameter.