Skip to content

Commit 687142e

Browse files
mp911deschauder
authored andcommitted
#373 - Use SpelQueryContext for expression query parsing.
We now use SpelQueryContext and SpelExtractor for analysis and rewrite of queries using SpEL. Original pull request: #376.
1 parent 7e69301 commit 687142e

File tree

2 files changed

+33
-75
lines changed

2 files changed

+33
-75
lines changed

src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java

+12-75
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.List;
20-
import java.util.regex.Matcher;
21-
import java.util.regex.Pattern;
2220

23-
import org.springframework.lang.Nullable;
21+
import org.springframework.data.repository.query.SpelQueryContext;
2422

2523
/**
2624
* Query using Spring Expression Language to indicate parameter bindings. Queries using SpEL use {@code :#{…}} to
@@ -32,18 +30,14 @@
3230
*/
3331
class ExpressionQuery {
3432

35-
private static final char CURLY_BRACE_OPEN = '{';
36-
private static final char CURLY_BRACE_CLOSE = '}';
37-
3833
private static final String SYNTHETIC_PARAMETER_TEMPLATE = "__synthetic_%d__";
3934

40-
private static final Pattern EXPRESSION_BINDING_PATTERN = Pattern.compile("[:]#\\{(.*)}");
41-
4235
private final String query;
4336

4437
private final List<ParameterBinding> parameterBindings;
4538

4639
private ExpressionQuery(String query, List<ParameterBinding> parameterBindings) {
40+
4741
this.query = query;
4842
this.parameterBindings = parameterBindings;
4943
}
@@ -57,9 +51,17 @@ private ExpressionQuery(String query, List<ParameterBinding> parameterBindings)
5751
public static ExpressionQuery create(String query) {
5852

5953
List<ParameterBinding> parameterBindings = new ArrayList<>();
60-
String rewritten = transformQueryAndCollectExpressionParametersIntoBindings(query, parameterBindings);
6154

62-
return new ExpressionQuery(rewritten, parameterBindings);
55+
SpelQueryContext queryContext = SpelQueryContext.of((counter, expression) -> {
56+
57+
String parameterName = String.format(SYNTHETIC_PARAMETER_TEMPLATE, counter);
58+
parameterBindings.add(new ParameterBinding(parameterName, expression));
59+
return parameterName;
60+
}, String::concat);
61+
62+
SpelQueryContext.SpelExtractor parsed = queryContext.parse(query);
63+
64+
return new ExpressionQuery(parsed.getQueryString(), parameterBindings);
6365
}
6466

6567
public String getQuery() {
@@ -70,67 +72,6 @@ public List<ParameterBinding> getBindings() {
7072
return parameterBindings;
7173
}
7274

73-
private static String transformQueryAndCollectExpressionParametersIntoBindings(String input,
74-
List<ParameterBinding> bindings) {
75-
76-
StringBuilder result = new StringBuilder();
77-
78-
int startIndex = 0;
79-
int currentPosition = 0;
80-
int parameterIndex = 0;
81-
82-
while (currentPosition < input.length()) {
83-
84-
Matcher matcher = findNextBindingOrExpression(input, currentPosition);
85-
86-
// no expression parameter found
87-
if (matcher == null) {
88-
break;
89-
}
90-
91-
int exprStart = matcher.start();
92-
currentPosition = exprStart;
93-
94-
// eat parameter expression
95-
int curlyBraceOpenCount = 1;
96-
currentPosition += 3;
97-
98-
while (curlyBraceOpenCount > 0 && currentPosition < input.length()) {
99-
switch (input.charAt(currentPosition++)) {
100-
case CURLY_BRACE_OPEN:
101-
curlyBraceOpenCount++;
102-
break;
103-
case CURLY_BRACE_CLOSE:
104-
curlyBraceOpenCount--;
105-
break;
106-
default:
107-
}
108-
}
109-
110-
result.append(input.subSequence(startIndex, exprStart));
111-
112-
String parameterName = String.format(SYNTHETIC_PARAMETER_TEMPLATE, parameterIndex++);
113-
result.append(':').append(parameterName);
114-
115-
bindings.add(ParameterBinding.named(parameterName, matcher.group(1)));
116-
117-
currentPosition = matcher.end();
118-
startIndex = currentPosition;
119-
}
120-
121-
return result.append(input.subSequence(currentPosition, input.length())).toString();
122-
}
123-
124-
@Nullable
125-
private static Matcher findNextBindingOrExpression(String input, int position) {
126-
127-
Matcher matcher = EXPRESSION_BINDING_PATTERN.matcher(input);
128-
if (matcher.find(position)) {
129-
return matcher;
130-
}
131-
132-
return null;
133-
}
13475

13576
@Override
13677
public String toString() {
@@ -153,10 +94,6 @@ private ParameterBinding(String parameterName, String expression) {
15394
this.parameterName = parameterName;
15495
}
15596

156-
static ParameterBinding named(String name, String expression) {
157-
return new ParameterBinding(name, expression);
158-
}
159-
16097
String getExpression() {
16198
return expression;
16299
}

src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java

+21
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.mockito.junit.MockitoJUnitRunner;
2929

3030
import org.springframework.data.domain.Sort;
31+
import org.springframework.data.geo.Point;
3132
import org.springframework.data.projection.ProjectionFactory;
3233
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
3334
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
@@ -237,6 +238,23 @@ public void skipsNonBindableParameters() {
237238
verifyNoMoreInteractions(bindSpec);
238239
}
239240

241+
@Test // gh-373
242+
public void bindsMultipleSpelParametersCorrectly() {
243+
244+
StringBasedR2dbcQuery query = getQueryMethod("queryWithTwoSpelExpressions", Point.class);
245+
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), new Point(1, 2));
246+
247+
BindableQuery stringQuery = query.createQuery(accessor);
248+
249+
assertThat(stringQuery.get())
250+
.isEqualTo("INSERT IGNORE INTO table (x, y) VALUES (:__synthetic_0__, :__synthetic_1__)");
251+
assertThat(stringQuery.bind(bindSpec)).isNotNull();
252+
253+
verify(bindSpec).bind("__synthetic_0__", 1d);
254+
verify(bindSpec).bind("__synthetic_1__", 2d);
255+
verifyNoMoreInteractions(bindSpec);
256+
}
257+
240258
private StringBasedR2dbcQuery getQueryMethod(String name, Class<?>... args) {
241259

242260
Method method = ReflectionUtils.findMethod(SampleRepository.class, name, args);
@@ -282,6 +300,9 @@ private interface SampleRepository extends Repository<Person, String> {
282300

283301
@Query("SELECT * FROM person WHERE lastname = :name")
284302
Person queryWithUnusedParameter(String name, Sort unused);
303+
304+
@Query("INSERT IGNORE INTO table (x, y) VALUES (:#{#point.x}, :#{#point.y})")
305+
Person queryWithTwoSpelExpressions(@Param("point") Point point);
285306
}
286307

287308
static class Person {

0 commit comments

Comments
 (0)