diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BindParameterNameSanitizer.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BindParameterNameSanitizer.java new file mode 100644 index 000000000..c84658c48 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BindParameterNameSanitizer.java @@ -0,0 +1,37 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.jdbc.core.convert; + +import java.util.regex.Pattern; + +/** + * Sanitizes the name of bind parameters, so they don't contain any illegal characters. + * + * @author Jens Schauder + * + * @since 3.0 + */ +enum BindParameterNameSanitizer { + INSTANCE; + + private static final Pattern parameterPattern = Pattern.compile("\\W"); + + String sanitize(String rawName) { + + return parameterPattern.matcher(rawName).replaceAll(""); + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java index 235133f69..34a24cd26 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java @@ -65,7 +65,6 @@ class SqlGenerator { static final SqlIdentifier IDS_SQL_PARAMETER = SqlIdentifier.unquoted("ids"); static final SqlIdentifier ROOT_ID_PARAMETER = SqlIdentifier.unquoted("rootId"); - private static final Pattern parameterPattern = Pattern.compile("\\W"); private final RelationalPersistentEntity entity; private final MappingContext, RelationalPersistentProperty> mappingContext; private final RenderContext renderContext; @@ -158,7 +157,7 @@ class SqlGenerator { } private BindMarker getBindMarker(SqlIdentifier columnName) { - return SQL.bindMarker(":" + parameterPattern.matcher(renderReference(columnName)).replaceAll("")); + return SQL.bindMarker(":" + BindParameterNameSanitizer.INSTANCE.sanitize(renderReference(columnName))); } /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java index fc89db767..4398e877c 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java @@ -68,7 +68,7 @@ class SqlIdentifierParameterSource extends AbstractSqlParameterSource { void addValue(SqlIdentifier identifier, Object value, int sqlType) { identifiers.add(identifier); - String name = identifier.getReference(identifierProcessing); + String name = BindParameterNameSanitizer.INSTANCE.sanitize(identifier.getReference(identifierProcessing)); namesToValues.put(name, value); registerSqlType(name, sqlType); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java index 103b92b23..d87e607fa 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java @@ -95,8 +95,8 @@ public class SqlParametersFactory { */ SqlIdentifierParameterSource forUpdate(T instance, Class domainType) { - return getParameterSource(instance, getRequiredPersistentEntity(domainType), "", RelationalPersistentProperty::isInsertOnly, - dialect.getIdentifierProcessing()); + return getParameterSource(instance, getRequiredPersistentEntity(domainType), "", + RelationalPersistentProperty::isInsertOnly, dialect.getIdentifierProcessing()); } /** diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryTest.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryTest.java index 292b822c8..25d37ca99 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryTest.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryTest.java @@ -38,6 +38,7 @@ import org.springframework.data.convert.WritingConverter; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.conversion.IdValueSource; import org.springframework.data.relational.core.dialect.AnsiDialect; +import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.jdbc.core.JdbcOperations; @@ -147,6 +148,21 @@ class SqlParametersFactoryTest { assertThat(sqlParameterSource.getValue("value")).isEqualTo(value); } + @Test // GH-1405 + void parameterNamesGetSanitized() { + + WithIllegalCharacters entity = new WithIllegalCharacters(23L,"aValue"); + + SqlIdentifierParameterSource sqlParameterSource = sqlParametersFactory.forInsert(entity, WithIllegalCharacters.class, + Identifier.empty(), IdValueSource.PROVIDED); + + assertThat(sqlParameterSource.getValue("id")).isEqualTo(23L); + assertThat(sqlParameterSource.getValue("value")).isEqualTo("aValue"); + + assertThat(sqlParameterSource.getValue("i.d")).isNull(); + assertThat(sqlParameterSource.getValue("val&ue")).isNull(); + } + @WritingConverter enum IdValueToStringConverter implements Converter { @@ -212,6 +228,16 @@ class SqlParametersFactoryTest { @Id private final Long id; } + @AllArgsConstructor + private static class WithIllegalCharacters { + + @Column("i.d") + @Id Long id; + + @Column("val&ue") + String value; + } + private SqlParametersFactory createSqlParametersFactoryWithConverters(List converters) { BasicJdbcConverter converter = new BasicJdbcConverter(context, relationResolver, diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 9c9c8646f..567eb5732 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -104,6 +104,7 @@ import lombok.Data; * @author Chirag Tailor * @author Diego Krupitza * @author Christopher Klein + * @author Mikhail Polivakha */ @Transactional @TestExecutionListeners(value = AssumeFeatureTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS) @@ -1242,8 +1243,9 @@ public class JdbcRepositoryIntegrationTests { assertThat(match.get().getName()).contains(two.getName()); } - @Test + @Test // GH-1405 void withDelimitedColumnTest() { + WithDelimitedColumn withDelimitedColumn = new WithDelimitedColumn(); withDelimitedColumn.setType("TYPICAL"); withDelimitedColumn.setIdentifier("UR-123");