Preserve square brackets for index/key expressions

Closes gh-27925
This commit is contained in:
Juergen Hoeller 2022-10-18 22:39:53 +02:00
parent 327eec09ae
commit d4fac82d68
4 changed files with 58 additions and 15 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -55,7 +55,7 @@ public abstract class NamedParameterUtils {
* Set of characters that qualify as parameter separators,
* indicating that a parameter name in an SQL String has ended.
*/
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^[]";
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^]";
/**
* An index with separator flags per character code.
@ -78,9 +78,9 @@ public abstract class NamedParameterUtils {
* Parse the SQL statement and locate any placeholders or named parameters.
* Named parameters are substituted for a JDBC placeholder.
* @param sql the SQL statement
* @return the parsed statement, represented as ParsedSql instance
* @return the parsed statement, represented as {@link ParsedSql} instance
*/
public static ParsedSql parseSqlStatement(final String sql) {
public static ParsedSql parseSqlStatement(String sql) {
Assert.notNull(sql, "SQL must not be null");
Set<String> namedParameters = new HashSet<>();
@ -122,17 +122,20 @@ public abstract class NamedParameterUtils {
while (statement[j] != '}') {
j++;
if (j >= statement.length) {
throw new InvalidDataAccessApiUsageException("Non-terminated named parameter declaration " +
"at position " + i + " in statement: " + sql);
throw new InvalidDataAccessApiUsageException(
"Non-terminated named parameter declaration at position " + i +
" in statement: " + sql);
}
if (statement[j] == ':' || statement[j] == '{') {
throw new InvalidDataAccessApiUsageException("Parameter name contains invalid character '" +
statement[j] + "' at position " + i + " in statement: " + sql);
throw new InvalidDataAccessApiUsageException(
"Parameter name contains invalid character '" + statement[j] +
"' at position " + i + " in statement: " + sql);
}
}
if (j - i > 2) {
parameter = sql.substring(i + 2, j);
namedParameterCount = addNewNamedParameter(namedParameters, namedParameterCount, parameter);
namedParameterCount = addNewNamedParameter(
namedParameters, namedParameterCount, parameter);
totalParameterCount = addNamedParameter(
parameterList, totalParameterCount, escapes, i, j + 1, parameter);
}
@ -144,7 +147,11 @@ public abstract class NamedParameterUtils {
}
if (j - i > 1) {
parameter = sql.substring(i + 1, j);
namedParameterCount = addNewNamedParameter(namedParameters, namedParameterCount, parameter);
if (parameter.contains("[")) {
parameter += "]"; // preserve end bracket for index/key
}
namedParameterCount = addNewNamedParameter(
namedParameters, namedParameterCount, parameter);
totalParameterCount = addNamedParameter(
parameterList, totalParameterCount, escapes, i, j, parameter);
}
@ -185,8 +192,8 @@ public abstract class NamedParameterUtils {
return parsedSql;
}
private static int addNamedParameter(
List<ParameterHolder> parameterList, int totalParameterCount, int escapes, int i, int j, String parameter) {
private static int addNamedParameter(List<ParameterHolder> parameterList,
int totalParameterCount, int escapes, int i, int j, String parameter) {
parameterList.add(new ParameterHolder(parameter, i - escapes, j - escapes));
totalParameterCount++;
@ -271,6 +278,7 @@ public abstract class NamedParameterUtils {
if (paramNames.isEmpty()) {
return originalSql;
}
StringBuilder actualSql = new StringBuilder(originalSql.length());
int lastIndex = 0;
for (int i = 0; i < paramNames.size(); i++) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -327,4 +327,29 @@ public class NamedParameterUtilsTests {
assertThat(psql.getParameterNames()).containsExactly("ext");
}
@Test // gh-27925
void namedParamMapReference() {
String sql = "insert into foos (id) values (:headers[id])";
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
assertThat(psql.getNamedParameterCount()).isEqualTo(1);
assertThat(psql.getParameterNames()).containsExactly("headers[id]");
class Foo {
final Map<String, Object> headers = new HashMap<>();
public Foo() {
this.headers.put("id", 1);
}
public Map<String, Object> getHeaders() {
return this.headers;
}
}
Foo foo = new Foo();
Object[] params = NamedParameterUtils.buildValueArray(psql,
new BeanPropertySqlParameterSource(foo), null);
assertThat(params[0]).isInstanceOf(SqlParameterValue.class);
assertThat(((SqlParameterValue) params[0]).getValue()).isEqualTo(foo.getHeaders().get("id"));
}
}

View File

@ -68,7 +68,7 @@ abstract class NamedParameterUtils {
* Set of characters that qualify as parameter separators,
* indicating that a parameter name in an SQL String has ended.
*/
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^[]";
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^]";
/**
* An index with separator flags per character code.
@ -160,6 +160,9 @@ abstract class NamedParameterUtils {
}
if (j - i > 1) {
parameter = sql.substring(i + 1, j);
if (parameter.contains("[")) {
parameter += "]"; // preserve end bracket for index/key
}
namedParameterCount = addNewNamedParameter(
namedParameters, namedParameterCount, parameter);
totalParameterCount = addNamedParameter(
@ -295,7 +298,6 @@ abstract class NamedParameterUtils {
if (paramSource.hasValue(paramName)) {
Parameter parameter = paramSource.getValue(paramName);
if (parameter.getValue() instanceof Collection<?> c) {
Iterator<?> entryIter = c.iterator();
int k = 0;
int counter = 0;

View File

@ -277,6 +277,14 @@ public class NamedParameterUtilsUnitTests {
assertThat(psql.getParameterNames()).containsExactly("ext");
}
@Test // gh-27925
void namedParamMapReference() {
String sql = "insert into foos (id) values (:headers[id])";
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
assertThat(psql.getNamedParameterCount()).isEqualTo(1);
assertThat(psql.getParameterNames()).containsExactly("headers[id]");
}
@Test
public void shouldAllowParsingMultipleUseOfParameter() {
String sql = "SELECT * FROM person where name = :id or lastname = :id";