diff --git a/pom.xml b/pom.xml
index ebd3103251..551841eaf3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-relational-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-1803-projection-SNAPSHOT
pom
Spring Data Relational Parent
@@ -46,13 +46,12 @@
1.0.0.RELEASE
1.1.4
1.0.2.RELEASE
- 1.4.1
- 1.3.0
+ 1.4.0
+ 1.2.0
1.3.0
- 1.37
0.4.0.BUILD-SNAPSHOT
@@ -165,22 +164,14 @@
jmh
-
- com.github.mp911de.microbenchmark-runner
- microbenchmark-runner-junit5
- ${mbr.version}
- test
-
org.openjdk.jmh
jmh-core
- ${jmh.version}
test
org.openjdk.jmh
jmh-generator-annprocess
- ${jmh.version}
test
diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml
index b3c39e64c3..e88d0a11c5 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
- 4.0.0-SNAPSHOT
+ 4.0.0-1803-projection-SNAPSHOT
../pom.xml
diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml
index e61fd64020..39ff7163a5 100644
--- a/spring-data-jdbc/pom.xml
+++ b/spring-data-jdbc/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-jdbc
- 4.0.0-SNAPSHOT
+ 4.0.0-1803-projection-SNAPSHOT
Spring Data JDBC
Spring Data module for JDBC repositories.
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-relational-parent
- 4.0.0-SNAPSHOT
+ 4.0.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 7ac637e8c3..4cc3e9aa7b 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
@@ -23,6 +23,7 @@
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
import org.springframework.data.mapping.PersistentPropertyPath;
+import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.dialect.RenderContextFactory;
@@ -36,6 +37,7 @@
import org.springframework.data.relational.core.sql.render.RenderContext;
import org.springframework.data.relational.core.sql.render.SqlRenderer;
import org.springframework.data.util.Lazy;
+import org.springframework.data.util.Predicates;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -507,45 +509,84 @@ 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<>();
+
+ for (SqlIdentifier columnName : query.getColumns()) {
+
+ try {
+ AggregatePath aggregatePath = mappingContext.getAggregatePath(
+ mappingContext.getPersistentPropertyPath(columnName.getReference(), entity.getTypeInformation()));
+
+ includeColumnAndJoin(aggregatePath, joins, columns);
+ } catch (InvalidPersistentPropertyPath e) {
+ columns.add(Column.create(columnName, table));
}
+ }
+
+ if (columns.isEmpty()) {
- Column column = getColumn(extPath);
- if (column != null) {
- columnExpressions.add(column);
+ for (PersistentPropertyPath path : mappingContext
+ .findPersistentPropertyPaths(entity.getType(), Predicates.isTrue())) {
+
+ AggregatePath aggregatePath = mappingContext.getAggregatePath(path);
+
+ includeColumnAndJoin(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 includeColumnAndJoin(AggregatePath aggregatePath, Set joins, Set columns) {
+
+ joins.addAll(getJoins(aggregatePath));
+
+ Column column = getColumn(aggregatePath);
+ if (column != null) {
+ columns.add(column);
}
+ }
- return (SelectBuilder.SelectWhere) baseSelect;
+ /**
+ * Projection including its source joins.
+ *
+ * @param columns
+ * @param joins
+ */
+ record Projection(Set columns, Set joins) {
}
private SelectBuilder.SelectOrdered selectBuilder(Collection keyColumns, Sort sort,
@@ -598,10 +639,7 @@ Column getColumn(AggregatePath path) {
// Simple entities without id include there backreference as a synthetic id in order to distinguish null entities
// from entities with only null values.
- if (path.isQualified() //
- || path.isCollectionLike() //
- || path.hasIdProperty() //
- ) {
+ if (path.isQualified() || path.isCollectionLike() || path.hasIdProperty()) {
return null;
}
@@ -611,9 +649,24 @@ Column getColumn(AggregatePath path) {
return sqlContext.getColumn(path);
}
+ List getJoins(AggregatePath path) {
+
+ List joins = new ArrayList<>();
+ while (!path.isRoot()) {
+ Join join = getJoin(path);
+ if (join != null) {
+ joins.add(join);
+ }
+
+ path = path.getParentPath();
+ }
+ return joins;
+ }
+
@Nullable
Join getJoin(AggregatePath path) {
+ // TODO: This doesn't handle paths with length > 1 correctly
if (!path.isEntity() || path.isEmbedded() || path.isMultiValued()) {
return null;
}
@@ -876,7 +929,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 00ce1278c7..cfa0416645 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 java.util.stream.Stream;
+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
@@ -2283,5 +2302,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 cc264cbe62..62e95245d7 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
@@ -32,6 +32,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Version;
@@ -351,12 +352,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", //
@@ -364,6 +366,45 @@ 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-1803
+ void selectingSetContentSelectsAllColumns() {
+
+ Query query = Query.empty().columns("elements.content");
+
+ String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource());
+
+ assertThat(sql).contains( //
+ "SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name"//
+ );
+ }
+
+ @Test // GH-1803
+ void selectByQueryWithMappedColumnPathsRendersCorrectSelection() {
+
+ Query query = Query.empty().columns("ref.content");
+
+ String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource());
+
+ assertThat(sql).contains( //
+ "SELECT", //
+ "ref.x_content AS ref_x_content", //
+ "FROM dummy_entity", //
+ "LEFT OUTER JOIN referenced_entity ref ON ref.dummy_entity = dummy_entity.id1");
+ }
+
@Test // GH-1919
void selectBySortedQuery() {
@@ -381,7 +422,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 3ee76fd3c1..d73dc4c5b5 100644
--- a/spring-data-r2dbc/pom.xml
+++ b/spring-data-r2dbc/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-r2dbc
- 4.0.0-SNAPSHOT
+ 4.0.0-1803-projection-SNAPSHOT
Spring Data R2DBC
Spring Data module for R2DBC
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-relational-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-1803-projection-SNAPSHOT
diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml
index 8fd6d7a6f0..d14de98951 100644
--- a/spring-data-relational/pom.xml
+++ b/spring-data-relational/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-relational
- 4.0.0-SNAPSHOT
+ 4.0.0-1803-projection-SNAPSHOT
Spring Data Relational
Spring Data Relational support
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-relational-parent
- 4.0.0-SNAPSHOT
+ 4.0.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;
}
/**