Seems, that Hibernate 6.2.9 (coming with the current Quarkus), contains additional validation and doesn’t allow to issue such a query at all:
Caused by: org.hibernate.query.sqm.InterpretationException: Error interpreting query [DELETE FROM XXX WHERE name = ?1 AND id = function('any', ?2)]; this may indicate a semantic (user query) problem or a bug in the parser [DELETE FROM XXX WHERE name = ?1 AND id = function('any', ?2)]
at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:97)
...
Caused by: java.lang.IllegalArgumentException: Can't compare test expression of type [BasicSqmPathSource(id : String)] with element of type [basicType@21(java.lang.Boolean,16)]
at org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.assertComparable(SqmCriteriaNodeBuilder.java:2102)
...
(in the entity, the “id” is a String).
Same happens when the Criteria API is used instead of JPQL.
What would be the best practice here?
We’re using =ANY (instead of IN) and passing the matching collection of elements as a single array parameter for two reasons:
to avoid the limit of query parameters (max 32767 bind-params in the PG JDBC driver, AFAIK)
performance vs (very long) boolean expressions that PostgreSQL generates for ‘plain’ INs
BTW. does Hibernate 6 JPQL / criteria API have a way to express this kind native query:
SELECT * FROM my_table
WHERE (attr_1, attr_2) IN (SELECT * FROM unnest(:attr_1_array, :attr_2_array))
– it was SELECTing * FROM a function (syntax required by PostgreSQL) I wasn’t able to express. Again, the arguments are arrays for the same reasons as above.
If the attribute by which you want to filter by is the primary key or a natural id, then you can just use the Hibernate multiLoad API and it will use that SQL syntax behind the scenes automatically for PostgreSQL.
Out of the box support for array functions/predicates just landed in Hibernate ORM 6.4, so if you can/want to upgrade to that, you’ll be able to make use of where array_contains(arrayAttribute, :arrayParam).
In the meantime, you can create a custom SqmFunction for this purpose and register it via a FunctionContributor. You can render any SQL you want in such a custom function implementation and use it from HQL like a regular function e.g. where array_any(arrayAttribute, :arrayParam).
java.lang.NullPointerException: Cannot invoke "org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource.getSqmPathType()" because the return value of "org.hibernate.query.sqm.tree.expression.SqmLiteral.getNodeType()" is null
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitLiteral(BaseSqmToSqlAstConverter.java:5445)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitLiteral(BaseSqmToSqlAstConverter.java:435)
at org.hibernate.query.sqm.tree.expression.SqmLiteral.accept(SqmLiteral.java:65)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitWithInferredType(BaseSqmToSqlAstConverter.java:6800)
at org.hibernate.query.sqm.function.SelfRenderingSqmFunction.resolveSqlAstArguments(SelfRenderingSqmFunction.java:132)
at org.hibernate.query.sqm.function.SelfRenderingSqmFunction.convertToSqlAst(SelfRenderingSqmFunction.java:144)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitFunction(BaseSqmToSqlAstConverter.java:6018)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitFunction(BaseSqmToSqlAstConverter.java:435)
at org.hibernate.query.sqm.tree.expression.SqmFunction.accept(SqmFunction.java:66)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitBooleanExpressionPredicate(BaseSqmToSqlAstConverter.java:7730)
at org.hibernate.query.sqm.tree.predicate.SqmBooleanExpressionPredicate.accept(SqmBooleanExpressionPredicate.java:71)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitJunctionPredicate(BaseSqmToSqlAstConverter.java:6967)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitJunctionPredicate(BaseSqmToSqlAstConverter.java:435)
at org.hibernate.query.sqm.tree.predicate.SqmJunctionPredicate.accept(SqmJunctionPredicate.java:74)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitWhereClause(BaseSqmToSqlAstConverter.java:2478)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitDeleteStatement(BaseSqmToSqlAstConverter.java:1121)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitDeleteStatement(BaseSqmToSqlAstConverter.java:435)
at org.hibernate.query.sqm.tree.delete.SqmDeleteStatement.accept(SqmDeleteStatement.java:94)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.translate(BaseSqmToSqlAstConverter.java:771)
at org.hibernate.query.sqm.internal.SimpleDeleteQueryPlan.createDeleteTranslator(SimpleDeleteQueryPlan.java:86)
at org.hibernate.query.sqm.internal.SimpleDeleteQueryPlan.executeUpdate(SimpleDeleteQueryPlan.java:105)
at org.hibernate.query.sqm.internal.QuerySqmImpl.doExecuteUpdate(QuerySqmImpl.java:735)
at org.hibernate.query.sqm.internal.QuerySqmImpl.executeUpdate(QuerySqmImpl.java:705)
It fails while trying to inspect the array literal:
Looks like the usage of custom array types is unnecessary now and Hibernate 6 will actually pass java arrays as SQL arrays without further steps. Are there any caveats here?
one more question: in a function contributor, what would be the correct invariant / return type for a function, that turns array(s) into a set of rows? (PostgreSQL’s select * from unnest(…) with one or more arrays).
The types of columns are not fixed in this case.
As of now, I’ve tried with StandardBasicTypes.OBJECT_TYPE (which definitely doesn’t describe reality), and though it works with the use case column IN (my_custom_function(?1), it doesnt’ work when trying to compare multiple columns, e.g. (column1, column2) in (my_custom_function(?1, ?2), resulting in:
java.lang.ClassCastException: class org.hibernate.type.JavaObjectType cannot be cast to class org.hibernate.metamodel.mapping.EmbeddableMappingType (org.hibernate.type.JavaObjectType and org.hibernate.metamodel.mapping