Skip to content

Allow passing of tuples to methods #1838

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

Closed
wants to merge 4 commits into from
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>3.4.0-SNAPSHOT</version>
<version>3.4.0-1323-in-with-tuple-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>3.4.0-SNAPSHOT</version>
<version>3.4.0-1323-in-with-tuple-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 @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-jdbc</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-1323-in-with-tuple-SNAPSHOT</version>

<name>Spring Data JDBC</name>
<description>Spring Data module for JDBC repositories.</description>
Expand All @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-1323-in-with-tuple-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,24 @@

import static org.springframework.data.jdbc.repository.query.JdbcQueryExecution.*;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.sql.JDBCType;
import java.sql.SQLType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalParameters;
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
Expand All @@ -52,7 +51,6 @@
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;

/**
Expand All @@ -72,7 +70,7 @@
*/
public class StringBasedJdbcQuery extends AbstractJdbcQuery {

private static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters";
private static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or use the javac flag -parameters";
private final JdbcConverter converter;
private final RowMapperFactory rowMapperFactory;
private final SpelEvaluator spelEvaluator;
Expand Down Expand Up @@ -185,53 +183,103 @@ private JdbcQueryExecution<?> createJdbcQueryExecution(RelationalParameterAccess

private MapSqlParameterSource bindParameters(RelationalParameterAccessor accessor) {

MapSqlParameterSource parameters = new MapSqlParameterSource();

Parameters<?, ?> bindableParameters = accessor.getBindableParameters();
MapSqlParameterSource parameters = new MapSqlParameterSource(
new LinkedHashMap<>(bindableParameters.getNumberOfParameters(), 1.0f));

for (Parameter bindableParameter : bindableParameters) {
convertAndAddParameter(parameters, bindableParameter, accessor.getBindableValue(bindableParameter.getIndex()));

Object value = accessor.getBindableValue(bindableParameter.getIndex());
String parameterName = bindableParameter.getName()
.orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED));
JdbcParameters.JdbcParameter parameter = getQueryMethod().getParameters()
.getParameter(bindableParameter.getIndex());

JdbcValue jdbcValue = writeValue(value, parameter.getTypeInformation(), parameter);
SQLType jdbcType = jdbcValue.getJdbcType();

if (jdbcType == null) {
parameters.addValue(parameterName, jdbcValue.getValue());
} else {
parameters.addValue(parameterName, jdbcValue.getValue(), jdbcType.getVendorTypeNumber());
}
}

return parameters;
}

