From 782bcf325d1aaab5a0e9c6da37af147646f7bc12 Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Tue, 31 May 2022 14:26:09 -0500 Subject: [PATCH] Fine tune checks for JDBC and JPA style parameters. The checks for JDBC and JPA parameters were sloppy and based on side effects. By using zero width lookaheads, we can precisely spot situtations where the user has both types of parameters. Otherwise, let the query on through to the JPA provider. Closes #2551. --- .../jpa/repository/query/StringQuery.java | 13 +++-- .../JpaQueryLookupStrategyUnitTests.java | 48 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index 60ca72af3..994a7837c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -47,6 +47,7 @@ import org.springframework.util.StringUtils; * @author Mark Paluch * @author Jens Schauder * @author Diego Krupitza + * @author Greg Turnquist */ class StringQuery implements DeclaredQuery { @@ -165,6 +166,10 @@ class StringQuery implements DeclaredQuery { // .............................................................^ start with a question mark. private static final Pattern PARAMETER_BINDING_BY_INDEX = Pattern.compile(POSITIONAL_OR_INDEXED_PARAMETER); private static final Pattern PARAMETER_BINDING_PATTERN; + private static final Pattern JDBC_STYLE_PARAM = Pattern.compile(" \\?(?!\\d)"); // ?[no digit] + private static final Pattern NUMBERED_STYLE_PARAM = Pattern.compile(" \\?(?=\\d)"); // ?[digit] + private static final Pattern NAMED_STYLE_PARAM = Pattern.compile(" :\\w+"); // :[text] + private static final String MESSAGE = "Already found parameter binding with same index / parameter name but differing binding type! " + "Already have: %s, found %s! If you bind a parameter multiple times make sure they use the same binding."; private static final int INDEXED_PARAMETER_GROUP = 4; @@ -243,13 +248,13 @@ class StringQuery implements DeclaredQuery { String expression = spelExtractor.getParameter(parameterName == null ? parameterIndexString : parameterName); String replacement = null; + queryMeta.usesJdbcStyleParameters = JDBC_STYLE_PARAM.matcher(resultingQuery).find(); + usesJpaStyleParameters = NUMBERED_STYLE_PARAM.matcher(resultingQuery).find() + || NAMED_STYLE_PARAM.matcher(resultingQuery).find(); + expressionParameterIndex++; if ("".equals(parameterIndexString)) { - - queryMeta.usesJdbcStyleParameters = true; parameterIndex = expressionParameterIndex; - } else { - usesJpaStyleParameters = true; } if (usesJpaStyleParameters && queryMeta.usesJdbcStyleParameters) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java index 76cc0cf13..745d6cac5 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java @@ -193,6 +193,42 @@ public class JpaQueryLookupStrategyUnitTests { assertThatIllegalStateException().isThrownBy(() -> query.getQueryMethod()); } + @Test // GH-2551 + void customQueryWithQuestionMarksShouldWork() throws NoSuchMethodException { + + QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND, + EVALUATION_CONTEXT_PROVIDER, new BeanFactoryQueryRewriterProvider(beanFactory), EscapeCharacter.DEFAULT); + + Method namedMethod = UserRepository.class.getMethod("customQueryWithQuestionMarksAndNamedParam", String.class); + RepositoryMetadata namedMetadata = new DefaultRepositoryMetadata(UserRepository.class); + + strategy.resolveQuery(namedMethod, namedMetadata, projectionFactory, namedQueries); + + assertThatIllegalArgumentException().isThrownBy(() -> { + + Method jdbcStyleMethod = UserRepository.class.getMethod("customQueryWithQuestionMarksAndJdbcStyleParam", + String.class); + RepositoryMetadata jdbcStyleMetadata = new DefaultRepositoryMetadata(UserRepository.class); + + strategy.resolveQuery(jdbcStyleMethod, jdbcStyleMetadata, projectionFactory, namedQueries); + }).withMessageContaining("JDBC style parameters (?) are not supported for JPA queries."); + + Method jpaStyleMethod = UserRepository.class.getMethod("customQueryWithQuestionMarksAndNumberedStyleParam", + String.class); + RepositoryMetadata jpaStyleMetadata = new DefaultRepositoryMetadata(UserRepository.class); + + strategy.resolveQuery(jpaStyleMethod, jpaStyleMetadata, projectionFactory, namedQueries); + + assertThatIllegalArgumentException().isThrownBy(() -> { + + Method jpaAndJdbcStyleMethod = UserRepository.class + .getMethod("customQueryWithQuestionMarksAndJdbcStyleAndNumberedStyleParam", String.class, String.class); + RepositoryMetadata jpaAndJdbcMetadata = new DefaultRepositoryMetadata(UserRepository.class); + + strategy.resolveQuery(jpaAndJdbcStyleMethod, jpaAndJdbcMetadata, projectionFactory, namedQueries); + }).withMessageContaining("Mixing of ? parameters and other forms like ?1 is not supported"); + } + interface UserRepository extends Repository { @Query("something absurd") @@ -210,6 +246,18 @@ public class JpaQueryLookupStrategyUnitTests { @Query(value = "something absurd", name = "my-query-name") User annotatedQueryWithQueryAndQueryName(); + @Query("SELECT * FROM table WHERE (json_col->'jsonKey')::jsonb \\?\\? :param ") + List customQueryWithQuestionMarksAndNamedParam(String param); + + @Query("SELECT * FROM table WHERE (json_col->'jsonKey')::jsonb \\?\\? ? ") + List customQueryWithQuestionMarksAndJdbcStyleParam(String param); + + @Query("SELECT * FROM table WHERE (json_col->'jsonKey')::jsonb \\?\\? ?1 ") + List customQueryWithQuestionMarksAndNumberedStyleParam(String param); + + @Query("SELECT * FROM table WHERE (json_col->'jsonKey')::jsonb \\?\\? ?1 and other_col = ? ") + List customQueryWithQuestionMarksAndJdbcStyleAndNumberedStyleParam(String param1, String param2); + // This is a named query with Sort parameter, which isn't supported List customNamedQuery(String firstname, Sort sort); }