From 8241aa1222629042a2fd5bbb2e58fb58bd361ddd Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Wed, 17 Jul 2024 16:00:10 +0200 Subject: [PATCH] Allow passing of tuples to repository query methods. Closes #1323 Original pull request: #1838 --- .../query/StringBasedJdbcQuery.java | 49 ++++++++++++++----- .../JdbcRepositoryIntegrationTests.java | 24 ++++++++- .../query/StringBasedJdbcQueryUnitTests.java | 31 ++++++++++++ 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java index ef353497f..f50d7bfb8 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java @@ -17,7 +17,9 @@ package org.springframework.data.jdbc.repository.query; 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; @@ -45,6 +47,7 @@ import org.springframework.data.repository.query.SpelEvaluator; import org.springframework.data.repository.query.SpelQueryContext; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; +import org.springframework.data.util.TypeUtils; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -204,23 +207,47 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery { TypeInformation typeInformation = parameter.getTypeInformation(); JdbcValue jdbcValue; - if (typeInformation.isCollectionLike() && value instanceof Collection) { + if (typeInformation.isCollectionLike() // + && value instanceof Collection collectionValue// + ) { + if ( typeInformation.getActualType().getType().isArray() ){ - List mapped = new ArrayList<>(); - SQLType jdbcType = null; + TypeInformation arrayElementType = typeInformation.getActualType().getActualType(); - TypeInformation actualType = typeInformation.getRequiredActualType(); - for (Object o : (Iterable) value) { - JdbcValue elementJdbcValue = converter.writeJdbcValue(o, actualType, parameter.getActualSqlType()); - if (jdbcType == null) { - jdbcType = elementJdbcValue.getJdbcType(); + List mapped = new ArrayList<>(); + + for (Object array : collectionValue) { + 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, arrayElementType, parameter.getActualSqlType()); + + mappedArray[i] = elementJdbcValue.getValue(); + } + mapped.add(mappedArray); } + jdbcValue = JdbcValue.of(mapped, JDBCType.OTHER); - mapped.add(elementJdbcValue.getValue()); - } + } else { + List mapped = new ArrayList<>(); + SQLType jdbcType = null; + + TypeInformation actualType = typeInformation.getRequiredActualType(); + for (Object o : collectionValue) { + JdbcValue elementJdbcValue = converter.writeJdbcValue(o, actualType, parameter.getActualSqlType()); + if (jdbcType == null) { + jdbcType = elementJdbcValue.getJdbcType(); + } - jdbcValue = JdbcValue.of(mapped, jdbcType); + mapped.add(elementJdbcValue.getValue()); + } + + jdbcValue = JdbcValue.of(mapped, jdbcType); + } } else { + SQLType sqlType = parameter.getSqlType(); jdbcValue = converter.writeJdbcValue(value, typeInformation, sqlType); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 9e343750e..9f4773d85 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -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; } @@ -1334,6 +1338,21 @@ public class JdbcRepositoryIntegrationTests { 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 tuples = List.of( + new Object[]{two.idProp, "two"}, // matches "two" + new Object[]{three.idProp, "two"} // matches nothing + ); + + repository.findByListInTuple(tuples); + } + private Root createRoot(String namePrefix) { return new Root(null, namePrefix, @@ -1461,6 +1480,9 @@ public class JdbcRepositoryIntegrationTests { Optional findDtoByIdProp(Long idProp); Optional findAllArgsDtoByIdProp(Long idProp); + + @Query("SELECT * FROM DUMMY_ENTITY WHERE (ID_PROP, NAME) IN (:tuples)") + List findByListInTuple(List tuples); } interface RootRepository extends ListCrudRepository { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java index 8cccbab38..2b3933e99 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java @@ -22,6 +22,7 @@ import java.lang.reflect.Method; 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; @@ -325,6 +326,33 @@ class StringBasedJdbcQueryUnitTests { 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)); } @@ -450,6 +478,9 @@ class StringBasedJdbcQueryUnitTests { @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 tuples); } @Test // GH-619