blaze-persistence icon indicating copy to clipboard operation
blaze-persistence copied to clipboard

SUM function throws NPE on case function: Cannot invoke "org.hibernate.type.Type.sqlTypes(org.hibernate.engine.spi.Mapping)" because "type" is null

Open ctobe-zen opened this issue 2 years ago • 5 comments

Description

When you SUM a case expression like below

NumberExpression<Integer> riskSum = new CaseBuilder()
             .when(QMeters.meterStatus.in("Current", "Expired", "Future")).then(1).otherwise(0)
             .sum();

*This might just be for predicates in the WHEN.

You get the following error:

Cannot invoke "org.hibernate.type.Type.sqlTypes(org.hibernate.engine.spi.Mapping)" because "type" is null

Shout out to Jan-Willem @jwgmeligmeyling for his work around suggestion of wrapping in a treat.

treatAsNum(Integer.class, expression).sum() which works.

Stack trace below:

Caused by: java.lang.NullPointerException: Cannot invoke "org.hibernate.type.Type.sqlTypes(org.hibernate.engine.spi.Mapping)" because "type" is null
at org.hibernate.dialect.function.StandardAnsiSqlAggregationFunctions$SumFunction.determineJdbcTypeCode(StandardAnsiSqlAggregationFunctions.java:200)
at org.hibernate.dialect.function.StandardAnsiSqlAggregationFunctions$SumFunction.getReturnType(StandardAnsiSqlAggregationFunctions.java:158)
at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.findFunctionReturnType(SessionFactoryHelper.java:431)
at org.hibernate.hql.internal.ast.tree.AggregateNode.getDataType(AggregateNode.java:68)
at org.hibernate.hql.internal.ast.tree.BinaryLogicOperatorNode.extractDataType(BinaryLogicOperatorNode.java:280)
at org.hibernate.hql.internal.ast.tree.BinaryLogicOperatorNode.initialize(BinaryLogicOperatorNode.java:49)
at org.hibernate.hql.internal.ast.HqlSqlWalker.prepareLogicOperator(HqlSqlWalker.java:1489)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.comparisonExpr(HqlSqlBaseWalker.java:4737)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.logicalExpr(HqlSqlBaseWalker.java:2180)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.searchedCaseWhenClause(HqlSqlBaseWalker.java:5580)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.searchedCaseExpression(HqlSqlBaseWalker.java:5361)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.caseExpr(HqlSqlBaseWalker.java:5237)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.arithmeticExpr(HqlSqlBaseWalker.java:3442)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectExpr(HqlSqlBaseWalker.java:2468)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectExprList(HqlSqlBaseWalker.java:2275)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectClause(HqlSqlBaseWalker.java:1534)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:611)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:339)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:287)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:276)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:162)
at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:636)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:748)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:848)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:114)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311)
at jdk.proxy2/jdk.proxy2.$Proxy271.createQuery(Unknown Source)
at com.blazebit.persistence.impl.AbstractCommonQueryBuilder.getTypedQuery(AbstractCommonQueryBuilder.java:2851)
at com.blazebit.persistence.impl.AbstractQueryBuilder.getQuery(AbstractQueryBuilder.java:53)
at com.blazebit.persistence.querydsl.AbstractBlazeJPAQuery.createQuery(AbstractBlazeJPAQuery.java:177)
at com.blazebit.persistence.querydsl.AbstractBlazeJPAQuery.createQuery(AbstractBlazeJPAQuery.java:167)
at com.querydsl.jpa.impl.AbstractJPAQuery.fetch(AbstractJPAQuery.java:242)
at com.demo.app.getDeletableSumById(PortfolioQueryService.java:255)

Expected behavior

I expected it to know the Type as JPA does unless QueryDSL is doing some special magic. The error only occurs with a BlazeJPAQuery and not a JPAQuery.

Actual behavior

It lost the type on the sum function. I believe using a count works, so maybe its just with Blaze's SUM implementation.

Environment

Java: 17 JPA-Provider: Hibernate 5.6.11.Final DBMS: MySQL 8 Blaze-Persistence: 1.6.8

ctobe-zen avatar May 05 '23 15:05 ctobe-zen

Can you please also post the generated HQL query string?

beikov avatar May 08 '23 12:05 beikov

new CaseBuilder()
             .when(QMeters.meterStatus.in("Current", "Expired", "Future")).then(1).otherwise(0)
             .sum()

I don't have the full query, but the fragment would for sure be serialized as:

SUM(CASE WHEN meters.meterStatus IN (:param_0, :param_1, :param_3) THEN :param_4 OTHERWISE :param_5)

So it might be a typing from the parameters thats the crulpit here.

Although I did suggest to @ctobe-zen to use literals instead of parameters before suggesting the TREAT_LONG function. I am not sure if that would have fixed it as well. If not, the following should reproduce it as well:

SUM(CASE WHEN meters.meterStatus IN ("Current", "Expired", "Future") THEN 1 OTHERWISE 0)

jwgmeligmeyling avatar May 08 '23 12:05 jwgmeligmeyling

I tried literals. It didn't work either. I could only get this to work using a treat as without it, it doesn't know the type.

ctobe-zen avatar May 09 '23 13:05 ctobe-zen

BlazeJPAQueryFactory has to be used with JPQLNextTemplates because Blaze-Persistence has its own query language (JPQL Next) as opposed to HQL. The translation from JPQL Next to HQL happens in Blaze-Persistence itself (and Hibernate version specific adjustments are made in the respective Blaze-Persistence Hibernate integrations). The Hibernate query handlers from Querydsl aren't used at all in BlazeJPAQueryFactory, as all queries go through the Blaze-Persistence CriteriaBuilder.

It is true that some dialects of Querydsl always render literals in case statements as a workaround of this limitation in Hibernate. Whether this implicit query rewriting is actually desirable is up for debate, which is why we went for an approach where literal rendering can be explicitly done through JPQLNextExpressions.literal(value).

jwgmeligmeyling avatar May 08 '24 16:05 jwgmeligmeyling