diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java index 1efea4e8031..ee947476692 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java @@ -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 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 parameterList, int totalParameterCount, int escapes, int i, int j, String parameter) { + private static int addNamedParameter(List 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++) { diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java index bf501d04654..32c5fdac2a3 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java @@ -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 headers = new HashMap<>(); + public Foo() { + this.headers.put("id", 1); + } + public Map 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")); + } + } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java index 6efe245ae67..a1b87ffedca 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java @@ -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; diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsUnitTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsUnitTests.java index c64fe8bf833..d604ca7520c 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsUnitTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsUnitTests.java @@ -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";