Skip to content

ParameterBinder creation fails during count query derivation of declared queries using parameters only in ORDER BY #3756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
pavelety opened this issue Jan 29, 2025 · 1 comment
Assignees
Labels
type: bug A general bug

Comments

@pavelety
Copy link

After migrating from 3.3.8 to 3.4.0 (issue stays in 3.4.1, 3.4.2) facing in runtime the IllegalStateException: No position associated for code like this:

@Query("""
	select p
	from Product p
	order by CASE WHEN p.width > :width THEN 0 ELSE 1 END
""")
Page<Product> findWithCustomOrder(int width, Pageable pageable);
Stacktrace for java.lang.IllegalStateException: No position associated
at org.springframework.data.jpa.repository.query.ParameterBinding$BindingIdentifier.getPosition(ParameterBinding.java:420) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.QueryParameterSetterFactory$BasicQueryParameterSetterFactory.create(QueryParameterSetterFactory.java:246) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createQueryParameterSetter(ParameterBinderFactory.java:146) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createSetters(ParameterBinderFactory.java:135) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createQueryAwareBinder(ParameterBinderFactory.java:102) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.createBinder(AbstractStringBasedJpaQuery.java:141) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.lambda$1(AbstractStringBasedJpaQuery.java:98) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.util.Lazy.getNullable(Lazy.java:135) ~[spring-data-commons-3.4.2.jar:3.4.2]
at org.springframework.data.util.Lazy.get(Lazy.java:113) ~[spring-data-commons-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.doCreateCountQuery(AbstractStringBasedJpaQuery.java:157) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.createCountQuery(AbstractJpaQuery.java:269) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$PagedExecution.count(JpaQueryExecution.java:211) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$PagedExecution.lambda$0(JpaQueryExecution.java:206) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.support.PageableExecutionUtils.getPage(PageableExecutionUtils.java:71) ~[spring-data-commons-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$PagedExecution.doExecute(JpaQueryExecution.java:205) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:93) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:152) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:140) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) ~[spring-data-commons-3.4.2.jar:3.4.2]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ~[spring-data-commons-3.4.2.jar:3.4.2]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170) ~[spring-data-commons-3.4.2.jar:3.4.2]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) ~[spring-data-commons-3.4.2.jar:3.4.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) ~[spring-data-commons-3.4.2.jar:3.4.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.2.jar:6.2.2]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) ~[spring-tx-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136) ~[spring-data-jpa-3.4.2.jar:3.4.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) ~[spring-aop-6.2.2.jar:6.2.2]
at jdk.proxy2/jdk.proxy2.$Proxy183.findWithCustomOrder(Unknown Source) ~[na:na]

Hibernate logs for 3.3.8:

select p1_0... from Product p1_0 order by case when p1_0.width>? then 0 else 1 end fetch first ? rows only
select count(p1_0.code) from Product p1_0

Hibernate logs for 3.4.2:

select p1_0... from Product p1_0 order by case when p1_0.width>? then 0 else 1 end fetch first ? rows only

@Param annotation and numeric access by ?1 don't help.

Workaround:
Figured out that it happens if parameters are only in order by part and came to a workaround (add always true condition into where part with query param).

@Query("""
	select p
	from Product p where :width=:width
	order by CASE WHEN p.width > :width THEN 0 ELSE 1 END
""")
Page<Product> findWithCustomOrder(int width, Pageable pageable);

Removing Pageable and Page is also fixing the issue.
If you need I can prepare a simple project, but issue is easily reproducible:
-query param is only in order by
-use Pageable and Page
-Spring Boot 3.4.0+

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 29, 2025
@mp911de mp911de self-assigned this Jan 29, 2025
@mp911de mp911de added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 29, 2025
@mp911de mp911de changed the title Can't use query parameter if it's only in order by part of JPQL and with Pageable ParameterBinder creation fails during count query derivation of declared queries using parameters only in ORDER BY Jan 29, 2025
@mp911de mp911de added this to the 3.3.9 (2024.0.9) milestone Jan 29, 2025
@mp911de
Copy link
Member

mp911de commented Jan 29, 2025

Thanks for reporting the problem. It is indeed an issue on our side introduced by a combination of changes (#3293, ) by copying parameters from the original query into the count query and remembering whether we use named parameters in the original query. 3.3.x is not affected because the named parameter check in StringQuery returns a cached (but wrong) value. We assumed that parameters have either a name or a position and because we've checked against the query, we didn't consider the case that count queries (we truncate the ORDER BY clause) might not use any parameters and so we've used a code path that wasn't intended for parameter lookup.

For the time being, you can specify a count-query.

mp911de added a commit that referenced this issue Jan 29, 2025
We now skip binding parameter lookup if the query isn't using named parameters and the parameter is not associated with a name. Also, we check for presence of lookup identifiers to avoid parameter binding that cannot be looked up as they are not used anymore.

This can happen when a declared query uses parameters only in the ORDER BY clause that is truncated during count query derivation. Then the query object reports parameters althtough they are not being used.

We also refined parameter carryover during count query derivation. Previously, we copied all parameters without introspecting their origin. now, we copy only expression parameters to the derived query as count query derivation doesn't have access to expressions as our query parsers require valid JPQL.

Closes #3756
mp911de added a commit that referenced this issue Jan 29, 2025
We now skip binding parameter lookup if the query isn't using named parameters and the parameter is not associated with a name. Also, we check for presence of lookup identifiers to avoid parameter binding that cannot be looked up as they are not used anymore.

This can happen when a declared query uses parameters only in the ORDER BY clause that is truncated during count query derivation. Then the query object reports parameters althtough they are not being used.

We also refined parameter carryover during count query derivation. Previously, we copied all parameters without introspecting their origin. now, we copy only expression parameters to the derived query as count query derivation doesn't have access to expressions as our query parsers require valid JPQL.

Closes #3756
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants