Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-381-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data Relational Parent</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-jdbc-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-381-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-jdbc</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-381-SNAPSHOT</version>

<name>Spring Data JDBC</name>
<description>Spring Data module for JDBC repositories.</description>
Expand All @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-381-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.SqlUtils;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
Expand Down Expand Up @@ -119,7 +120,7 @@ public <T> Object insert(T instance, Class<T> domainType, Identifier identifier)

operations.update( //
sql(domainType).getInsert(new HashSet<>(Arrays.asList(parameterSource.getParameterNames()))), //
parameterSource, //
sanitize(parameterSource), //
holder //
);

Expand All @@ -136,7 +137,7 @@ public <S> boolean update(S instance, Class<S> domainType) {
RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);

return operations.update(sql(domainType).getUpdate(),
getParameterSource(instance, persistentEntity, "", Predicates.includeAll())) != 0;
sanitize(getParameterSource(instance, persistentEntity, "", Predicates.includeAll()))) != 0;
}

/*
Expand All @@ -147,7 +148,7 @@ public <S> boolean update(S instance, Class<S> domainType) {
public void delete(Object id, Class<?> domainType) {

String deleteByIdSql = sql(domainType).getDeleteById();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
MapSqlParameterSource parameter = sanitize(createIdParameterSource(id, domainType));

operations.update(deleteByIdSql, parameter);
}
Expand Down Expand Up @@ -217,7 +218,7 @@ public <T> T findById(Object id, Class<T> domainType) {
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);

try {
return operations.queryForObject(findOneSql, parameter, (RowMapper<T>) getEntityRowMapper(domainType));
return operations.queryForObject(findOneSql, sanitize(parameter), (RowMapper<T>) getEntityRowMapper(domainType));
} catch (EmptyResultDataAccessException e) {
return null;
}
Expand Down Expand Up @@ -248,7 +249,7 @@ public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {

String findAllInListSql = sql(domainType).getFindAllInList();

return operations.query(findAllInListSql, parameterSource, (RowMapper<T>) getEntityRowMapper(domainType));
return operations.query(findAllInListSql, sanitize(parameterSource), (RowMapper<T>) getEntityRowMapper(domainType));
}

/*
Expand All @@ -274,7 +275,7 @@ public Iterable<Object> findAllByPath(Identifier identifier,
RowMapper<?> rowMapper = path.isMap() ? this.getMapEntityRowMapper(path, identifier)
: this.getEntityRowMapper(path, identifier);

return operations.query(findAllByProperty, parameters, (RowMapper<Object>) rowMapper);
return operations.query(findAllByProperty, sanitize( parameters), (RowMapper<Object>) rowMapper);
}

/*
Expand Down Expand Up @@ -302,7 +303,7 @@ public <T> boolean existsById(Object id, Class<T> domainType) {
String existsSql = sql(domainType).getExists();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);

Boolean result = operations.queryForObject(existsSql, parameter, Boolean.class);
Boolean result = operations.queryForObject(existsSql,sanitize( parameter), Boolean.class);
Assert.state(result != null, "The result of an exists query must not be null");

return result;
Expand Down Expand Up @@ -344,6 +345,24 @@ private <S, T> MapSqlParameterSource getParameterSource(S instance, RelationalPe
return parameters;
}

private MapSqlParameterSource sanitize(MapSqlParameterSource parameterSource) {

MapSqlParameterSource sanitized = new MapSqlParameterSource();

for (String parameterName : parameterSource.getParameterNames()) {

String sanitizedName = SqlUtils.sanitizeName(parameterName);

sanitized.addValue( //
sanitizedName, //
parameterSource.getValue(parameterName), //
parameterSource.getSqlType(parameterName), //
parameterSource.getTypeName(parameterName)); //
}

return sanitized;
}

@Nullable
@SuppressWarnings("unchecked")
private <S, ID> ID getIdValueOrNull(S instance, RelationalPersistentEntity<S> persistentEntity) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private static Condition getSubselectCondition(PersistentPropertyPathExtension p
}

private static BindMarker getBindMarker(String columnName) {
return SQL.bindMarker(":" + parameterPattern.matcher(columnName).replaceAll(""));
return SQL.bindMarker(":" + SqlUtils.sanitizeName(columnName));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
Expand Down Expand Up @@ -597,6 +596,26 @@ public void shouldDeleteChainOfMapsWithoutIds() {
});
}

@Test // DATAJDBC-381
@IfProfileValue(name = "current.database", value = "mysql")
public void saveAndLoadEntityWithKeywordAsColumnName() {

WithKeywordColumn entity = new WithKeywordColumn();
entity.virtual = "some value";

WithKeywordColumn saved = template.save(entity);

WithKeywordColumn reloaded = template.findById(saved.id, WithKeywordColumn.class);

assertThat(reloaded.virtual).isEqualTo("some value");

reloaded.virtual = "other value";

template.save(reloaded);

template.deleteById(reloaded.id, WithKeywordColumn.class);
}

private static NoIdMapChain4 createNoIdMapTree() {

NoIdMapChain4 chain4 = new NoIdMapChain4();
Expand Down Expand Up @@ -870,4 +889,12 @@ static class NoIdMapChain4 {
String fourValue;
Map<String, NoIdMapChain3> chain3 = new HashMap<>();
}

@Data
static class WithKeywordColumn {

@Id private Long id;

@Column("`virtual`") private String virtual;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public void getInsertForQuotedColumnName() {
String insert = sqlGenerator.getInsert(emptySet());

assertThat(insert)
.isEqualTo("INSERT INTO entity_with_quoted_column_name " + "(\"test_@123\") " + "VALUES (:test_123)");
.isEqualTo("INSERT INTO entity_with_quoted_column_name " + "(\"test_@123\") " + "VALUES (:test_123_)");
}

@Test // DATAJDBC-266
Expand Down Expand Up @@ -291,8 +291,8 @@ public void getUpdateForQuotedColumnName() {

String update = sqlGenerator.getUpdate();

assertThat(update).isEqualTo("UPDATE entity_with_quoted_column_name " + "SET \"test_@123\" = :test_123 "
+ "WHERE entity_with_quoted_column_name.\"test_@id\" = :test_id");
assertThat(update).isEqualTo("UPDATE entity_with_quoted_column_name " + "SET \"test_@123\" = :test_123_ "
+ "WHERE entity_with_quoted_column_name.\"test_@id\" = :test_id_");
}

@Test // DATAJDBC-324
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
import org.springframework.test.annotation.ProfileValueSource;

/**
* This {@link ProfileValueSource} offers a single set of keys {@code current.database.is.not.<database>} where
* This {@link ProfileValueSource} offers a set of keys {@code current.database.is.not.<database>} where
* {@code <database> } is a database as used in active profiles to enable integration tests to run with a certain
* database. The value returned for these keys is {@code "true"} or {@code "false"} depending on if the database is
* actually the one currently used by integration tests.
* actually the one currently used by integration tests. Additionally it offers the key {@code current.database} which
* holds the database value.
*
* @author Jens Schauder
*/
Expand All @@ -37,10 +38,14 @@ public class DatabaseProfileValueSource implements ProfileValueSource {
@Override
public String get(String key) {

if (!key.startsWith("current.database.is.not.")) {
return null;
if (key.startsWith("current.database.is.not.")) {
return Boolean.toString(!key.endsWith(currentDatabase)).toLowerCase();
}

return Boolean.toString(!key.endsWith(currentDatabase)).toLowerCase();
if (key.startsWith("current.database")) {
return currentDatabase;
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,10 @@ CREATE TABLE NO_ID_MAP_CHAIN0
NO_ID_MAP_CHAIN3_KEY,
NO_ID_MAP_CHAIN2_KEY
)
);

CREATE TABLE WITH_KEYWORD_COLUMN
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
`VIRTUAL` VARCHAR(200)
);
4 changes: 2 additions & 2 deletions spring-data-relational/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-relational</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-381-SNAPSHOT</version>

<name>Spring Data Relational</name>
<description>Spring Data Relational support</description>

<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-381-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.relational.core.sql.SqlUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

Expand Down Expand Up @@ -189,7 +190,7 @@ public String getColumnName() {
*/
public String getColumnAlias() {

return prefixWithTableAlias(getColumnName());
return SqlUtils.sanitizeName(prefixWithTableAlias(getColumnName()));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2019 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.relational.core.sql;

import lombok.experimental.UtilityClass;

import java.util.regex.Pattern;

import org.springframework.util.StringUtils;

/**
* Utility class for SQL related functions.
*
* @author Jens Schauder
* @since 1.1
*/
@UtilityClass
public class SqlUtils {

private static final Pattern parameterPattern = Pattern.compile("\\W");

/**
* Sanitizes a name so that the result maybe used for example as a bind parameter name or an alias. This is done by
* removing all special characters and if any where present appending and '_' in order to avoid resulting with a
* keyword.
*
* @param name as used for a table or a column. It may contain special characters like quotes.
*/
public String sanitizeName(String name) {

if (StringUtils.isEmpty(name)) {
return name;
}

String sanitized = parameterPattern.matcher(name).replaceAll("");
return sanitized.equals(name) ? sanitized : sanitized + "_";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2019 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.relational.core.sql;

import static org.assertj.core.api.Assertions.*;

import org.junit.Test;

/**
* Unit tests for SqlUtils.
*
* @author Jens Schauder
*/
public class SqlUtilsUnitTests {

@Test
public void simpleName() {
assertThat(SqlUtils.sanitizeName("simple")).isEqualTo("simple");
}

@Test
public void specialCharactersGetTrimmedName() {
assertThat(SqlUtils.sanitizeName("`this might be a keyword`")).isEqualTo("thismightbeakeyword_");
}
}