From 00d1d254d1016f89d9e46297efc044fecd0f8dca 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 | 17 +++++-- .../JpaQueryLookupStrategyUnitTests.java | 48 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index f8c276910..3c7d2a40f 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -46,6 +46,8 @@ import org.springframework.util.StringUtils; * @author Oliver Wehrens * @author Mark Paluch * @author Jens Schauder + * @author Diego Krupitza + * @author Greg Turnquist */ class StringQuery implements DeclaredQuery { @@ -190,6 +192,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; @@ -266,15 +272,16 @@ class StringQuery implements DeclaredQuery { String expression = spelExtractor.getParameter(parameterName == null ? parameterIndexString : parameterName); String replacement = null; - Assert.isTrue(parameterIndexString != null || parameterName != null, () -> String.format("We need either a name or an index! Offending query string: %s", query)); + Assert.isTrue(parameterIndexString != null || parameterName != null, + () -> String.format("We need either a name or an index! Offending query string: %s", query)); + + 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/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java index aeae43261..375533b1d 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java @@ -190,6 +190,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, 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") @@ -207,6 +243,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); }