Skip to content

Commit 630b687

Browse files
committed
GH-2007 Propagated the SqlType to the parameter source during Spel expression evaluation
1 parent 7ded5f8 commit 630b687

11 files changed

+227
-6
lines changed

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.beans.BeanUtils;
3232
import org.springframework.beans.factory.BeanFactory;
3333
import org.springframework.data.expression.ValueEvaluationContext;
34+
import org.springframework.data.expression.ValueExpression;
3435
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
3536
import org.springframework.data.jdbc.core.convert.JdbcConverter;
3637
import org.springframework.data.jdbc.core.mapping.JdbcValue;
@@ -176,7 +177,7 @@ private String evaluateExpressions(Object[] objects, Parameters<?, ?> bindablePa
176177
.getEvaluationContext(objects);
177178

178179
parsedQuery.getParameterMap().forEach((paramName, valueExpression) -> {
179-
parameterMap.addValue(paramName, valueExpression.evaluate(evaluationContext));
180+
addEvaluatedParameterToParameterSource(parameterMap, paramName, valueExpression, evaluationContext);
180181
});
181182

182183
return parsedQuery.getQueryString();
@@ -185,6 +186,39 @@ private String evaluateExpressions(Object[] objects, Parameters<?, ?> bindablePa
185186
return this.query;
186187
}
187188

189+
private static void addEvaluatedParameterToParameterSource(
190+
MapSqlParameterSource parameterMap,
191+
String paramName,
192+
ValueExpression valueExpression,
193+
ValueEvaluationContext evaluationContext) {
194+
195+
Object evaluatedValue = valueExpression.evaluate(evaluationContext);
196+
Class<?> valueType = valueExpression.getValueType(evaluationContext);
197+
198+
SQLType sqlType;
199+
200+
if (valueType == null) {
201+
if (evaluatedValue != null) {
202+
sqlType = getSqlType(evaluatedValue.getClass());
203+
} else {
204+
sqlType = null;
205+
}
206+
} else {
207+
sqlType = getSqlType(valueType);
208+
}
209+
210+
if (sqlType != null) {
211+
parameterMap.addValue(paramName, evaluatedValue, sqlType.getVendorTypeNumber());
212+
} else {
213+
parameterMap.addValue(paramName, evaluatedValue);
214+
}
215+
}
216+
217+
private static SQLType getSqlType(Class<?> valueType) {
218+
Class<?> resolvedPrimitiveType = JdbcColumnTypes.INSTANCE.resolvePrimitiveType(valueType);
219+
return JdbcUtil.targetSqlTypeFor(resolvedPrimitiveType);
220+
}
221+
188222
private JdbcQueryExecution<?> createJdbcQueryExecution(RelationalParameterAccessor accessor,
189223
ResultProcessor processor) {
190224

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@
4141
import org.junit.jupiter.api.Test;
4242
import org.junit.jupiter.params.ParameterizedTest;
4343
import org.junit.jupiter.params.provider.Arguments;
44+
import org.junit.jupiter.params.provider.EnumSource;
4445
import org.junit.jupiter.params.provider.MethodSource;
46+
import org.junit.jupiter.params.provider.NullSource;
47+
import org.junit.jupiter.params.provider.ValueSource;
4548
import org.springframework.beans.factory.annotation.Autowired;
4649
import org.springframework.beans.factory.config.PropertiesFactoryBean;
4750
import org.springframework.context.ApplicationListener;
@@ -112,6 +115,7 @@ public class JdbcRepositoryIntegrationTests {
112115
@Autowired RootRepository rootRepository;
113116
@Autowired WithDelimitedColumnRepository withDelimitedColumnRepository;
114117
@Autowired EntityWithSequenceRepository entityWithSequenceRepository;
118+
@Autowired ExpressionSqlTypePropagationRepository expressionSqlTypePropagationRepository;
115119

116120
public static Stream<Arguments> findAllByExamplePageableSource() {
117121

@@ -346,6 +350,20 @@ public void update() {
346350
});
347351
}
348352

353+
@ParameterizedTest
354+
@NullSource
355+
@EnumSource(value = EnumClass.class)
356+
void shouldSaveWithCustomSpellExpressions(EnumClass value) {
357+
expressionSqlTypePropagationRepository.saveWithSpel(new ExpressionSqlTypePropagation(1L, value));
358+
359+
var found = expressionSqlTypePropagationRepository.findById(1L);
360+
361+
assertThat(found).isPresent().hasValueSatisfying(entity -> {
362+
assertThat(entity.getIdentifier()).isEqualTo(1L);
363+
assertThat(entity.getEnumClass()).isEqualTo(value);
364+
});
365+
}
366+
349367
@Test // DATAJDBC-98
350368
public void updateMany() {
351369

@@ -1573,6 +1591,18 @@ interface WithDelimitedColumnRepository extends CrudRepository<WithDelimitedColu
15731591

15741592
interface EntityWithSequenceRepository extends CrudRepository<EntityWithSequence, Long> {}
15751593

1594+
interface ExpressionSqlTypePropagationRepository extends CrudRepository<ExpressionSqlTypePropagation, Long> {
1595+
1596+
// language=sql
1597+
@Modifying
1598+
@Query(value = """
1599+
INSERT INTO EXPRESSION_SQL_TYPE_PROPAGATION(identifier, enum_class)
1600+
VALUES(:#{#expressionSqlTypePropagation.identifier}, :#{#expressionSqlTypePropagation.enumClass})
1601+
""")
1602+
void saveWithSpel(@Param("expressionSqlTypePropagation") ExpressionSqlTypePropagation expressionSqlTypePropagation);
1603+
}
1604+
1605+
15761606
interface DummyProjection {
15771607
String getName();
15781608
}
@@ -1608,6 +1638,11 @@ EntityWithSequenceRepository entityWithSequenceRepository() {
16081638
return factory.getRepository(EntityWithSequenceRepository.class);
16091639
}
16101640

1641+
@Bean
1642+
ExpressionSqlTypePropagationRepository simpleEnumClassRepository() {
1643+
return factory.getRepository(ExpressionSqlTypePropagationRepository.class);
1644+
}
1645+
16111646
@Bean
16121647
NamedQueries namedQueries() throws IOException {
16131648

@@ -1893,6 +1928,32 @@ public Long getId() {
18931928
}
18941929
}
18951930

1931+
static class ExpressionSqlTypePropagation {
1932+
1933+
@Id
1934+
Long identifier;
1935+
1936+
EnumClass enumClass;
1937+
1938+
public ExpressionSqlTypePropagation(Long identifier, EnumClass enumClass) {
1939+
this.identifier = identifier;
1940+
this.enumClass = enumClass;
1941+
}
1942+
1943+
public EnumClass getEnumClass() {
1944+
return enumClass;
1945+
}
1946+
1947+
public Long getIdentifier() {
1948+
return identifier;
1949+
}
1950+
}
1951+
1952+
enum EnumClass {
1953+
ACTIVE,
1954+
DELETE
1955+
}
1956+
18961957
static class EntityWithSequence {
18971958

18981959
@Id

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

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,36 @@
1515
*/
1616
package org.springframework.data.jdbc.repository.query;
1717

18-
import static org.assertj.core.api.Assertions.*;
19-
import static org.mockito.Mockito.*;
18+
import static org.assertj.core.api.Assertions.LIST;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
21+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
22+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
23+
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
24+
import static org.mockito.Mockito.any;
25+
import static org.mockito.Mockito.anyString;
26+
import static org.mockito.Mockito.eq;
27+
import static org.mockito.Mockito.mock;
28+
import static org.mockito.Mockito.verify;
2029

2130
import java.lang.reflect.Method;
2231
import java.sql.JDBCType;
2332
import java.sql.ResultSet;
33+
import java.sql.Types;
34+
import java.time.DayOfWeek;
35+
import java.time.Instant;
2436
import java.util.ArrayList;
2537
import java.util.Arrays;
2638
import java.util.Iterator;
2739
import java.util.List;
40+
import java.util.Map;
2841
import java.util.Properties;
2942
import java.util.Set;
3043
import java.util.stream.Stream;
3144

32-
import org.assertj.core.api.Assertions;
3345
import org.junit.jupiter.api.BeforeEach;
3446
import org.junit.jupiter.api.Test;
3547
import org.mockito.ArgumentCaptor;
36-
3748
import org.springframework.core.convert.converter.Converter;
3849
import org.springframework.core.env.StandardEnvironment;
3950
import org.springframework.dao.DataAccessException;
@@ -105,7 +116,7 @@ void emptyQueryThrowsException() {
105116

106117
JdbcQueryMethod queryMethod = createMethod("noAnnotation");
107118

108-
Assertions.assertThatExceptionOfType(IllegalStateException.class) //
119+
assertThatExceptionOfType(IllegalStateException.class) //
109120
.isThrownBy(() -> createQuery(queryMethod).execute(new Object[] {}));
110121
}
111122

@@ -299,6 +310,37 @@ void convertsEnumCollectionParameterIntoStringCollectionParameter() {
299310
assertThat(sqlParameterSource.getValue("directions")).asList().containsExactlyInAnyOrder("LEFT", "RIGHT");
300311
}
301312

313+
@Test // GH-1212
314+
void spelParametersSqlTypesArePropagatedCorrectly() {
315+
316+
String type = "TYPE";
317+
int score = 12;
318+
Instant creationDate = Instant.now();
319+
DayOfWeek dayOfWeek = DayOfWeek.SUNDAY;
320+
ComplexEntity expressionRootObject = new ComplexEntity(type, score, creationDate, dayOfWeek);
321+
322+
SqlParameterSource sqlParameterSource = forMethod("spelContainingQuery", ComplexEntity.class)
323+
.withArguments(expressionRootObject).extractParameterSource();
324+
325+
var expectedSqlTypes = Map.<Object, Integer>of(
326+
type, Types.VARCHAR,
327+
score, Types.INTEGER,
328+
creationDate, Types.TIMESTAMP,
329+
dayOfWeek, Types.VARCHAR
330+
);
331+
332+
assertThat(sqlParameterSource.getParameterNames()).hasSize(5); // 1 root + 4 expressions
333+
assertThat(sqlParameterSource.getParameterNames()).satisfies(parameterNames -> {
334+
for (var paramName : parameterNames) {
335+
if (paramName.equalsIgnoreCase("complexEntity")) {
336+
continue; // do not check root for sqlType
337+
}
338+
Object value = sqlParameterSource.getValue(paramName);
339+
assertThat(sqlParameterSource.getSqlType(paramName)).isEqualTo(expectedSqlTypes.get(value));
340+
}
341+
});
342+
}
343+
302344
@Test // GH-1212
303345
void convertsEnumCollectionParameterUsingCustomConverterWhenRegisteredForType() {
304346

@@ -506,6 +548,15 @@ interface MyRepository extends Repository<Object, Long> {
506548
@Query(value = "some sql statement")
507549
List<Object> findByEnumTypeIn(Set<Direction> directions);
508550

551+
@Query(value = """
552+
SELECT * FROM my_table
553+
WHERE t = :#{#complexEntity.type}
554+
AND s = :#{#complexEntity.score}
555+
AND cd = :#{#complexEntity.creationDate}
556+
AND dow = :#{#complexEntity.dayOfWeek}
557+
""")
558+
List<Object> spelContainingQuery(ComplexEntity complexEntity);
559+
509560
@Query(value = "some sql statement")
510561
List<Object> findBySimpleValue(Integer value);
511562

@@ -652,6 +703,37 @@ public Object getRootObject() {
652703
}
653704
}
654705

706+
static class ComplexEntity {
707+
708+
String type;
709+
Integer score;
710+
Instant creationDate;
711+
DayOfWeek dayOfWeek;
712+
713+
public ComplexEntity(String type, Integer score, Instant creationDate, DayOfWeek dayOfWeek) {
714+
this.type = type;
715+
this.score = score;
716+
this.creationDate = creationDate;
717+
this.dayOfWeek = dayOfWeek;
718+
}
719+
720+
public String getType() {
721+
return type;
722+
}
723+
724+
public Integer getScore() {
725+
return score;
726+
}
727+
728+
public Instant getCreationDate() {
729+
return creationDate;
730+
}
731+
732+
public DayOfWeek getDayOfWeek() {
733+
return dayOfWeek;
734+
}
735+
}
736+
655737
private class StubRowMapperFactory implements RowMapperFactory {
656738

657739
private final String preparedReference;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ DROP TABLE WITH_DELIMITED_COLUMN;
66
DROP TABLE ENTITY_WITH_SEQUENCE;
77
DROP SEQUENCE ENTITY_SEQUENCE;
88
DROP TABLE PROVIDED_ID_ENTITY;
9+
DROP TABLE EXPRESSION_SQL_TYPE_PROPAGATION;
910

1011
CREATE TABLE dummy_entity
1112
(
@@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
6364
ID BIGINT NOT NULL PRIMARY KEY,
6465
NAME VARCHAR(30)
6566
);
67+
68+
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
69+
IDENTIFIER BIGINT NOT NULL PRIMARY KEY,
70+
ENUM_CLASS VARCHAR(30)
71+
);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
5454
ID BIGINT PRIMARY KEY,
5555
NAME VARCHAR(30)
5656
);
57+
58+
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
59+
IDENTIFIER BIGINT PRIMARY KEY,
60+
ENUM_CLASS VARCHAR(30)
61+
);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
5454
ID BIGINT PRIMARY KEY,
5555
NAME VARCHAR(30)
5656
);
57+
58+
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
59+
IDENTIFIER BIGINT PRIMARY KEY,
60+
ENUM_CLASS VARCHAR(30)
61+
);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
5454
ID BIGINT PRIMARY KEY,
5555
NAME VARCHAR(30)
5656
);
57+
58+
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
59+
IDENTIFIER BIGINT PRIMARY KEY,
60+
ENUM_CLASS VARCHAR(30)
61+
);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ DROP TABLE IF EXISTS WITH_DELIMITED_COLUMN;
66
DROP TABLE IF EXISTS ENTITY_WITH_SEQUENCE;
77
DROP SEQUENCE IF EXISTS ENTITY_SEQUENCE;
88
DROP TABLE IF EXISTS PROVIDED_ID_ENTITY;
9+
DROP TABLE IF EXISTS EXPRESSION_SQL_TYPE_PROPAGATION;
910

