#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.
This commit is contained in:
Mark Paluch
2020-04-21 15:30:09 +02:00
parent dd7c713980
commit 8c0d01ae8c
6 changed files with 90 additions and 30 deletions

View File

@@ -86,18 +86,20 @@ class DefaultStatementMapper implements StatementMapper {
Table table = selectSpec.getTable();
SelectBuilder.SelectAndFrom selectAndFrom = StatementBuilder.select(getSelectList(selectSpec, entity));
if(selectSpec.isDistinct()){
if (selectSpec.isDistinct()) {
selectAndFrom = selectAndFrom.distinct();
}
SelectBuilder.SelectFromAndJoin selectBuilder = selectAndFrom.from(table);
BindMarkers bindMarkers = this.dialect.getBindMarkersFactory().create();
Bindings bindings = Bindings.empty();
CriteriaDefinition criteria = selectSpec.getCriteria();
if (!selectSpec.getCriteria().isEmpty()) {
if (criteria != null && !criteria.isEmpty()) {
BoundCondition mappedObject = this.updateMapper.getMappedObject(bindMarkers, selectSpec.getCriteria(), table,
entity);
BoundCondition mappedObject = this.updateMapper.getMappedObject(bindMarkers, criteria, table, entity);
bindings = mappedObject.getBindings();
selectBuilder.where(mappedObject.getCondition());

View File

@@ -202,7 +202,7 @@ public interface StatementMapper {
private final Sort sort;
private final long offset;
private final int limit;
private boolean distinct = false;
private final boolean distinct;
protected SelectSpec(Table table, List<String> projectedFields, List<Expression> selectList,
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct) {
@@ -237,8 +237,8 @@ public interface StatementMapper {
List<String> projectedFields = Collections.emptyList();
List<Expression> selectList = Collections.emptyList();
return new SelectSpec(Table.create(table), projectedFields, selectList, Criteria.empty(), Sort.unsorted(), -1,
-1, false);
return new SelectSpec(Table.create(table), projectedFields, selectList, Criteria.empty(), Sort.unsorted(), -1, -1,
false);
}
public SelectSpec doWithTable(BiFunction<Table, SelectSpec, SelectSpec> function) {
@@ -279,7 +279,8 @@ public interface StatementMapper {
List<Expression> selectList = new ArrayList<>(this.selectList);
selectList.addAll(Arrays.asList(expressions));
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset, this.distinct);
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset,
this.distinct);
}
/**
@@ -371,12 +372,11 @@ public interface StatementMapper {
/**
* Associate a result statement distinct with the select and create a new {@link SelectSpec}.
*
* @param distinct
* @return the {@link SelectSpec}.
*/
public SelectSpec distinct(boolean distinct) {
public SelectSpec distinct() {
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
this.offset, distinct);
this.offset, true);
}
public Table getTable() {

View File

@@ -15,6 +15,10 @@
*/
package org.springframework.data.r2dbc.repository.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.core.DatabaseClient;
@@ -23,6 +27,8 @@ import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalParameters;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.parser.PartTree;
/**
@@ -34,6 +40,7 @@ import org.springframework.data.repository.query.parser.PartTree;
*/
public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
private final ResultProcessor processor;
private final ReactiveDataAccessStrategy dataAccessStrategy;
private final RelationalParameters parameters;
private final PartTree tree;
@@ -50,6 +57,8 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
public PartTreeR2dbcQuery(R2dbcQueryMethod method, DatabaseClient databaseClient, R2dbcConverter converter,
ReactiveDataAccessStrategy dataAccessStrategy) {
super(method, databaseClient, converter);
this.processor = method.getResultProcessor();
this.dataAccessStrategy = dataAccessStrategy;
this.parameters = method.getParameters();
@@ -78,8 +87,16 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
@Override
protected BindableQuery createQuery(RelationalParameterAccessor accessor) {
ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType();
List<String> projectedProperties = Collections.emptyList();
if (returnedType.needsCustomConstruction()) {
projectedProperties = new ArrayList<>(returnedType.getInputProperties());
}
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor);
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor,
projectedProperties);
PreparedOperation<?> preparedQuery = queryCreator.createQuery(getDynamicSort(accessor));
return new PreparedOperationBindableQuery(preparedQuery);

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.r2dbc.repository.query;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -32,7 +33,7 @@ import org.springframework.data.relational.repository.query.RelationalParameterA
import org.springframework.data.relational.repository.query.RelationalQueryCreator;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert;
import org.springframework.lang.Nullable;
/**
* Implementation of {@link AbstractQueryCreator} that creates {@link PreparedOperation} from a {@link PartTree}.
@@ -42,34 +43,35 @@ import org.springframework.util.Assert;
* @author Mingyuan Wu
* @since 1.1
*/
public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<?>> {
class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<?>> {
private final PartTree tree;
private final RelationalParameterAccessor accessor;
private final ReactiveDataAccessStrategy dataAccessStrategy;
private final RelationalEntityMetadata<?> entityMetadata;
private final List<String> projectedProperties;
/**
* Creates new instance of this class with the given {@link PartTree}, {@link ReactiveDataAccessStrategy},
* {@link RelationalEntityMetadata} and {@link RelationalParameterAccessor}.
*
*
* @param tree part tree, must not be {@literal null}.
* @param dataAccessStrategy data access strategy, must not be {@literal null}.
* @param entityMetadata relational entity metadata, must not be {@literal null}.
* @param accessor parameter metadata provider, must not be {@literal null}.
* @param projectedProperties properties to project, must not be {@literal null}.
*/
public R2dbcQueryCreator(PartTree tree, ReactiveDataAccessStrategy dataAccessStrategy,
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor) {
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor,
List<String> projectedProperties) {
super(tree, accessor);
Assert.notNull(dataAccessStrategy, "Data access strategy must not be null");
Assert.notNull(entityMetadata, "Relational entity metadata must not be null");
this.tree = tree;
this.accessor = accessor;
this.dataAccessStrategy = dataAccessStrategy;
this.entityMetadata = entityMetadata;
this.projectedProperties = projectedProperties;
}
/**
@@ -80,7 +82,7 @@ public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<
* @return instance of {@link PreparedOperation}
*/
@Override
protected PreparedOperation<?> complete(Criteria criteria, Sort sort) {
protected PreparedOperation<?> complete(@Nullable Criteria criteria, Sort sort) {
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityMetadata.getJavaType());
@@ -91,7 +93,7 @@ public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<
return select(criteria, sort, statementMapper);
}
private PreparedOperation<?> delete(Criteria criteria, StatementMapper statementMapper) {
private PreparedOperation<?> delete(@Nullable Criteria criteria, StatementMapper statementMapper) {
StatementMapper.DeleteSpec deleteSpec = statementMapper.createDelete(entityMetadata.getTableName())
.withCriteria(criteria);
@@ -99,7 +101,7 @@ public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<
return statementMapper.getMappedObject(deleteSpec);
}
private PreparedOperation<?> select(Criteria criteria, Sort sort, StatementMapper statementMapper) {
private PreparedOperation<?> select(@Nullable Criteria criteria, Sort sort, StatementMapper statementMapper) {
StatementMapper.SelectSpec selectSpec = statementMapper.createSelect(entityMetadata.getTableName())
.withProjection(getSelectProjection());
@@ -123,8 +125,8 @@ public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<
selectSpec = selectSpec.withSort(getSort(sort));
}
if(tree.isDistinct()){
selectSpec = selectSpec.distinct(true);
if (tree.isDistinct()) {
selectSpec = selectSpec.distinct();
}
return statementMapper.getMappedObject(selectSpec);
@@ -134,7 +136,18 @@ public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<
List<SqlIdentifier> columnNames;
if (tree.isExistsProjection()) {
if (!projectedProperties.isEmpty()) {
RelationalPersistentEntity<?> entity = entityMetadata.getTableEntity();
columnNames = new ArrayList<>(projectedProperties.size());
for (String projectedProperty : projectedProperties) {
RelationalPersistentProperty property = entity.getPersistentProperty(projectedProperty);
columnNames.add(property != null ? property.getColumnName() : SqlIdentifier.unquoted(projectedProperty));
}
} else if (tree.isExistsProjection()) {
columnNames = dataAccessStrategy.getIdentifierColumns(entityMetadata.getJavaType());
} else {
columnNames = dataAccessStrategy.getAllColumns(entityMetadata.getJavaType());

View File

@@ -156,6 +156,26 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
}).verifyComplete();
}
@Test // gh-344
public void shouldFindApplyingDistinctProjection() {
LegoSet legoSet1 = new LegoSet(null, "SCHAUFELRADBAGGER", 12);
LegoSet legoSet2 = new LegoSet(null, "SCHAUFELRADBAGGER", 13);
repository.saveAll(Arrays.asList(legoSet1, legoSet2)) //
.as(StepVerifier::create) //
.expectNextCount(2) //
.verifyComplete();
repository.findDistinctBy() //
.map(Named::getName) //
.collectList() //
.as(StepVerifier::create) //
.consumeNextWith(actual -> {
assertThat(actual).hasSize(1).contains("SCHAUFELRADBAGGER");
}).verifyComplete();
}
@Test // gh-41
public void shouldFindApplyingSimpleTypeProjection() {
@@ -282,6 +302,8 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
Flux<Named> findAsProjection();
Flux<Named> findDistinctBy();
Mono<LegoSet> findByManual(int manual);
Flux<Integer> findAllIds();

View File

@@ -614,11 +614,10 @@ public class PartTreeR2dbcQueryUnitTests {
dataAccessStrategy);
BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }));
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + DISTINCT + " " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
assertThat(bindableQuery.get()).isEqualTo("SELECT " + DISTINCT + " " + TABLE + ".first_name, " + TABLE
+ ".foo FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
}
private R2dbcQueryMethod getQueryMethod(String methodName, Class<?>... parameterTypes) throws Exception {
Method method = UserRepository.class.getMethod(methodName, parameterTypes);
return new R2dbcQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
@@ -696,8 +695,8 @@ public class PartTreeR2dbcQueryUnitTests {
Flux<User> findTop3ByFirstName(String firstName);
Mono<User> findFirstByFirstName(String firstName);
Mono<User> findDistinctByFirstName(String firstName);
Mono<UserProjection> findDistinctByFirstName(String firstName);
Mono<Integer> deleteByFirstName(String firstName);
}
@@ -713,4 +712,11 @@ public class PartTreeR2dbcQueryUnitTests {
private Integer age;
private Boolean active;
}
interface UserProjection {
String getFirstName();
String getFoo();
}
}