spring-projects / spring-data-jpa

Simplifies the development of creating a JPA-based data access layer.
https://spring.io/projects/spring-data-jpa/
Apache License 2.0
3.03k stars 1.42k forks source link

Query Parser Regression Bug in 3.3.5 #3661

Closed MartinHaeusler closed 3 weeks ago

MartinHaeusler commented 3 weeks ago

Spring Data 3.3.5 has a regression bug in its HQL query parser.

The following query:

    @Modifying
    @Query(
        value = """
        DELETE
        FROM
            MyEntity AS me
        WHERE
            me.deleted IS NOT NULL
        """
    )
    public void deleteAllSoftDeleted();

... creates an error during application startup:

org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract void com.example.demo.repository.MyEntityRepository.deleteAllSoftDeleted(); Reason: Cannot invoke "org.antlr.v4.runtime.ParserRuleContext.getParent()" because "ctx" is null
    at app//org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101)
    at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:119)
    at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:103)
    at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:92)
    at java.base@21.0.4/java.util.Optional.map(Optional.java:260)
    at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.<init>(QueryExecutorMethodInterceptor.java:92)
    at app//org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:357)
    at app//org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:290)
    at app//org.springframework.data.util.Lazy.getNullable(Lazy.java:135)
    at app//org.springframework.data.util.Lazy.get(Lazy.java:113)
    at app//org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:296)
    at app//org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:132)
    at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1853)
    at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1802)
    ... 52 more
Caused by: java.lang.NullPointerException: Cannot invoke "org.antlr.v4.runtime.ParserRuleContext.getParent()" because "ctx" is null
    at org.springframework.data.jpa.repository.query.HqlQueryTransformer.isSubquery(HqlQueryTransformer.java:105)
    at org.springframework.data.jpa.repository.query.HqlQueryTransformer.isSubquery(HqlQueryTransformer.java:105)
    at org.springframework.data.jpa.repository.query.HqlQueryTransformer.isSubquery(HqlQueryTransformer.java:105)
    at org.springframework.data.jpa.repository.query.HqlQueryTransformer.isSubquery(HqlQueryTransformer.java:105)
    at org.springframework.data.jpa.repository.query.HqlQueryTransformer.isSubquery(HqlQueryTransformer.java:105)
    at org.springframework.data.jpa.repository.query.HqlQueryTransformer.isSubquery(HqlQueryTransformer.java:105)
    at org.springframework.data.jpa.repository.query.HqlQueryTransformer.visitVariable(HqlQueryTransformer.java:362)
    at org.springframework.data.jpa.repository.query.HqlQueryRenderer.visitVariable(HqlQueryRenderer.java:1)
    at org.springframework.data.jpa.repository.query.HqlParser$VariableContext.accept(HqlParser.java:10200)
    at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18)
    at org.springframework.data.jpa.repository.query.HqlQueryRenderer.visitTargetEntity(HqlQueryRenderer.java:481)
    at org.springframework.data.jpa.repository.query.HqlQueryRenderer.visitTargetEntity(HqlQueryRenderer.java:1)
    at org.springframework.data.jpa.repository.query.HqlParser$TargetEntityContext.accept(HqlParser.java:2018)
    at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18)
    at org.springframework.data.jpa.repository.query.HqlQueryRenderer.visitDeleteStatement(HqlQueryRenderer.java:526)
    at org.springframework.data.jpa.repository.query.HqlQueryRenderer.visitDeleteStatement(HqlQueryRenderer.java:1)
    at org.springframework.data.jpa.repository.query.HqlParser$DeleteStatementContext.accept(HqlParser.java:2198)
    at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18)
    at org.springframework.data.jpa.repository.query.HqlQueryRenderer.visitQl_statement(HqlQueryRenderer.java:46)
    at org.springframework.data.jpa.repository.query.HqlQueryRenderer.visitQl_statement(HqlQueryRenderer.java:1)
    at org.springframework.data.jpa.repository.query.HqlParser$Ql_statementContext.accept(HqlParser.java:303)
    at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18)
    at org.springframework.data.jpa.repository.query.HqlQueryRenderer.visitStart(HqlQueryRenderer.java:35)
    at org.springframework.data.jpa.repository.query.HqlQueryRenderer.visitStart(HqlQueryRenderer.java:1)
    at org.springframework.data.jpa.repository.query.HqlParser$StartContext.accept(HqlParser.java:247)
    at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18)
    at org.springframework.data.jpa.repository.query.HqlQueryParser.doFindAlias(HqlQueryParser.java:101)
    at org.springframework.data.jpa.repository.query.JpaQueryParserSupport.findAlias(JpaQueryParserSupport.java:96)
    at org.springframework.data.jpa.repository.query.JpaQueryEnhancer.detectAlias(JpaQueryEnhancer.java:124)
    at org.springframework.data.jpa.repository.query.StringQuery.<init>(StringQuery.java:90)
    at org.springframework.data.jpa.repository.query.DeclaredQuery.of(DeclaredQuery.java:40)
    at org.springframework.data.jpa.repository.query.JpaQueryMethod.assertParameterNamesInAnnotatedQuery(JpaQueryMethod.java:168)
    at org.springframework.data.jpa.repository.query.JpaQueryMethod.<init>(JpaQueryMethod.java:149)
    at org.springframework.data.jpa.repository.query.DefaultJpaQueryMethodFactory.build(DefaultJpaQueryMethodFactory.java:44)
    at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:94)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:115)
    ... 64 more

I had a quick look at the code. At first glance to me it seems that HqlQueryTransformer.isSubquery is either missing a case in its instanceof cascade, or should not have been called in the first place.

The same code works fine in 3.3.4.

A minimal reproducer is available here: https://github.com/MartinHaeusler/springDataParseErrorReproducer

MartinHaeusler commented 3 weeks ago

Potentially a duplicate of this (not sure though, the query is different): https://github.com/spring-projects/spring-data-jpa/issues/3649

christophstrobl commented 3 weeks ago

thanks for reporting - update and delete ones are affected. Fix via #3650 will be in next service release.