Skip to content

Commit 8c0d01a

Browse files
committed
#344 - Polishing.
Consider projection properties as SELECT projection. Refactor distinct() into marker method without accepting a boolean flag. Make distinct field final. Update tests. Original pull request: #346.
1 parent dd7c713 commit 8c0d01a

File tree

6 files changed

+90
-30
lines changed

6 files changed

+90
-30
lines changed

src/main/java/org/springframework/data/r2dbc/core/DefaultStatementMapper.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,20 @@ private PreparedOperation<Select> getMappedObject(SelectSpec selectSpec,
8686

8787
Table table = selectSpec.getTable();
8888
SelectBuilder.SelectAndFrom selectAndFrom = StatementBuilder.select(getSelectList(selectSpec, entity));
89-
if(selectSpec.isDistinct()){
89+
90+
if (selectSpec.isDistinct()) {
9091
selectAndFrom = selectAndFrom.distinct();
9192
}
93+
9294
SelectBuilder.SelectFromAndJoin selectBuilder = selectAndFrom.from(table);
9395

9496
BindMarkers bindMarkers = this.dialect.getBindMarkersFactory().create();
9597
Bindings bindings = Bindings.empty();
98+
CriteriaDefinition criteria = selectSpec.getCriteria();
9699

97-
if (!selectSpec.getCriteria().isEmpty()) {
100+
if (criteria != null && !criteria.isEmpty()) {
98101

99-
BoundCondition mappedObject = this.updateMapper.getMappedObject(bindMarkers, selectSpec.getCriteria(), table,
100-
entity);
102+
BoundCondition mappedObject = this.updateMapper.getMappedObject(bindMarkers, criteria, table, entity);
101103

102104
bindings = mappedObject.getBindings();
103105
selectBuilder.where(mappedObject.getCondition());

src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java

+7-7
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ class SelectSpec {
202202
private final Sort sort;
203203
private final long offset;
204204
private final int limit;
205-
private boolean distinct = false;
205+
private final boolean distinct;
206206

207207
protected SelectSpec(Table table, List<String> projectedFields, List<Expression> selectList,
208208
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct) {
@@ -237,8 +237,8 @@ public static SelectSpec create(SqlIdentifier table) {
237237

238238
List<String> projectedFields = Collections.emptyList();
239239
List<Expression> selectList = Collections.emptyList();
240-
return new SelectSpec(Table.create(table), projectedFields, selectList, Criteria.empty(), Sort.unsorted(), -1,
241-
-1, false);
240+
return new SelectSpec(Table.create(table), projectedFields, selectList, Criteria.empty(), Sort.unsorted(), -1, -1,
241+
false);
242242
}
243243

244244
public SelectSpec doWithTable(BiFunction<Table, SelectSpec, SelectSpec> function) {
@@ -279,7 +279,8 @@ public SelectSpec withProjection(Expression... expressions) {
279279
List<Expression> selectList = new ArrayList<>(this.selectList);
280280
selectList.addAll(Arrays.asList(expressions));
281281

282-
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset, this.distinct);
282+
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset,
283+
this.distinct);
283284
}
284285

285286
/**
@@ -371,12 +372,11 @@ public SelectSpec limit(int limit) {
371372
/**
372373
* Associate a result statement distinct with the select and create a new {@link SelectSpec}.
373374
*
374-
* @param distinct
375375
* @return the {@link SelectSpec}.
376376
*/
377-
public SelectSpec distinct(boolean distinct) {
377+
public SelectSpec distinct() {
378378
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
379-
this.offset, distinct);
379+
this.offset, true);
380380
}
381381

382382
public Table getTable() {

src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
package org.springframework.data.r2dbc.repository.query;
1717

18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.List;
21+
1822
import org.springframework.data.domain.Sort;
1923
import org.springframework.data.r2dbc.convert.R2dbcConverter;
2024
import org.springframework.data.r2dbc.core.DatabaseClient;
@@ -23,6 +27,8 @@
2327
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
2428
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
2529
import org.springframework.data.relational.repository.query.RelationalParameters;
30+
import org.springframework.data.repository.query.ResultProcessor;
31+
import org.springframework.data.repository.query.ReturnedType;
2632
import org.springframework.data.repository.query.parser.PartTree;
2733

2834
/**
@@ -34,6 +40,7 @@
3440
*/
3541
public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
3642

43+
private final ResultProcessor processor;
3744
private final ReactiveDataAccessStrategy dataAccessStrategy;
3845
private final RelationalParameters parameters;
3946
private final PartTree tree;
@@ -50,6 +57,8 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
5057
public PartTreeR2dbcQuery(R2dbcQueryMethod method, DatabaseClient databaseClient, R2dbcConverter converter,
5158
ReactiveDataAccessStrategy dataAccessStrategy) {
5259
super(method, databaseClient, converter);
60+
61+
this.processor = method.getResultProcessor();
5362
this.dataAccessStrategy = dataAccessStrategy;
5463
this.parameters = method.getParameters();
5564

@@ -78,8 +87,16 @@ protected boolean isModifyingQuery() {
7887
@Override
7988
protected BindableQuery createQuery(RelationalParameterAccessor accessor) {
8089

90+
ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType();
91+
List<String> projectedProperties = Collections.emptyList();
92+
93+
if (returnedType.needsCustomConstruction()) {
94+
projectedProperties = new ArrayList<>(returnedType.getInputProperties());
95+
}
96+
8197
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
82-
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor);
98+
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor,
99+
projectedProperties);
83100
PreparedOperation<?> preparedQuery = queryCreator.createQuery(getDynamicSort(accessor));
84101

85102
return new PreparedOperationBindableQuery(preparedQuery);

src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryCreator.java

+26-13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.r2dbc.repository.query;
1717

18+
import java.util.ArrayList;
1819
import java.util.List;
1920
import java.util.stream.Collectors;
2021

@@ -32,7 +33,7 @@
3233
import org.springframework.data.relational.repository.query.RelationalQueryCreator;
3334
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
3435
import org.springframework.data.repository.query.parser.PartTree;
35-
import org.springframework.util.Assert;
36+
import org.springframework.lang.Nullable;
3637

3738
/**
3839
* Implementation of {@link AbstractQueryCreator} that creates {@link PreparedOperation} from a {@link PartTree}.
@@ -42,34 +43,35 @@
4243
* @author Mingyuan Wu
4344
* @since 1.1
4445
*/
45-
public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<?>> {
46+
class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<?>> {
4647

4748
private final PartTree tree;
4849
private final RelationalParameterAccessor accessor;
4950
private final ReactiveDataAccessStrategy dataAccessStrategy;
5051
private final RelationalEntityMetadata<?> entityMetadata;
52+
private final List<String> projectedProperties;
5153

5254
/**
5355
* Creates new instance of this class with the given {@link PartTree}, {@link ReactiveDataAccessStrategy},
5456
* {@link RelationalEntityMetadata} and {@link RelationalParameterAccessor}.
55-
*
57+
*
5658
* @param tree part tree, must not be {@literal null}.
5759
* @param dataAccessStrategy data access strategy, must not be {@literal null}.
5860
* @param entityMetadata relational entity metadata, must not be {@literal null}.
5961
* @param accessor parameter metadata provider, must not be {@literal null}.
62+
* @param projectedProperties properties to project, must not be {@literal null}.
6063
*/
6164
public R2dbcQueryCreator(PartTree tree, ReactiveDataAccessStrategy dataAccessStrategy,
62-
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor) {
65+
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor,
66+
List<String> projectedProperties) {
6367
super(tree, accessor);
6468

65-
Assert.notNull(dataAccessStrategy, "Data access strategy must not be null");
66-
Assert.notNull(entityMetadata, "Relational entity metadata must not be null");
67-
6869
this.tree = tree;
6970
this.accessor = accessor;
7071

7172
this.dataAccessStrategy = dataAccessStrategy;
7273
this.entityMetadata = entityMetadata;
74+
this.projectedProperties = projectedProperties;
7375
}
7476

7577
/**
@@ -80,7 +82,7 @@ public R2dbcQueryCreator(PartTree tree, ReactiveDataAccessStrategy dataAccessStr
8082
* @return instance of {@link PreparedOperation}
8183
*/
8284
@Override
83-
protected PreparedOperation<?> complete(Criteria criteria, Sort sort) {
85+
protected PreparedOperation<?> complete(@Nullable Criteria criteria, Sort sort) {
8486

8587
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityMetadata.getJavaType());
8688

@@ -91,15 +93,15 @@ protected PreparedOperation<?> complete(Criteria criteria, Sort sort) {
9193
return select(criteria, sort, statementMapper);
9294
}
9395

94-
private PreparedOperation<?> delete(Criteria criteria, StatementMapper statementMapper) {
96+
private PreparedOperation<?> delete(@Nullable Criteria criteria, StatementMapper statementMapper) {
9597

9698
StatementMapper.DeleteSpec deleteSpec = statementMapper.createDelete(entityMetadata.getTableName())
9799
.withCriteria(criteria);
98100

99101
return statementMapper.getMappedObject(deleteSpec);
100102
}
101103

102-
private PreparedOperation<?> select(Criteria criteria, Sort sort, StatementMapper statementMapper) {
104+
private PreparedOperation<?> select(@Nullable Criteria criteria, Sort sort, StatementMapper statementMapper) {
103105

104106
StatementMapper.SelectSpec selectSpec = statementMapper.createSelect(entityMetadata.getTableName())
105107
.withProjection(getSelectProjection());
@@ -123,8 +125,8 @@ private PreparedOperation<?> select(Criteria criteria, Sort sort, StatementMappe
123125
selectSpec = selectSpec.withSort(getSort(sort));
124126
}
125127

126-
if(tree.isDistinct()){
127-
selectSpec = selectSpec.distinct(true);
128+
if (tree.isDistinct()) {
129+
selectSpec = selectSpec.distinct();
128130
}
129131

130132
return statementMapper.getMappedObject(selectSpec);
@@ -134,7 +136,18 @@ private SqlIdentifier[] getSelectProjection() {
134136

135137
List<SqlIdentifier> columnNames;
136138

137-
if (tree.isExistsProjection()) {
139+
if (!projectedProperties.isEmpty()) {
140+
141+
RelationalPersistentEntity<?> entity = entityMetadata.getTableEntity();
142+
columnNames = new ArrayList<>(projectedProperties.size());
143+
144+
for (String projectedProperty : projectedProperties) {
145+
146+
RelationalPersistentProperty property = entity.getPersistentProperty(projectedProperty);
147+
columnNames.add(property != null ? property.getColumnName() : SqlIdentifier.unquoted(projectedProperty));
148+
}
149+
150+
} else if (tree.isExistsProjection()) {
138151
columnNames = dataAccessStrategy.getIdentifierColumns(entityMetadata.getJavaType());
139152
} else {
140153
columnNames = dataAccessStrategy.getAllColumns(entityMetadata.getJavaType());

src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryIntegrationTests.java

+22
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,26 @@ public void shouldFindApplyingProjection() {
156156
}).verifyComplete();
157157
}
158158

159+
@Test // gh-344
160+
public void shouldFindApplyingDistinctProjection() {
161+
162+
LegoSet legoSet1 = new LegoSet(null, "SCHAUFELRADBAGGER", 12);
163+
LegoSet legoSet2 = new LegoSet(null, "SCHAUFELRADBAGGER", 13);
164+
165+
repository.saveAll(Arrays.asList(legoSet1, legoSet2)) //
166+
.as(StepVerifier::create) //
167+
.expectNextCount(2) //
168+
.verifyComplete();
169+
170+
repository.findDistinctBy() //
171+
.map(Named::getName) //
172+
.collectList() //
173+
.as(StepVerifier::create) //
174+
.consumeNextWith(actual -> {
175+
assertThat(actual).hasSize(1).contains("SCHAUFELRADBAGGER");
176+
}).verifyComplete();
177+
}
178+
159179
@Test // gh-41
160180
public void shouldFindApplyingSimpleTypeProjection() {
161181

@@ -282,6 +302,8 @@ interface LegoSetRepository extends ReactiveCrudRepository<LegoSet, Integer> {
282302

283303
Flux<Named> findAsProjection();
284304

305+
Flux<Named> findDistinctBy();
306+
285307
Mono<LegoSet> findByManual(int manual);
286308

287309
Flux<Integer> findAllIds();

src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java

+11-5
Original file line numberDiff line numberDiff line change
@@ -614,11 +614,10 @@ public void createsQueryToFindAllEntitiesByStringAttributeWithDistinct() throws
614614
dataAccessStrategy);
615615
BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }));
616616

617-
assertThat(bindableQuery.get())
618-
.isEqualTo("SELECT " + DISTINCT + " " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
617+
assertThat(bindableQuery.get()).isEqualTo("SELECT " + DISTINCT + " " + TABLE + ".first_name, " + TABLE
618+
+ ".foo FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
619619
}
620620

621-
622621
private R2dbcQueryMethod getQueryMethod(String methodName, Class<?>... parameterTypes) throws Exception {
623622
Method method = UserRepository.class.getMethod(methodName, parameterTypes);
624623
return new R2dbcQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
@@ -696,8 +695,8 @@ interface UserRepository extends Repository<User, Long> {
696695
Flux<User> findTop3ByFirstName(String firstName);
697696

698697
Mono<User> findFirstByFirstName(String firstName);
699-
700-
Mono<User> findDistinctByFirstName(String firstName);
698+
699+
Mono<UserProjection> findDistinctByFirstName(String firstName);
701700

702701
Mono<Integer> deleteByFirstName(String firstName);
703702
}
@@ -713,4 +712,11 @@ private static class User {
713712
private Integer age;
714713
private Boolean active;
715714
}
715+
716+
interface UserProjection {
717+
718+
String getFirstName();
719+
720+
String getFoo();
721+
}
716722
}

0 commit comments

Comments
 (0)