private void convertAndAddParameter(MapSqlParameterSource parameters, Parameter p, Object value) {
private JdbcValue writeValue(@Nullable Object value, TypeInformation<?> typeInformation,
JdbcParameters.JdbcParameter parameter) {

if (value == null) {
return JdbcValue.of(value, parameter.getSqlType());
}

if (typeInformation.isCollectionLike() && value instanceof Collection<?> collection) {

TypeInformation<?> actualType = typeInformation.getActualType();

// tuple-binding
if (actualType != null && actualType.getType().isArray()) {

TypeInformation<?> nestedElementType = actualType.getRequiredActualType();
return writeCollection(collection, JDBCType.OTHER,
array -> writeArrayValue(parameter, array, nestedElementType));
}

// parameter expansion
return writeCollection(collection, parameter.getActualSqlType(),
it -> converter.writeJdbcValue(it, typeInformation.getRequiredActualType(), parameter.getActualSqlType()));
}

SQLType sqlType = parameter.getSqlType();
return converter.writeJdbcValue(value, typeInformation, sqlType);
}

String parameterName = p.getName().orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED));
private JdbcValue writeCollection(Collection<?> value, SQLType defaultType, Function<Object, Object> mapper) {

JdbcParameters.JdbcParameter parameter = getQueryMethod().getParameters().getParameter(p.getIndex());
TypeInformation<?> typeInformation = parameter.getTypeInformation();
if (value.isEmpty()) {
return JdbcValue.of(value, defaultType);
}

JdbcValue jdbcValue;
if (typeInformation.isCollectionLike() && value instanceof Collection<?>) {
List<Object> mapped = new ArrayList<>(value.size());
SQLType jdbcType = null;

for (Object o : value) {

List<Object> mapped = new ArrayList<>();
SQLType jdbcType = null;
Object mappedValue = mapper.apply(o);

TypeInformation<?> actualType = typeInformation.getRequiredActualType();
for (Object o : (Iterable<?>) value) {
JdbcValue elementJdbcValue = converter.writeJdbcValue(o, actualType, parameter.getActualSqlType());
if (mappedValue instanceof JdbcValue jv) {
if (jdbcType == null) {
jdbcType = elementJdbcValue.getJdbcType();
jdbcType = jv.getJdbcType();
}

mapped.add(elementJdbcValue.getValue());
mappedValue = jv.getValue();
}

jdbcValue = JdbcValue.of(mapped, jdbcType);
} else {
SQLType sqlType = parameter.getSqlType();
jdbcValue = converter.writeJdbcValue(value, typeInformation, sqlType);
mapped.add(mappedValue);
}

SQLType jdbcType = jdbcValue.getJdbcType();
if (jdbcType == null) {
jdbcValue = JdbcValue.of(mapped, jdbcType == null ? defaultType : jdbcType);

parameters.addValue(parameterName, jdbcValue.getValue());
} else {
parameters.addValue(parameterName, jdbcValue.getValue(), jdbcType.getVendorTypeNumber());
return jdbcValue;
}

private Object[] writeArrayValue(JdbcParameters.JdbcParameter parameter, Object array,
TypeInformation<?> nestedElementType) {

int length = Array.getLength(array);
Object[] mappedArray = new Object[length];

for (int i = 0; i < length; i++) {

Object element = Array.get(array, i);
JdbcValue elementJdbcValue = converter.writeJdbcValue(element, nestedElementType, parameter.getActualSqlType());

mappedArray[i] = elementJdbcValue.getValue();
}

return mappedArray;
}

RowMapper<Object> determineRowMapper(ResultProcessor resultProcessor, boolean hasDynamicProjection) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,13 @@ public class JdbcRepositoryIntegrationTests {
@Autowired WithDelimitedColumnRepository withDelimitedColumnRepository;

private static DummyEntity createDummyEntity() {
return createDummyEntity("Entity Name");
}

private static DummyEntity createDummyEntity(String entityName) {

DummyEntity entity = new DummyEntity();
entity.setName("Entity Name");
entity.setName(entityName);

return entity;
}
Expand Down Expand Up @@ -1334,6 +1338,23 @@ void withDelimitedColumnTest() {
assertThat(inDatabase.get().getIdentifier()).isEqualTo("UR-123");
}

@Test // GH-1323
void queryWithTupleIn() {

DummyEntity one = repository.save(createDummyEntity("one"));
DummyEntity two = repository.save(createDummyEntity( "two"));
DummyEntity three = repository.save(createDummyEntity( "three"));

List<Object[]> tuples = List.of(
new Object[]{two.idProp, "two"}, // matches "two"
new Object[]{three.idProp, "two"} // matches nothing
);

List<DummyEntity> result = repository.findByListInTuple(tuples);

assertThat(result).containsOnly(two);
}

private Root createRoot(String namePrefix) {

return new Root(null, namePrefix,
Expand Down Expand Up @@ -1461,6 +1482,9 @@ interface DummyEntityRepository extends CrudRepository<DummyEntity, Long>, Query
Optional<DummyDto> findDtoByIdProp(Long idProp);

Optional<DummyAllArgsDto> findAllArgsDtoByIdProp(Long idProp);

@Query("SELECT * FROM DUMMY_ENTITY WHERE (ID_PROP, NAME) IN (:tuples)")
List<DummyEntity> findByListInTuple(List<Object[]> tuples);
}

interface RootRepository extends ListCrudRepository<Root, Long> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
Expand Down Expand Up @@ -325,6 +326,33 @@ void appliesConverterToIterable() {
assertThat(sqlParameterSource.getValue("value")).isEqualTo("one");
}

@Test // GH-1323
void queryByListOfTuples() {

String[][] tuples = {new String[]{"Albert", "Einstein"}, new String[]{"Richard", "Feynman"}};

SqlParameterSource parameterSource = forMethod("findByListOfTuples", List.class) //
.withArguments(Arrays.asList(tuples))
.extractParameterSource();

assertThat(parameterSource.getValue("tuples"))
.asInstanceOf(LIST)
.containsExactly(tuples);
}

@Test // GH-1323
void queryByListOfConvertableTuples() {

SqlParameterSource parameterSource = forMethod("findByListOfTuples", List.class) //
.withCustomConverters(DirectionToIntegerConverter.INSTANCE) //
.withArguments(Arrays.asList(new Object[]{Direction.LEFT, "Einstein"}, new Object[]{Direction.RIGHT, "Feynman"}))
.extractParameterSource();

assertThat(parameterSource.getValue("tuples"))
.asInstanceOf(LIST)
.containsExactly(new Object[][]{new Object[]{-1, "Einstein"}, new Object[]{1, "Feynman"}});
}

QueryFixture forMethod(String name, Class... paramTypes) {
return new QueryFixture(createMethod(name, paramTypes));
}
Expand Down Expand Up @@ -450,6 +478,9 @@ interface MyRepository extends Repository<Object, Long> {

@Query("SELECT * FROM person WHERE lastname = $1")
Object unsupportedLimitQuery(@Param("lastname") String lastname, Limit limit);

@Query("select count(1) from person where (firstname, lastname) in (:tuples)")
Object findByListOfTuples(@Param("tuples") List<Object[]> tuples);
}

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

<artifactId>spring-data-r2dbc</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-1323-in-with-tuple-SNAPSHOT</version>

<name>Spring Data R2DBC</name>
<description>Spring Data module for R2DBC</description>
Expand All @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-1323-in-with-tuple-SNAPSHOT</version>
</parent>

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

<artifactId>spring-data-relational</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-1323-in-with-tuple-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>3.4.0-SNAPSHOT</version>
<version>3.4.0-1323-in-with-tuple-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Loading