From 4a51f68876b1528c402f6ef7e861ea76c86dee8f Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 3 Jun 2025 16:53:04 +0200 Subject: [PATCH] Avoid capturing `?&` and `?|` as bind parameter markers. We now exclude `?&` and `?|` from being matched as JDBC-style parameter bind marker. Closes #3907 --- .../jpa/repository/query/StringQuery.java | 13 +++++---- .../query/StringQueryUnitTests.java | 29 ++++++++++++++++++- 2 files changed, 35 insertions(+), 7 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 4e794dee5..3e5c7fe73 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 @@ -143,11 +143,10 @@ class StringQuery implements DeclaredQuery { for (ParameterBinding binding : bindings) { Predicate identifier = binding::bindsTo; - Predicate notCompatible = Predicate.not(binding::isCompatibleWith); + Predicate notCompatible = Predicate.not(binding::isCompatibleWith); - // replace incompatible bindings - if ( derivedBindings.removeIf( - it -> identifier.test(it) && notCompatible.test(it))) { + // replace incompatible bindings + if (derivedBindings.removeIf(it -> identifier.test(it) && notCompatible.test(it))) { derivedBindings.add(binding); } } @@ -206,7 +205,7 @@ class StringQuery implements DeclaredQuery { INSTANCE; private static final String EXPRESSION_PARAMETER_PREFIX = "__$synthetic$__"; - public static final String POSITIONAL_OR_INDEXED_PARAMETER = "\\?(\\d*+(?![#\\w]))"; + public static final String POSITIONAL_OR_INDEXED_PARAMETER = "\\?(\\d*+(?![\\&\\|#\\w]))"; // .....................................................................^ not followed by a hash or a letter. // .................................................................^ zero or more digits. // .............................................................^ start with a question mark. @@ -295,7 +294,9 @@ class StringQuery implements DeclaredQuery { Integer parameterIndex = getParameterIndex(parameterIndexString); String match = matcher.group(0); - if (JDBC_STYLE_PARAM.matcher(match).find()) { + Matcher jdbcStyleMatcher = JDBC_STYLE_PARAM.matcher(match); + + if (jdbcStyleMatcher.find()) { queryMeta.usesJdbcStyleParameters = true; } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java index e612e971a..05c8cb3a4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java @@ -236,6 +236,21 @@ class StringQueryUnitTests { assertThat(bindings).hasSize(3); } + @Test // GH-3907 + void rewritesPositionalLikeToUniqueParametersIfNecessaryUsingPostgresJsonbOperator() { + + StringQuery query = new StringQuery( + "select '[\"x\", \"c\"]'::jsonb ?| '[\"x\", \"c\"]'::jsonb from User u where u.firstname like %?1 or u.firstname like ?1% or u.firstname = ?1", + true); + + assertThat(query.hasParameterBindings()).isTrue(); + assertThat(query.getQueryString()).isEqualTo( + "select '[\"x\", \"c\"]'::jsonb ?| '[\"x\", \"c\"]'::jsonb from User u where u.firstname like ?1 or u.firstname like ?2 or u.firstname = ?3"); + + List bindings = query.getParameterBindings(); + assertThat(bindings).hasSize(3); + } + @Test // GH-3041 void reusesNamedLikeBindingsWherePossible() { @@ -494,7 +509,6 @@ class StringQueryUnitTests { assertThat(bindings).hasSize(1); assertPositionalBinding(ParameterBinding.class, 1, bindings.get(0)); - } @Test // DATAJPA-473 @@ -590,6 +604,19 @@ class StringQueryUnitTests { .isEqualTo("select a from A a where a.b LIKE :__$synthetic$__1 and a.c LIKE :__$synthetic$__2"); } + @Test // GH-3907 + void considersOnlyDedicatedPositionalBindMarkersAsSuch() { + + StringQuery query = new StringQuery( + "select '[\"x\", \"c\"]'::jsonb ?| array[?1]::text[] FROM foo WHERE foo BETWEEN ?1 and ?2", true); + + assertThat(query.getParameterBindings()).hasSize(2); + + query = new StringQuery("select '[\"x\", \"c\"]'::jsonb ?& array[:foo]::text[] FROM foo WHERE foo = :bar", true); + + assertThat(query.getParameterBindings()).hasSize(2); + } + @Test // DATAJPA-712, GH-3619 void shouldReplaceAllPositionExpressionParametersWithInClause() {