quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.72k stars 2.66k forks source link

Hibernate ORM 6.2 - NullPointerException in TupleMappingModelExpressible #36363

Closed mikethecalamity closed 10 months ago

mikethecalamity commented 1 year ago

Describe the bug

I am attempting to migrate from Quarkus 2.16.8 to 3.3.3 and I receive a NPE when running a query using the new version. Everything worked before and I thought I made all the necessary updates in the migration guide.

I'm assuming something is missing from my migration or configuration, but I can't determine what.

Expected behavior

NullPointerException: Cannot invoke "org.hibernate.metamodel.mapping.MappingModelExpressible.forEachJdbcType(int, org.hibernate.internal.util.IndexedConsumer)
" because "this.components[i]" is null
        at org.hibernate.metamodel.model.domain.internal.TupleMappingModelExpressible.forEachJdbcType(TupleMappingModelExpressible.java:41)
        at org.hibernate.metamodel.model.domain.internal.TupleMappingModelExpressible.<init>(TupleMappingModelExpressible.java:28)
        at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.resolveMappingExpressible(MappingMetamodelImpl.java:810)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.determineValueMapping(BaseSqmToSqlAstConverter.java:5675)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.lambda$visitWithInferredType$98(BaseSqmToSqlAstConverter.java:6784)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.resolveInferredType(BaseSqmToSqlAstConverter.java:5216)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitTuple(BaseSqmToSqlAstConverter.java:5971)
        at org.hibernate.query.sqm.tree.expression.SqmTuple.accept(SqmTuple.java:84)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitWithInferredType(BaseSqmToSqlAstConverter.java:6786)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitInSubQueryPredicate(BaseSqmToSqlAstConverter.java:7716)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitInSubQueryPredicate(BaseSqmToSqlAstConverter.java:434)
        at org.hibernate.query.sqm.tree.predicate.SqmInSubQueryPredicate.accept(SqmInSubQueryPredicate.java:87)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitWhereClause(BaseSqmToSqlAstConverter.java:2471)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQuerySpec(BaseSqmToSqlAstConverter.java:2048)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQuerySpec(BaseSqmToSqlAstConverter.java:434)
        at org.hibernate.query.sqm.tree.select.SqmQuerySpec.accept(SqmQuerySpec.java:125)
        at org.hibernate.query.sqm.spi.BaseSemanticQueryWalker.visitQueryPart(BaseSemanticQueryWalker.java:221)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQueryPart(BaseSqmToSqlAstConverter.java:1902)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitSelectStatement(BaseSqmToSqlAstConverter.java:1587)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitSelectStatement(BaseSqmToSqlAstConverter.java:434)
        at org.hibernate.query.sqm.tree.select.SqmSelectStatement.accept(SqmSelectStatement.java:222)
        at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.translate(BaseSqmToSqlAstConverter.java:770)
        at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.buildCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:345)
        at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:268)
        at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:244)
        at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:518)
        at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:367)
        at org.hibernate.query.Query.getResultList(Query.java:119)
        at my.project.jpa.DataManager.getAll(DataManager.java:47)

Actual behavior

No failure

How to Reproduce?

Build Gradle:

dependencies {
    implementation 'io.quarkus:quarkus-config-yaml'
    implementation 'io.quarkus:quarkus-hibernate-orm'
    implementation 'io.quarkus:quarkus-jdbc-postgresql'
    implementation 'io.quarkus:quarkus-kubernetes'
    implementation 'io.quarkus:quarkus-kubernetes-config'
    implementation 'io.quarkus:quarkus-logging-json'
    implementation 'io.quarkus:quarkus-rest-client-reactive'
    implementation 'io.quarkus:quarkus-rest-client-reactive-jackson'
    implementation 'io.quarkus:quarkus-resteasy-reactive'
    implementation 'io.quarkus:quarkus-resteasy-reactive-jackson'
    implementation 'io.quarkus:quarkus-smallrye-context-propagation'
    implementation 'io.quarkus:quarkus-smallrye-health'
    implementation 'io.quarkus:quarkus-smallrye-openapi'
    implementation 'io.quarkus:quarkus-undertow'
    implementation 'org.amqphub.quarkus:quarkus-qpid-jms'
}

Code:

@Inject
private EntityManager em;

public List<MyEntity> getAll(final Instant time) {
    final TypedQuery<MyEntity> q = em.createNamedQuery(MyEntity.GET_LATEST_AT_TIME, MyEntity.class);
    q.setParameter("time", time);
    return q.getResultList();
}

@Entity
@Table(schema = "my_schema", name = "my_table")
@NamedQueries({
        @NamedQuery(name = MyEntity.GET_LATEST_AT_TIME,
                query = "SELECT m FROM MyEntity AS m WHERE (m.associatedId, m.value) IN"
                        + " (SELECT innerM.associatedId, max(innerM.value) FROM MyEntity AS innerM"
                        + " WHERE innerM.value <= :time AND innerM.isDeleted = false"
                        + " GROUP BY innerM.associatedId)")
})
public class MyEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    public static final String GET_LATEST_AT_TIME = "my_table.getLatestAtTime";

    @Id
    @Column(name = "id")
    private UUID id;

    @Column(name = "value")
    private long value;

    @Column(name = "associated_id")
    private UUID associatedId;

    @Column(name = "is_deleted")
    private boolean isDeleted;
}

Config:

