Skip to content

GH-2007 Propagated the SqlType to the parameter source during Spel ex… #2079

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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;
Expand Down Expand Up @@ -176,7 +177,7 @@ private String evaluateExpressions(Object[] objects, Parameters<?, ?> bindablePa
.getEvaluationContext(objects);

parsedQuery.getParameterMap().forEach((paramName, valueExpression) -> {
parameterMap.addValue(paramName, valueExpression.evaluate(evaluationContext));
addEvaluatedParameterToParameterSource(parameterMap, paramName, valueExpression, evaluationContext);
});

return parsedQuery.getQueryString();
Expand All @@ -185,6 +186,39 @@ private String evaluateExpressions(Object[] objects, Parameters<?, ?> bindablePa
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) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@
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;
Expand Down Expand Up @@ -112,6 +115,7 @@ public class JdbcRepositoryIntegrationTests {
@Autowired RootRepository rootRepository;
@Autowired WithDelimitedColumnRepository withDelimitedColumnRepository;
@Autowired EntityWithSequenceRepository entityWithSequenceRepository;
@Autowired ExpressionSqlTypePropagationRepository expressionSqlTypePropagationRepository;

public static Stream<Arguments> findAllByExamplePageableSource() {

Expand Down Expand Up @@ -346,6 +350,20 @@ public void update() {
});
}

@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() {

Expand Down Expand Up @@ -1573,6 +1591,18 @@ interface WithDelimitedColumnRepository extends CrudRepository<WithDelimitedColu

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();
}
Expand Down Expand Up @@ -1608,6 +1638,11 @@ EntityWithSequenceRepository entityWithSequenceRepository() {
return factory.getRepository(EntityWithSequenceRepository.class);
}

@Bean
ExpressionSqlTypePropagationRepository simpleEnumClassRepository() {
return factory.getRepository(ExpressionSqlTypePropagationRepository.class);
}

@Bean
NamedQueries namedQueries() throws IOException {

Expand Down Expand Up @@ -1893,6 +1928,32 @@ public Long getId() {
}
}

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -105,7 +116,7 @@ void emptyQueryThrowsException() {

JdbcQueryMethod queryMethod = createMethod("noAnnotation");

Assertions.assertThatExceptionOfType(IllegalStateException.class) //
assertThatExceptionOfType(IllegalStateException.class) //
.isThrownBy(() -> createQuery(queryMethod).execute(new Object[] {}));
}

Expand Down Expand Up @@ -299,6 +310,37 @@ void convertsEnumCollectionParameterIntoStringCollectionParameter() {
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() {

Expand Down Expand Up @@ -506,6 +548,15 @@ interface MyRepository extends Repository<Object, Long> {
@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);

Expand Down Expand Up @@ -652,6 +703,37 @@ public Object getRootObject() {
}
}

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
(
Expand Down Expand Up @@ -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)
);
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Original file line number Diff line number Diff line change
Expand Up @@ -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
(
Expand Down Expand Up @@ -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)
);
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Original file line number Diff line number Diff line change
Expand Up @@ -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
(
Expand Down Expand Up @@ -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)
);
Loading