1011
CREATE TABLE dummy_entity
1112
(
@@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
6364
ID BIGINT PRIMARY KEY,
6465
NAME VARCHAR(30)
6566
);
67+
68+
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
69+
IDENTIFIER BIGINT PRIMARY KEY,
70+
ENUM_CLASS VARCHAR(30)
71+
);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
4949
ID BIGINT PRIMARY KEY,
5050
NAME VARCHAR(30)
5151
);
52+
53+
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
54+
IDENTIFIER BIGINT PRIMARY KEY,
55+
ENUM_CLASS VARCHAR(30)
56+
);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ DROP TABLE WITH_DELIMITED_COLUMN CASCADE CONSTRAINTS PURGE;
66
DROP TABLE ENTITY_WITH_SEQUENCE CASCADE CONSTRAINTS PURGE;
77
DROP SEQUENCE ENTITY_SEQUENCE;
88
DROP TABLE PROVIDED_ID_ENTITY CASCADE CONSTRAINTS PURGE;
9+
DROP TABLE EXPRESSION_SQL_TYPE_PROPAGATION CASCADE CONSTRAINTS PURGE;
910

1011
CREATE TABLE DUMMY_ENTITY
1112
(
@@ -63,3 +64,8 @@ CREATE TABLE PROVIDED_ID_ENTITY
6364
ID NUMBER PRIMARY KEY,
6465
NAME VARCHAR2(30)
6566
);
67+
68+
CREATE TABLE EXPRESSION_SQL_TYPE_PROPAGATION(
69+
IDENTIFIER BIGINT PRIMARY KEY,
70+
ENUM_CLASS VARCHAR2(30)
71+
);

0 commit comments

Comments
 (0)