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 d1cb75e02d..f0c97dc573 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-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -142,16 +142,25 @@ public abstract class NamedParameterUtils { j++; } else { - while (j < statement.length && !isParameterSeparator(statement[j])) { + boolean paramWithSquareBrackets = false; + while (j < statement.length) { + c = statement[j]; + if (isParameterSeparator(c)) { + break; + } + if (c == '[') { + paramWithSquareBrackets = true; + } + else if (c == ']') { + if (!paramWithSquareBrackets) { + break; + } + paramWithSquareBrackets = false; + } j++; } if (j - i > 1) { parameter = sql.substring(i + 1, j); - if (j < statement.length && statement[j] == ']' && parameter.contains("[")) { - // preserve end bracket for index/key - j++; - parameter = sql.substring(i + 1, j); - } namedParameterCount = addNewNamedParameter( namedParameters, namedParameterCount, parameter); totalParameterCount = addNamedParameter( 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 23c4e6e838..1fcf399bf6 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-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -330,6 +330,18 @@ public class NamedParameterUtilsTests { assertThat(sqlToUse).isEqualTo("SELECT ARRAY[?]"); } + @Test // gh-31596 + void paramNameWithNestedSquareBrackets() { + String sql = "insert into GeneratedAlways (id, first_name, last_name) values " + + "(:records[0].id, :records[0].firstName, :records[0].lastName), " + + "(:records[1].id, :records[1].firstName, :records[1].lastName)"; + + ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); + assertThat(parsedSql.getParameterNames()).containsOnly( + "records[0].id", "records[0].firstName", "records[0].lastName", + "records[1].id", "records[1].firstName", "records[1].lastName"); + } + @Test // gh-27925 void namedParamMapReference() { String sql = "insert into foos (id) values (:headers[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 b1b80ea306..306de12f87 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 @@ -67,7 +67,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. @@ -83,12 +83,12 @@ abstract class NamedParameterUtils { // ------------------------------------------------------------------------- - // Core methods used by NamedParameterSupport. + // Core methods used by NamedParameterExpander // ------------------------------------------------------------------------- /** * Parse the SQL statement and locate any placeholders or named parameters. - * Named parameters are substituted for a R2DBC placeholder. + * Named parameters are substituted for an R2DBC placeholder. * @param sql the SQL statement * @return the parsed statement, represented as {@link ParsedSql} instance */ @@ -154,16 +154,25 @@ abstract class NamedParameterUtils { j++; } else { - while (j < statement.length && !isParameterSeparator(statement[j])) { + boolean paramWithSquareBrackets = false; + while (j < statement.length) { + c = statement[j]; + if (isParameterSeparator(c)) { + break; + } + if (c == '[') { + paramWithSquareBrackets = true; + } + else if (c == ']') { + if (!paramWithSquareBrackets) { + break; + } + paramWithSquareBrackets = false; + } j++; } if (j - i > 1) { parameter = sql.substring(i + 1, j); - if (j < statement.length && statement[j] == ']' && parameter.contains("[")) { - // preserve end bracket for index/key - j++; - parameter = sql.substring(i + 1, j); - } namedParameterCount = addNewNamedParameter( namedParameters, namedParameterCount, parameter); totalParameterCount = addNamedParameter( @@ -261,7 +270,7 @@ abstract class NamedParameterUtils { /** * Parse the SQL statement and locate any placeholders or named parameters. Named - * parameters are substituted for a R2DBC placeholder, and any select list is expanded + * parameters are substituted for an R2DBC placeholder, and any select list is expanded * to the required number of placeholders. Select lists may contain an array of objects, * and in that case the placeholders will be grouped and enclosed with parentheses. * This allows for the use of "expression lists" in the SQL statement like: