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); }