diff --git a/pom.xml b/pom.xml index 028dfdbd68..9e0b3d54e9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 3.5.0-SNAPSHOT + 3.5.0-1803-projection-SNAPSHOT pom Spring Data Relational Parent @@ -46,8 +46,8 @@ 1.0.0.RELEASE 1.1.4 1.0.2.RELEASE - 1.3.1 - 1.3.0 + 1.3.0 + 1.2.0 4.2.0 diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 9c02f50608..91fe3bb4cc 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.5.0-SNAPSHOT + 3.5.0-1803-projection-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index aba16d9e30..919d71f93f 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 3.5.0-SNAPSHOT + 3.5.0-1803-projection-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.5.0-SNAPSHOT + 3.5.0-1803-projection-SNAPSHOT diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java index cf173ff570..357d921131 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java @@ -39,6 +39,7 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * Generates SQL statements to be used by {@link SimpleJdbcRepository} @@ -507,45 +508,89 @@ private String createFindAllSql() { } private SelectBuilder.SelectWhere selectBuilder() { - return selectBuilder(Collections.emptyList()); + return selectBuilder(Collections.emptyList(), Query.empty()); + } + + private SelectBuilder.SelectWhere selectBuilder(Query query) { + return selectBuilder(Collections.emptyList(), query); } private SelectBuilder.SelectWhere selectBuilder(Collection keyColumns) { + return selectBuilder(keyColumns, Query.empty()); + } + + private SelectBuilder.SelectWhere selectBuilder(Collection keyColumns, Query query) { Table table = getTable(); - Set columnExpressions = new LinkedHashSet<>(); + Projection projection = getProjection(keyColumns, query, table); + SelectBuilder.SelectAndFrom selectBuilder = StatementBuilder.select(projection.columns()); + SelectBuilder.SelectJoin baseSelect = selectBuilder.from(table); - List joinTables = new ArrayList<>(); - for (PersistentPropertyPath path : mappingContext - .findPersistentPropertyPaths(entity.getType(), p -> true)) { + for (Join join : projection.joins()) { + baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId); + } - AggregatePath extPath = mappingContext.getAggregatePath(path); + return (SelectBuilder.SelectWhere) baseSelect; + } - // add a join if necessary - Join join = getJoin(extPath); - if (join != null) { - joinTables.add(join); + private Projection getProjection(Collection keyColumns, Query query, Table table) { + + Set columns = new LinkedHashSet<>(); + Set joins = new LinkedHashSet<>(); + + if (!CollectionUtils.isEmpty(query.getColumns())) { + for (SqlIdentifier columnName : query.getColumns()) { + + String columnNameString = columnName.getReference(); + RelationalPersistentProperty property = entity.getPersistentProperty(columnNameString); + if (property != null) { + + AggregatePath aggregatePath = mappingContext.getAggregatePath( + mappingContext.getPersistentPropertyPath(columnNameString, entity.getTypeInformation())); + gatherColumn(aggregatePath, joins, columns); + } else { + columns.add(Column.create(columnName, table)); + } } + } else { + for (PersistentPropertyPath path : mappingContext + .findPersistentPropertyPaths(entity.getType(), p -> true)) { + + AggregatePath aggregatePath = mappingContext.getAggregatePath(path); - Column column = getColumn(extPath); - if (column != null) { - columnExpressions.add(column); + gatherColumn(aggregatePath, joins, columns); } } for (SqlIdentifier keyColumn : keyColumns) { - columnExpressions.add(table.column(keyColumn).as(keyColumn)); + columns.add(table.column(keyColumn).as(keyColumn)); } - SelectBuilder.SelectAndFrom selectBuilder = StatementBuilder.select(columnExpressions); - SelectBuilder.SelectJoin baseSelect = selectBuilder.from(table); + return new Projection(columns, joins); + } - for (Join join : joinTables) { - baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId); + private void gatherColumn(AggregatePath aggregatePath, Set joins, Set columns) { + + // add a join if necessary + Join join = getJoin(aggregatePath); + if (join != null) { + joins.add(join); } - return (SelectBuilder.SelectWhere) baseSelect; + Column column = getColumn(aggregatePath); + if (column != null) { + columns.add(column); + } + } + + /** + * Projection including its source joins. + * + * @param columns + * @param joins + */ + record Projection(Set columns, Set joins) { } private SelectBuilder.SelectOrdered selectBuilder(Collection keyColumns, Sort sort, @@ -876,7 +921,7 @@ public String selectByQuery(Query query, MapSqlParameterSource parameterSource) Assert.notNull(parameterSource, "parameterSource must not be null"); - SelectBuilder.SelectWhere selectBuilder = selectBuilder(); + SelectBuilder.SelectWhere selectBuilder = selectBuilder(query); Select select = applyQueryOnSelect(query, parameterSource, selectBuilder) // .build(); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java index c08e58de66..6762b5f3e4 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java @@ -29,6 +29,7 @@ import java.util.stream.IntStream; import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; @@ -235,7 +236,25 @@ void findAllByQuery() { Query query = Query.query(criteria); Iterable reloadedById = template.findAll(query, SimpleListParent.class); - assertThat(reloadedById).extracting(e -> e.id, e -> e.content.size()).containsExactly(tuple(two.id, 2)); + assertThat(reloadedById) // + .extracting(e -> e.id, e-> e.name, e -> e.content.size()) // + .containsExactly(tuple(two.id, two.name, 2)); + } + + @Test // GH-1803 + void findAllByQueryWithColumns() { + + template.save(SimpleListParent.of("one", "one_1")); + SimpleListParent two = template.save(SimpleListParent.of("two", "two_1", "two_2")); + template.save(SimpleListParent.of("three", "three_1", "three_2", "three_3")); + + CriteriaDefinition criteria = CriteriaDefinition.from(Criteria.where("id").is(two.id)); + Query query = Query.query(criteria).columns("id"); + Iterable reloadedById = template.findAll(query, SimpleListParent.class); + + assertThat(reloadedById) // + .extracting(e -> e.id, e-> e.name, e -> e.content.size()) // + .containsExactly(tuple(two.id, null, 2)); } @Test // GH-1601 @@ -2240,5 +2259,10 @@ static class JdbcAggregateTemplateIntegrationTests extends AbstractJdbcAggregate static class JdbcAggregateTemplateSingleQueryLoadingIntegrationTests extends AbstractJdbcAggregateTemplateIntegrationTests { + @Disabled + @Override + void findAllByQueryWithColumns() { + super.findAllByQueryWithColumns(); + } } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java index d095b27ccb..69286031cb 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java @@ -345,12 +345,13 @@ void findAllPagedAndSorted() { @Test // GH-1919 void selectByQuery() { - Query query = Query.query(Criteria.where("id").is(23L)); + Query query = Query.query(Criteria.where("id").is(23L)).columns(new String[0]); String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource()); assertThat(sql).contains( // "SELECT", // + "dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name", // "FROM dummy_entity", // "LEFT OUTER JOIN referenced_entity ref ON ref.dummy_entity = dummy_entity.id1", // "LEFT OUTER JOIN second_level_referenced_entity ref_further ON ref_further.referenced_entity = ref.x_l1id", // @@ -358,6 +359,19 @@ void selectByQuery() { ); } + @Test // GH-1803 + void selectByQueryWithColumnLimit() { + + Query query = Query.empty().columns("id", "alpha", "beta", "gamma"); + + String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource()); + + assertThat(sql).contains( // + "SELECT dummy_entity.id1 AS id1, dummy_entity.alpha, dummy_entity.beta, dummy_entity.gamma", // + "FROM dummy_entity" // + ); + } + @Test // GH-1919 void selectBySortedQuery() { @@ -375,7 +389,8 @@ void selectBySortedQuery() { "ORDER BY dummy_entity.id1 ASC" // ); assertThat(sql).containsOnlyOnce("LEFT OUTER JOIN referenced_entity ref ON ref.dummy_entity = dummy_entity.id1"); - assertThat(sql).containsOnlyOnce("LEFT OUTER JOIN second_level_referenced_entity ref_further ON ref_further.referenced_entity = ref.x_l1id"); + assertThat(sql).containsOnlyOnce( + "LEFT OUTER JOIN second_level_referenced_entity ref_further ON ref_further.referenced_entity = ref.x_l1id"); } @Test // DATAJDBC-131, DATAJDBC-111 diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index fd450fd21b..2324c0c820 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 3.5.0-SNAPSHOT + 3.5.0-1803-projection-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.5.0-SNAPSHOT + 3.5.0-1803-projection-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 9760a0f1e7..b2547c2659 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 3.5.0-SNAPSHOT + 3.5.0-1803-projection-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.5.0-SNAPSHOT + 3.5.0-1803-projection-SNAPSHOT diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java index 3a8e9d72c6..6d1ed69d4f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java @@ -41,6 +41,7 @@ */ public class Query { + private static final Query EMPTY = new Query(null); private static final int NO_LIMIT = -1; private final @Nullable CriteriaDefinition criteria; @@ -84,7 +85,7 @@ private Query(@Nullable CriteriaDefinition criteria, List columns * @return */ public static Query empty() { - return new Query(null); + return EMPTY; } /**