Browse Source

Propagate SqlType from Spel expression evaluation.

Signed-off-by: mipo256 <mikhailpolivakha@gmail.com>

Commit message edited by Jens Schauder

Original pull request #2079
Closes #2007
pull/2076/merge
mipo256 6 months ago committed by Jens Schauder
parent
commit
01f10dd8c1
No known key found for this signature in database
GPG Key ID: 2BE5D185CD2A1CE6
  1. 36
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java
  2. 61
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
  3. 92
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java
  4. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql
  5. 5
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql
  6. 5
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql
  7. 5
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql
  8. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql
  9. 5
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql
  10. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql
  11. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql

36
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java

@ -31,6 +31,7 @@ import org.springframework.beans.BeanInstantiationException; @@ -31,6 +31,7 @@ import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.data.expression.ValueEvaluationContext;
import org.springframework.data.expression.ValueExpression;
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
@ -176,7 +177,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery { @@ -176,7 +177,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
.getEvaluationContext(objects);
parsedQuery.getParameterMap().forEach((paramName, valueExpression) -> {
parameterMap.addValue(paramName, valueExpression.evaluate(evaluationContext));
addEvaluatedParameterToParameterSource(parameterMap, paramName, valueExpression, evaluationContext);
});
return parsedQuery.getQueryString();
@ -185,6 +186,39 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery { @@ -185,6 +186,39 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
return this.query;
}
private static void addEvaluatedParameterToParameterSource(
MapSqlParameterSource parameterMap,
String paramName,
ValueExpression valueExpression,
ValueEvaluationContext evaluationContext) {
Object evaluatedValue = valueExpression.evaluate(evaluationContext);
Class<?> valueType = valueExpression.getValueType(evaluationContext);
SQLType sqlType;
if (valueType == null) {
if (evaluatedValue != null) {
sqlType = getSqlType(evaluatedValue.getClass());
} else {
sqlType = null;
}
} else {
sqlType = getSqlType(valueType);
}
if (sqlType != null) {
parameterMap.addValue(paramName, evaluatedValue, sqlType.getVendorTypeNumber());
} else {
parameterMap.addValue(paramName, evaluatedValue);
}
}
private static SQLType getSqlType(Class<?> valueType) {
Class<?> resolvedPrimitiveType = JdbcColumnTypes.INSTANCE.resolvePrimitiveType(valueType);
return JdbcUtil.targetSqlTypeFor(resolvedPrimitiveType);
}
private JdbcQueryExecution<?> createJdbcQueryExecution(RelationalParameterAccessor accessor,
ResultProcessor processor) {

61
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

@ -41,7 +41,10 @@ import org.junit.jupiter.api.BeforeEach; @@ -41,7 +41,10 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationListener;
@ -112,6 +115,7 @@ public class JdbcRepositoryIntegrationTests { @@ -112,6 +115,7 @@ public class JdbcRepositoryIntegrationTests {
@Autowired RootRepository rootRepository;
@Autowired WithDelimitedColumnRepository withDelimitedColumnRepository;
@Autowired EntityWithSequenceRepository entityWithSequenceRepository;
@Autowired ExpressionSqlTypePropagationRepository expressionSqlTypePropagationRepository;
public static Stream<Arguments> findAllByExamplePageableSource() {
@ -346,6 +350,20 @@ public class JdbcRepositoryIntegrationTests { @@ -346,6 +350,20 @@ public class JdbcRepositoryIntegrationTests {
});
}
@ParameterizedTest
@NullSource
@EnumSource(value = EnumClass.class)
void shouldSaveWithCustomSpellExpressions(EnumClass value) {
expressionSqlTypePropagationRepository.saveWithSpel(new ExpressionSqlTypePropagation(1L, value));
var found = expressionSqlTypePropagationRepository.findById(1L);
assertThat(found).isPresent().hasValueSatisfying(entity -> {
assertThat(entity.getIdentifier()).isEqualTo(1L);
assertThat(entity.getEnumClass()).isEqualTo(value);
});
}
@Test // DATAJDBC-98
public void updateMany() {
@ -1573,6 +1591,18 @@ public class JdbcRepositoryIntegrationTests { @@ -1573,6 +1591,18 @@ public class JdbcRepositoryIntegrationTests {
interface EntityWithSequenceRepository extends CrudRepository<EntityWithSequence, Long> {}
interface ExpressionSqlTypePropagationRepository extends CrudRepository<ExpressionSqlTypePropagation, Long> {
// language=sql
@Modifying
@Query(value = """
INSERT INTO EXPRESSION_SQL_TYPE_PROPAGATION(identifier, enum_class)
VALUES(:#{#expressionSqlTypePropagation.identifier}, :#{#expressionSqlTypePropagation.enumClass})
""")
void saveWithSpel(@Param("expressionSqlTypePropagation") ExpressionSqlTypePropagation expressionSqlTypePropagation);
}
interface DummyProjection {
String getName();
}
@ -1608,6 +1638,11 @@ public class JdbcRepositoryIntegrationTests { @@ -1608,6 +1638,11 @@ public class JdbcRepositoryIntegrationTests {
return factory.getRepository(EntityWithSequenceRepository.class);
}
@Bean
ExpressionSqlTypePropagationRepository simpleEnumClassRepository() {
return factory.getRepository(ExpressionSqlTypePropagationRepository.class);
}
@Bean
NamedQueries namedQueries() throws IOException {
@ -1893,6 +1928,32 @@ public class JdbcRepositoryIntegrationTests { @@ -1893,6 +1928,32 @@ public class JdbcRepositoryIntegrationTests {
}
}
static class ExpressionSqlTypePropagation {
@Id
Long identifier;
EnumClass enumClass;
public ExpressionSqlTypePropagation(Long identifier, EnumClass enumClass) {
this.identifier = identifier;
this.enumClass = enumClass;
}
public EnumClass getEnumClass() {
return enumClass;
}
public Long getIdentifier() {
return identifier;
}
}
enum EnumClass {
ACTIVE,
DELETE
}
static class EntityWithSequence {
@Id

92
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java

@ -15,25 +15,36 @@ @@ -15,25 +15,36 @@
*/
package org.springframework.data.jdbc.repository.query;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.LIST;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.lang.reflect.Method;
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.sql.Types;
import java.time.DayOfWeek;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Stream;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.dao.DataAccessException;
@ -105,7 +116,7 @@ class StringBasedJdbcQueryUnitTests { @@ -105,7 +116,7 @@ class StringBasedJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = createMethod("noAnnotation");
Assertions.assertThatExceptionOfType(IllegalStateException.class) //
assertThatExceptionOfType(IllegalStateException.class) //
.isThrownBy(() -> createQuery(queryMethod).execute(new Object[] {}));
}
@ -299,6 +310,37 @@ class StringBasedJdbcQueryUnitTests { @@ -299,6 +310,37 @@ class StringBasedJdbcQueryUnitTests {
assertThat(sqlParameterSource.getValue("directions")).asList().containsExactlyInAnyOrder("LEFT", "RIGHT");
}
@Test // GH-1212
void spelParametersSqlTypesArePropagatedCorrectly() {
String type = "TYPE";
int score = 12;
Instant creationDate = Instant.now();
DayOfWeek dayOfWeek = DayOfWeek.SUNDAY;
ComplexEntity expressionRootObject = new ComplexEntity(type, score, creationDate, dayOfWeek);
SqlParameterSource sqlParameterSource = forMethod("spelContainingQuery", ComplexEntity.class)
.withArguments(expressionRootObject).extractParameterSource();
var expectedSqlTypes = Map.<Object, Integer>of(
type, Types.VARCHAR,
score, Types.INTEGER,
creationDate, Types.TIMESTAMP,
dayOfWeek, Types.VARCHAR
);
assertThat(sqlParameterSource.getParameterNames()).hasSize(5); // 1 root + 4 expressions
assertThat(sqlParameterSource.getParameterNames()).satisfies(parameterNames -> {
for (var paramName : parameterNames) {
if (paramName.equalsIgnoreCase("complexEntity")) {
continue; // do not check root for sqlType
}
Object value = sqlParameterSource.getValue(paramName);
assertThat(sqlParameterSource.getSqlType(paramName)).isEqualTo(expectedSqlTypes.get(value));
}
});
}
@Test // GH-1212
void convertsEnumCollectionParameterUsingCustomConverterWhenRegisteredForType() {
@ -506,6 +548,15 @@ class StringBasedJdbcQueryUnitTests { @@ -506,6 +548,15 @@ class StringBasedJdbcQueryUnitTests {
@Query(value = "some sql statement")
List<Object> findByEnumTypeIn(Set<Direction> directions);
@Query(value = """
SELECT * FROM my_table
WHERE t = :#{#complexEntity.type}
AND s = :#{#complexEntity.score}
AND cd = :#{#complexEntity.creationDate}
AND dow = :#{#complexEntity.dayOfWeek}
""")
List<Object> spelContainingQuery(ComplexEntity complexEntity);
@Query(value = "some sql statement")
List<Object> findBySimpleValue(Integer value);
@ -652,6 +703,37 @@ class StringBasedJdbcQueryUnitTests { @@ -652,6 +703,37 @@ class StringBasedJdbcQueryUnitTests {
}
}
static class ComplexEntity {
String type;
Integer score;
Instant creationDate;
DayOfWeek dayOfWeek;
public ComplexEntity(String type, Integer score, Instant creationDate, DayOfWeek dayOfWeek) {
this.type = type;
this.score = score;
this.creationDate = creationDate;
this.dayOfWeek = dayOfWeek;
}
public String getType() {
return type;
}
public Integer getScore() {
return score;
}
public Instant getCreationDate() {
return creationDate;
}
public DayOfWeek getDayOfWeek() {
return dayOfWeek;
}
}
private class StubRowMapperFactory implements RowMapperFactory {
private final String preparedReference;

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql

@ -6,6 +6,7 @@ DROP TABLE WITH_DELIMITED_COLUMN; @@ -6,6 +6,7 @@ DROP TABLE WITH_DELIMITED_COLUMN;
DROP TABLE ENTITY_WITH_SEQUENCE;
DROP SEQUENCE ENTITY_SEQUENCE;
DROP TABLE PROVIDED_ID_ENTITY;
DROP TABLE EXPRESSION_SQL_TYPE_PROPAGATION;
CREATE TABLE dummy_entity
(
@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY @@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
ID BIGINT NOT NULL PRIMARY KEY,
NAME VARCHAR(30)
);
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
IDENTIFIER BIGINT NOT NULL PRIMARY KEY,
ENUM_CLASS VARCHAR(30)
);

5
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql

@ -54,3 +54,8 @@ CREATE TABLE PROVIDED_ID_ENTITY @@ -54,3 +54,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
ID BIGINT PRIMARY KEY,
NAME VARCHAR(30)
);
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
IDENTIFIER BIGINT PRIMARY KEY,
ENUM_CLASS VARCHAR(30)
);

5
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql

@ -54,3 +54,8 @@ CREATE TABLE PROVIDED_ID_ENTITY @@ -54,3 +54,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
ID BIGINT PRIMARY KEY,
NAME VARCHAR(30)
);
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
IDENTIFIER BIGINT PRIMARY KEY,
ENUM_CLASS VARCHAR(30)
);

5
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql

@ -54,3 +54,8 @@ CREATE TABLE PROVIDED_ID_ENTITY @@ -54,3 +54,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
ID BIGINT PRIMARY KEY,
NAME VARCHAR(30)
);
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
IDENTIFIER BIGINT PRIMARY KEY,
ENUM_CLASS VARCHAR(30)
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql

@ -6,6 +6,7 @@ DROP TABLE IF EXISTS WITH_DELIMITED_COLUMN; @@ -6,6 +6,7 @@ DROP TABLE IF EXISTS WITH_DELIMITED_COLUMN;
DROP TABLE IF EXISTS ENTITY_WITH_SEQUENCE;
DROP SEQUENCE IF EXISTS ENTITY_SEQUENCE;
DROP TABLE IF EXISTS PROVIDED_ID_ENTITY;
DROP TABLE IF EXISTS EXPRESSION_SQL_TYPE_PROPAGATION;
CREATE TABLE dummy_entity
(
@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY @@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
ID BIGINT PRIMARY KEY,
NAME VARCHAR(30)
);
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
IDENTIFIER BIGINT PRIMARY KEY,
ENUM_CLASS VARCHAR(30)
);

5
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql

@ -49,3 +49,8 @@ CREATE TABLE PROVIDED_ID_ENTITY @@ -49,3 +49,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
ID BIGINT PRIMARY KEY,
NAME VARCHAR(30)
);
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
IDENTIFIER BIGINT PRIMARY KEY,
ENUM_CLASS VARCHAR(30)
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql

@ -6,6 +6,7 @@ DROP TABLE WITH_DELIMITED_COLUMN CASCADE CONSTRAINTS PURGE; @@ -6,6 +6,7 @@ DROP TABLE WITH_DELIMITED_COLUMN CASCADE CONSTRAINTS PURGE;
DROP TABLE ENTITY_WITH_SEQUENCE CASCADE CONSTRAINTS PURGE;
DROP SEQUENCE ENTITY_SEQUENCE;
DROP TABLE PROVIDED_ID_ENTITY CASCADE CONSTRAINTS PURGE;
DROP TABLE EXPRESSION_SQL_TYPE_PROPAGATION CASCADE CONSTRAINTS PURGE;
CREATE TABLE DUMMY_ENTITY
(
@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY @@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
ID NUMBER PRIMARY KEY,
NAME VARCHAR2(30)
);
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
IDENTIFIER BIGINT PRIMARY KEY,
ENUM_CLASS VARCHAR2(30)
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql

@ -6,6 +6,7 @@ DROP TABLE WITH_DELIMITED_COLUMN; @@ -6,6 +6,7 @@ DROP TABLE WITH_DELIMITED_COLUMN;
DROP TABLE ENTITY_WITH_SEQUENCE;
DROP SEQUENCE ENTITY_SEQUENCE;
DROP TABLE PROVIDED_ID_ENTITY;
DROP TABLE EXPRESSION_SQL_TYPE_PROPAGATION;
CREATE TABLE dummy_entity
(
@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY @@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
ID BIGINT PRIMARY KEY,
NAME VARCHAR(30)
);
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
IDENTIFIER BIGINT PRIMARY KEY,
ENUM_CLASS VARCHAR(30)
);

Loading…
Cancel
Save