Skip to content

Commit 6f64cfd

Browse files
committed
Test square brackets with index/key expressions
See gh-27925
1 parent 652781c commit 6f64cfd

File tree

4 files changed

+54
-44
lines changed

4 files changed

+54
-44
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -78,9 +78,9 @@ public abstract class NamedParameterUtils {
7878
* Parse the SQL statement and locate any placeholders or named parameters.
7979
* Named parameters are substituted for a JDBC placeholder.
8080
* @param sql the SQL statement
81-
* @return the parsed statement, represented as ParsedSql instance
81+
* @return the parsed statement, represented as {@link ParsedSql} instance
8282
*/
83-
public static ParsedSql parseSqlStatement(final String sql) {
83+
public static ParsedSql parseSqlStatement(String sql) {
8484
Assert.notNull(sql, "SQL must not be null");
8585

8686
Set<String> namedParameters = new HashSet<>();
@@ -122,17 +122,20 @@ public static ParsedSql parseSqlStatement(final String sql) {
122122
while (statement[j] != '}') {
123123
j++;
124124
if (j >= statement.length) {
125-
throw new InvalidDataAccessApiUsageException("Non-terminated named parameter declaration " +
126-
"at position " + i + " in statement: " + sql);
125+
throw new InvalidDataAccessApiUsageException(
126+
"Non-terminated named parameter declaration at position " + i +
127+
" in statement: " + sql);
127128
}
128129
if (statement[j] == ':' || statement[j] == '{') {
129-
throw new InvalidDataAccessApiUsageException("Parameter name contains invalid character '" +
130-
statement[j] + "' at position " + i + " in statement: " + sql);
130+
throw new InvalidDataAccessApiUsageException(
131+
"Parameter name contains invalid character '" + statement[j] +
132+
"' at position " + i + " in statement: " + sql);
131133
}
132134
}
133135
if (j - i > 2) {
134136
parameter = sql.substring(i + 2, j);
135-
namedParameterCount = addNewNamedParameter(namedParameters, namedParameterCount, parameter);
137+
namedParameterCount = addNewNamedParameter(
138+
namedParameters, namedParameterCount, parameter);
136139
totalParameterCount = addNamedParameter(
137140
parameterList, totalParameterCount, escapes, i, j + 1, parameter);
138141
}
@@ -144,7 +147,8 @@ public static ParsedSql parseSqlStatement(final String sql) {
144147
}
145148
if (j - i > 1) {
146149
parameter = sql.substring(i + 1, j);
147-
namedParameterCount = addNewNamedParameter(namedParameters, namedParameterCount, parameter);
150+
namedParameterCount = addNewNamedParameter(
151+
namedParameters, namedParameterCount, parameter);
148152
totalParameterCount = addNamedParameter(
149153
parameterList, totalParameterCount, escapes, i, j, parameter);
150154
}
@@ -185,8 +189,8 @@ public static ParsedSql parseSqlStatement(final String sql) {
185189
return parsedSql;
186190
}
187191

188-
private static int addNamedParameter(
189-
List<ParameterHolder> parameterList, int totalParameterCount, int escapes, int i, int j, String parameter) {
192+
private static int addNamedParameter(List<ParameterHolder> parameterList,
193+
int totalParameterCount, int escapes, int i, int j, String parameter) {
190194

191195
parameterList.add(new ParameterHolder(parameter, i - escapes, j - escapes));
192196
totalParameterCount++;
@@ -271,6 +275,7 @@ public static String substituteNamedParameters(ParsedSql parsedSql, @Nullable Sq
271275
if (paramNames.isEmpty()) {
272276
return originalSql;
273277
}
278+
274279
StringBuilder actualSql = new StringBuilder(originalSql.length());
275280
int lastIndex = 0;
276281
for (int i = 0; i < paramNames.size(); i++) {

spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -319,4 +319,29 @@ public void parseSqlStatementWithQuotesAndCommentAfter() {
319319
assertThat(psql2.getParameterNames().get(0)).isEqualTo("xxx");
320320
}
321321

322+
@Test // gh-27925
323+
void namedParamMapReference() {
324+
String sql = "insert into foos (id) values (:headers[id])";
325+
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
326+
assertThat(psql.getNamedParameterCount()).isEqualTo(1);
327+
assertThat(psql.getParameterNames()).containsExactly("headers[id]");
328+
329+
class Foo {
330+
final Map<String, Object> headers = new HashMap<>();
331+
public Foo() {
332+
this.headers.put("id", 1);
333+
}
334+
public Map<String, Object> getHeaders() {
335+
return this.headers;
336+
}
337+
}
338+
339+
Foo foo = new Foo();
340+
Object[] params = NamedParameterUtils.buildValueArray(psql,
341+
new BeanPropertySqlParameterSource(foo), null);
342+
343+
assertThat(params[0]).isInstanceOf(SqlParameterValue.class);
344+
assertThat(((SqlParameterValue) params[0]).getValue()).isEqualTo(foo.getHeaders().get("id"));
345+
}
346+
322347
}

spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@
3737
/**
3838
* Helper methods for named parameter parsing.
3939
*
40-
* <p>Only intended for internal use within Spring's R2DBC
41-
* framework.
40+
* <p>Only intended for internal use within Spring's R2DBC framework.
4241
*
4342
* <p>References to the same parameter name are substituted with
4443
* the same bind marker placeholder if a {@link BindMarkersFactory} uses
@@ -293,7 +292,6 @@ public static PreparedOperation<String> substituteNamedParameters(ParsedSql pars
293292
if (paramSource.hasValue(paramName)) {
294293
Object value = paramSource.getValue(paramName);
295294
if (value instanceof Collection) {
296-
297295
Iterator<?> entryIter = ((Collection<?>) value).iterator();
298296
int k = 0;
299297
int counter = 0;

spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsUnitTests.java

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@ public class NamedParameterUtilsUnitTests {
4343

4444
private final BindMarkersFactory BIND_MARKERS = BindMarkersFactory.indexed("$", 1);
4545

46+
4647
@Test
4748
public void shouldParseSql() {
4849
String sql = "xxx :a yyyy :b :c :a zzzzz";
@@ -145,7 +146,6 @@ public void parseSqlStatementWithPostgresContainedOperator() {
145146
String sql = "select 'first name' from artists where info->'stat'->'albums' = ?? :album and '[\"1\",\"2\",\"3\"]'::jsonb ?? '4'";
146147

147148
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
148-
149149
assertThat(parsedSql.getTotalParameterCount()).isEqualTo(1);
150150
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
151151
}
@@ -156,7 +156,6 @@ public void parseSqlStatementWithPostgresAnyArrayStringsExistsOperator() {
156156
String sql = "select '[\"3\", \"11\"]'::jsonb ?| '{1,3,11,12,17}'::text[]";
157157

158158
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
159-
160159
assertThat(parsedSql.getTotalParameterCount()).isEqualTo(0);
161160
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
162161
}
@@ -177,7 +176,6 @@ public void parseSqlStatementWithEscapedColon() {
177176
String sql = "select '0\\:0' as a, foo from bar where baz < DATE(:p1 23\\:59\\:59) and baz = :p2";
178177

179178
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
180-
181179
assertThat(parsedSql.getParameterNames()).containsExactly("p1", "p2");
182180
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
183181
}
@@ -198,7 +196,6 @@ public void parseSqlStatementWithEmptyBracketsOrBracketsInQuotes() {
198196
String sql = "select foo from bar where baz = b:{}z";
199197

200198
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
201-
202199
assertThat(parsedSql.getParameterNames()).isEmpty();
203200
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
204201

@@ -225,13 +222,11 @@ public void parseSqlStatementWithLogicalAnd() {
225222
String expectedSql = "xxx & yyyy";
226223

227224
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(expectedSql);
228-
229225
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
230226
}
231227

232228
@Test
233229
public void substituteNamedParametersWithLogicalAnd() {
234-
235230
String expectedSql = "xxx & yyyy";
236231

237232
assertThat(expand(expectedSql)).isEqualTo(expectedSql);
@@ -249,7 +244,6 @@ public void parseSqlStatementWithQuotedSingleQuote() {
249244
String sql = "SELECT ':foo'':doo', :xxx FROM DUAL";
250245

251246
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
252-
253247
assertThat(psql.getTotalParameterCount()).isEqualTo(1);
254248
assertThat(psql.getParameterNames()).containsExactly("xxx");
255249
}
@@ -259,7 +253,6 @@ public void parseSqlStatementWithQuotesAndCommentBefore() {
259253
String sql = "SELECT /*:doo*/':foo', :xxx FROM DUAL";
260254

261255
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
262-
263256
assertThat(psql.getTotalParameterCount()).isEqualTo(1);
264257
assertThat(psql.getParameterNames()).containsExactly("xxx");
265258
}
@@ -269,18 +262,23 @@ public void parseSqlStatementWithQuotesAndCommentAfter() {
269262
String sql2 = "SELECT ':foo'/*:doo*/, :xxx FROM DUAL";
270263

271264
ParsedSql psql2 = NamedParameterUtils.parseSqlStatement(sql2);
272-
273265
assertThat(psql2.getTotalParameterCount()).isEqualTo(1);
274266
assertThat(psql2.getParameterNames()).containsExactly("xxx");
275267
}
276268

269+
@Test // gh-27925
270+
void namedParamMapReference() {
271+
String sql = "insert into foos (id) values (:headers[id])";
272+
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
273+
assertThat(psql.getNamedParameterCount()).isEqualTo(1);
274+
assertThat(psql.getParameterNames()).containsExactly("headers[id]");
275+
}
276+
277277
@Test
278278
public void shouldAllowParsingMultipleUseOfParameter() {
279-
280279
String sql = "SELECT * FROM person where name = :id or lastname = :id";
281280

282281
ParsedSql parsed = NamedParameterUtils.parseSqlStatement(sql);
283-
284282
assertThat(parsed.getTotalParameterCount()).isEqualTo(2);
285283
assertThat(parsed.getNamedParameterCount()).isEqualTo(1);
286284
assertThat(parsed.getParameterNames()).containsExactly("id", "id");
@@ -300,23 +298,19 @@ sql, factory, new MapBindParameterSource(
300298
"SELECT * FROM person where name = $0 or lastname = $0");
301299

302300
operation.bindTo(new BindTarget() {
303-
304301
@Override
305302
public void bind(String identifier, Object value) {
306303
throw new UnsupportedOperationException();
307304
}
308-
309305
@Override
310306
public void bind(int index, Object value) {
311307
assertThat(index).isEqualTo(0);
312308
assertThat(value).isEqualTo("foo");
313309
}
314-
315310
@Override
316311
public void bindNull(String identifier, Class<?> type) {
317312
throw new UnsupportedOperationException();
318313
}
319-
320314
@Override
321315
public void bindNull(int index, Class<?> type) {
322316
throw new UnsupportedOperationException();
@@ -340,25 +334,20 @@ sql, factory, new MapBindParameterSource(Collections.singletonMap("ids",
340334
"SELECT * FROM person where name IN ($0, $1, $2) or lastname IN ($0, $1, $2)");
341335

342336
operation.bindTo(new BindTarget() {
343-
344337
@Override
345338
public void bind(String identifier, Object value) {
346339
throw new UnsupportedOperationException();
347340
}
348-
349341
@Override
350342
public void bind(int index, Object value) {
351343
assertThat(index).isIn(0, 1, 2);
352344
assertThat(value).isIn("foo", "bar", "baz");
353-
354345
bindings.add(index, value);
355346
}
356-
357347
@Override
358348
public void bindNull(String identifier, Class<?> type) {
359349
throw new UnsupportedOperationException();
360350
}
361-
362351
@Override
363352
public void bindNull(int index, Class<?> type) {
364353
throw new UnsupportedOperationException();
@@ -386,22 +375,18 @@ sql, factory, new MapBindParameterSource(
386375
Map<Integer, Object> bindValues = new LinkedHashMap<>();
387376

388377
operation.bindTo(new BindTarget() {
389-
390378
@Override
391379
public void bind(String identifier, Object value) {
392380
throw new UnsupportedOperationException();
393381
}
394-
395382
@Override
396383
public void bind(int index, Object value) {
397384
bindValues.put(index, value);
398385
}
399-
400386
@Override
401387
public void bindNull(String identifier, Class<?> type) {
402388
throw new UnsupportedOperationException();
403389
}
404-
405390
@Override
406391
public void bindNull(int index, Class<?> type) {
407392
throw new UnsupportedOperationException();
@@ -425,22 +410,18 @@ sql, factory, new MapBindParameterSource(
425410
"SELECT * FROM person where name = $0 or lastname = $0");
426411

427412
operation.bindTo(new BindTarget() {
428-
429413
@Override
430414
public void bind(String identifier, Object value) {
431415
throw new UnsupportedOperationException();
432416
}
433-
434417
@Override
435418
public void bind(int index, Object value) {
436419
throw new UnsupportedOperationException();
437420
}
438-
439421
@Override
440422
public void bindNull(String identifier, Class<?> type) {
441423
throw new UnsupportedOperationException();
442424
}
443-
444425
@Override
445426
public void bindNull(int index, Class<?> type) {
446427
assertThat(index).isEqualTo(0);
@@ -449,6 +430,7 @@ public void bindNull(int index, Class<?> type) {
449430
});
450431
}
451432

433+
452434
private String expand(ParsedSql sql) {
453435
return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS,
454436
new MapBindParameterSource()).toQuery();

0 commit comments

Comments
 (0)