Skip to content

Commit 2bdd0f8

Browse files
committed
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.
1 parent bd1dc05 commit 2bdd0f8

File tree

2 files changed

+57
-4
lines changed

2 files changed

+57
-4
lines changed

src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
* @author Mark Paluch
4848
* @author Jens Schauder
4949
* @author Diego Krupitza
50+
* @author Greg Turnquist
5051
*/
5152
class StringQuery implements DeclaredQuery {
5253

@@ -201,6 +202,10 @@ enum ParameterBindingParser {
201202
// .............................................................^ start with a question mark.
202203
private static final Pattern PARAMETER_BINDING_BY_INDEX = Pattern.compile(POSITIONAL_OR_INDEXED_PARAMETER);
203204
private static final Pattern PARAMETER_BINDING_PATTERN;
205+
private static final Pattern JDBC_STYLE_PARAM = Pattern.compile(" \\?(?!\\d)"); // <space>?[no digit]
206+
private static final Pattern NUMBERED_STYLE_PARAM = Pattern.compile(" \\?(?=\\d)"); // <space>?[digit]
207+
private static final Pattern NAMED_STYLE_PARAM = Pattern.compile(" :\\w+"); // <space>:[text]
208+
204209
private static final String MESSAGE = "Already found parameter binding with same index / parameter name but differing binding type! "
205210
+ "Already have: %s, found %s! If you bind a parameter multiple times make sure they use the same binding.";
206211
private static final int INDEXED_PARAMETER_GROUP = 4;
@@ -279,13 +284,13 @@ private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(St
279284
String expression = spelExtractor.getParameter(parameterName == null ? parameterIndexString : parameterName);
280285
String replacement = null;
281286

287+
queryMeta.usesJdbcStyleParameters = JDBC_STYLE_PARAM.matcher(resultingQuery).find();
288+
usesJpaStyleParameters = NUMBERED_STYLE_PARAM.matcher(resultingQuery).find()
289+
|| NAMED_STYLE_PARAM.matcher(resultingQuery).find();
290+
282291
expressionParameterIndex++;
283292
if ("".equals(parameterIndexString)) {
284-
285-
queryMeta.usesJdbcStyleParameters = true;
286293
parameterIndex = expressionParameterIndex;
287-
} else {
288-
usesJpaStyleParameters = true;
289294
}
290295

291296
if (usesJpaStyleParameters && queryMeta.usesJdbcStyleParameters) {

src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java

+48
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,42 @@ void noQueryShouldNotBeInvoked() {
190190
assertThatIllegalStateException().isThrownBy(() -> query.getQueryMethod());
191191
}
192192

193+
@Test // GH-2551
194+
void customQueryWithQuestionMarksShouldWork() throws NoSuchMethodException {
195+
196+
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
197+
EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.DEFAULT);
198+
199+
Method namedMethod = UserRepository.class.getMethod("customQueryWithQuestionMarksAndNamedParam", String.class);
200+
RepositoryMetadata namedMetadata = new DefaultRepositoryMetadata(UserRepository.class);
201+
202+
strategy.resolveQuery(namedMethod, namedMetadata, projectionFactory, namedQueries);
203+
204+
assertThatIllegalArgumentException().isThrownBy(() -> {
205+
206+
Method jdbcStyleMethod = UserRepository.class.getMethod("customQueryWithQuestionMarksAndJdbcStyleParam",
207+
String.class);
208+
RepositoryMetadata jdbcStyleMetadata = new DefaultRepositoryMetadata(UserRepository.class);
209+
210+
strategy.resolveQuery(jdbcStyleMethod, jdbcStyleMetadata, projectionFactory, namedQueries);
211+
}).withMessageContaining("JDBC style parameters (?) are not supported for JPA queries.");
212+
213+
Method jpaStyleMethod = UserRepository.class.getMethod("customQueryWithQuestionMarksAndNumberedStyleParam",
214+
String.class);
215+
RepositoryMetadata jpaStyleMetadata = new DefaultRepositoryMetadata(UserRepository.class);
216+
217+
strategy.resolveQuery(jpaStyleMethod, jpaStyleMetadata, projectionFactory, namedQueries);
218+
219+
assertThatIllegalArgumentException().isThrownBy(() -> {
220+
221+
Method jpaAndJdbcStyleMethod = UserRepository.class
222+
.getMethod("customQueryWithQuestionMarksAndJdbcStyleAndNumberedStyleParam", String.class, String.class);
223+
RepositoryMetadata jpaAndJdbcMetadata = new DefaultRepositoryMetadata(UserRepository.class);
224+
225+
strategy.resolveQuery(jpaAndJdbcStyleMethod, jpaAndJdbcMetadata, projectionFactory, namedQueries);
226+
}).withMessageContaining("Mixing of ? parameters and other forms like ?1 is not supported");
227+
}
228+
193229
interface UserRepository extends Repository<User, Integer> {
194230

195231
@Query("something absurd")
@@ -207,6 +243,18 @@ interface UserRepository extends Repository<User, Integer> {
207243
@Query(value = "something absurd", name = "my-query-name")
208244
User annotatedQueryWithQueryAndQueryName();
209245

246+
@Query("SELECT * FROM table WHERE (json_col->'jsonKey')::jsonb \\?\\? :param ")
247+
List<User> customQueryWithQuestionMarksAndNamedParam(String param);
248+
249+
@Query("SELECT * FROM table WHERE (json_col->'jsonKey')::jsonb \\?\\? ? ")
250+
List<User> customQueryWithQuestionMarksAndJdbcStyleParam(String param);
251+
252+
@Query("SELECT * FROM table WHERE (json_col->'jsonKey')::jsonb \\?\\? ?1 ")
253+
List<User> customQueryWithQuestionMarksAndNumberedStyleParam(String param);
254+
255+
@Query("SELECT * FROM table WHERE (json_col->'jsonKey')::jsonb \\?\\? ?1 and other_col = ? ")
256+
List<User> customQueryWithQuestionMarksAndJdbcStyleAndNumberedStyleParam(String param1, String param2);
257+
210258
// This is a named query with Sort parameter, which isn't supported
211259
List<User> customNamedQuery(String firstname, Sort sort);
212260
}

0 commit comments

Comments
 (0)