quarkus:
  datasource:
    db-kind: postgresql
    jdbc:
      background-validation-interval: 20S
      idle-removal-interval: 30S
      max-size: 10
      min-size: 0
      url: jdbc:postgresql://${PGHOST}:${PGPORT}/${PGDATABASE}?user=${PGDATABASE}&ApplicationName=my-project&reWriteBatchedInserts=true
    username: ${PGDATABASE}
    password: ${PGPASSWORD}

Output of uname -a or ver

No response

Output of java -version

Temurin 17.0.8

GraalVM version (if different from Java)

No response

Quarkus version or git rev

3.3.3

Build tool (ie. output of mvnw --version or gradlew --version)

Gradle 8.3

Additional information

No response

quarkus-bot[bot] commented 1 year ago

/cc @Sanne (hibernate-orm), @geoand (kubernetes), @gsmet (hibernate-orm), @iocanel (kubernetes), @yrodiere (hibernate-orm)

yrodiere commented 1 year ago

Thanks for reporting.

This looks more like a bug (potentially in Hibernate ORM itself) than something missing from the migration guide.

That being said, I think the reproducer would be slightly more useful if it included the query that triggers the failure :] What's the code of MyEntity, and particularly what's the value of MyEntity.GET_LATEST_AT_TIME and what's the definition of the corresponding query?

gsmet commented 1 year ago

It would help immensely if you could put together a self contained reproducer. Thanks!

Sanne commented 1 year ago

cc/ @beikov

mikethecalamity commented 1 year ago

@yrodiere I added my entity class to the example. I don't think it has to do with the query, I think that query is just the first one to be run.

@gsmet I'll try to put together a self-contained reproducer. I'm not sure if I'll be able to because of the postgres database though.

mikethecalamity commented 1 year ago

@gsmet I was finally able to get together a self-contained reproducer, but I'm not get the error. So it's definitely something with my company's baseline/configuration. But I'm not sure where to go from here to track down the issue, since the error is deep in the guts of Hibernate.

yrodiere commented 1 year ago

@mikethecalamity In my experience the only way to proceed in these cases is to build the reproducer by basically copy-pasting your whole project to a new one, then stripping it down little by little, regularly checking whether the bug is still there. It's ready when you can't remove anything without eliminating the bug.

In particular, make sure to use Quarkus in your reproducer, at least at first, because Quarkus does customize Hibernate ORM significantly and this can lead ORM to behave differently than with its default settings, including different bugs.

ingalemart commented 1 year ago

I am experiencing the same bug in Spring Boot 3.1.4. The problem seems to be related to HQL queries containing clauses such as (value1, value2) IN (subquery). The "TupleMappingModelExpressible" constructor calls the "forEachJdbcType" method which uses the "components" array, but in this case, the array contains null values.

yrodiere commented 1 year ago

I am experiencing the same bug in Spring Boot 3.1.4. The problem seems to be related to HQL queries containing clauses such as (value1, value2) IN (subquery). The "TupleMappingModelExpressible" constructor calls the "forEachJdbcType" method which uses the "components" array, but in this case, the array contains null values.

Thanks for letting us know @ingalemart! Did you report this to the ORM project already? Do you have a link?

ingalemart commented 1 year ago

I am experiencing the same bug in Spring Boot 3.1.4. The problem seems to be related to HQL queries containing clauses such as (value1, value2) IN (subquery). The "TupleMappingModelExpressible" constructor calls the "forEachJdbcType" method which uses the "components" array, but in this case, the array contains null values.

Thanks for letting us know @ingalemart! Did you report this to the ORM project already? Do you have a link?

Yes, here is the ticket: https://hibernate.atlassian.net/browse/HHH-17332

yrodiere commented 1 year ago

Thanks! I updated the labels.

mikethecalamity commented 1 year ago

@ingalemart is there any workaround that you've found?

ingalemart commented 12 months ago

@ingalemart is there any workaround that you've found?

@mikethecalamity Unfortunately I couldn't find a proper workaround, but I guess you could revert to Hibernate ORM 6.2.4 or earlier, or you could try to rewrite your query so that it does not need an anonoymous tuple. Maybe you could split it into two separate queries or use a self join

mikethecalamity commented 12 months ago

Thanks @ingalemart, that works for now, I added to my build.gradle:

configurations.all {
    resolutionStrategy {
        force 'org.hibernate.orm:hibernate-core:6.2.4.Final'
    }
 }
mikethecalamity commented 10 months ago

@ingalemart any word on whether the Hibernate folks fixed this issue? And if so what version has the fix

yrodiere commented 10 months ago

As @ingalemart wrote, this was reported as https://hibernate.atlassian.net/browse/HHH-17332, which is fixed in several versions, as you can see by following the link. As for Quarkus, this will be fixed when we merge the upgrade to ORM 6.4: #36978

mikethecalamity commented 6 months ago

Has anyone still seen this in Hibernate 6.4? I've been working on upgrading our services to Quarkus 3.9.3 (which uses the latest Hibernate 6.4.4) and I still see this issue. But the reproducer linked in the hibernate issue does now pass with 6.4.4. So I am at a loss as to why this issue would still be happening and cannot get it to reproduce in a vacuum.

yrodiere commented 6 months ago

@mikethecalamity can you please try to write a reproducer based on this code, which provides a closer environment to quarkus? https://github.com/hibernate/hibernate-test-case-templates/blob/main/orm/hibernate-orm-6/src/test/java/org/hibernate/bugs/QuarkusLikeORMUnitTestCase.java

If you manage to reproduce, please open a new issue with a link to this one .