#344 - Add support for support distinct derived query methods.
We now support distinct derived queries that are useful with projections to retrieve distinct results.
interface UserRepository extends Repository<User, Long> {
Mono<UserProjection> findDistinctByFirstName(String firstName);
}
interface UserProjection {
String getFirstName();
}
Original pull request: #346.
This commit is contained in:
@@ -41,6 +41,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Roman Chigvintsev
|
||||
* @author Mingyuan Wu
|
||||
*/
|
||||
class DefaultStatementMapper implements StatementMapper {
|
||||
|
||||
@@ -84,8 +85,11 @@ class DefaultStatementMapper implements StatementMapper {
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Table table = selectSpec.getTable();
|
||||
SelectBuilder.SelectFromAndJoin selectBuilder = StatementBuilder.select(getSelectList(selectSpec, entity))
|
||||
.from(table);
|
||||
SelectBuilder.SelectAndFrom selectAndFrom = StatementBuilder.select(getSelectList(selectSpec, entity));
|
||||
if(selectSpec.isDistinct()){
|
||||
selectAndFrom = selectAndFrom.distinct();
|
||||
}
|
||||
SelectBuilder.SelectFromAndJoin selectBuilder = selectAndFrom.from(table);
|
||||
|
||||
BindMarkers bindMarkers = this.dialect.getBindMarkersFactory().create();
|
||||
Bindings bindings = Bindings.empty();
|
||||
|
||||
@@ -202,9 +202,10 @@ public interface StatementMapper {
|
||||
private final Sort sort;
|
||||
private final long offset;
|
||||
private final int limit;
|
||||
private boolean distinct = false;
|
||||
|
||||
protected SelectSpec(Table table, List<String> projectedFields, List<Expression> selectList,
|
||||
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset) {
|
||||
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct) {
|
||||
this.table = table;
|
||||
this.projectedFields = projectedFields;
|
||||
this.selectList = selectList;
|
||||
@@ -212,6 +213,7 @@ public interface StatementMapper {
|
||||
this.sort = sort;
|
||||
this.offset = offset;
|
||||
this.limit = limit;
|
||||
this.distinct = distinct;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,7 +238,7 @@ 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);
|
||||
-1, false);
|
||||
}
|
||||
|
||||
public SelectSpec doWithTable(BiFunction<Table, SelectSpec, SelectSpec> function) {
|
||||
@@ -277,7 +279,7 @@ 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);
|
||||
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,7 +295,7 @@ public interface StatementMapper {
|
||||
selectList.addAll(projectedFields);
|
||||
|
||||
return new SelectSpec(this.table, this.projectedFields, selectList, this.criteria, this.sort, this.limit,
|
||||
this.offset);
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,7 +306,7 @@ public interface StatementMapper {
|
||||
*/
|
||||
public SelectSpec withCriteria(CriteriaDefinition criteria) {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, criteria, this.sort, this.limit,
|
||||
this.offset);
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,11 +319,11 @@ public interface StatementMapper {
|
||||
|
||||
if (sort.isSorted()) {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, sort, this.limit,
|
||||
this.offset);
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
|
||||
this.offset);
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,33 +339,44 @@ public interface StatementMapper {
|
||||
Sort sort = page.getSort();
|
||||
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria,
|
||||
sort.isSorted() ? sort : this.sort, page.getPageSize(), page.getOffset());
|
||||
sort.isSorted() ? sort : this.sort, page.getPageSize(), page.getOffset(), this.distinct);
|
||||
}
|
||||
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
|
||||
this.offset);
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a result offset with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param page
|
||||
* @param offset
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
public SelectSpec offset(long offset) {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
|
||||
offset);
|
||||
offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a result limit with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param page
|
||||
* @param limit
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
public SelectSpec limit(int limit) {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
|
||||
this.offset);
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
|
||||
this.offset, distinct);
|
||||
}
|
||||
|
||||
public Table getTable() {
|
||||
@@ -399,6 +412,10 @@ public interface StatementMapper {
|
||||
public int getLimit() {
|
||||
return this.limit;
|
||||
}
|
||||
|
||||
public boolean isDistinct() {
|
||||
return this.distinct;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -123,6 +123,10 @@ public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<
|
||||
selectSpec = selectSpec.withSort(getSort(sort));
|
||||
}
|
||||
|
||||
if(tree.isDistinct()){
|
||||
selectSpec = selectSpec.distinct(true);
|
||||
}
|
||||
|
||||
return statementMapper.getMappedObject(selectSpec);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ public class PartTreeR2dbcQueryUnitTests {
|
||||
private static final String TABLE = "users";
|
||||
private static final String ALL_FIELDS = TABLE + ".id, " + TABLE + ".first_name, " + TABLE + ".last_name, " + TABLE
|
||||
+ ".date_of_birth, " + TABLE + ".age, " + TABLE + ".active";
|
||||
private static final String DISTINCT = "DISTINCT";
|
||||
|
||||
@Mock ConnectionFactory connectionFactory;
|
||||
@Mock R2dbcConverter r2dbcConverter;
|
||||
@@ -605,6 +606,19 @@ public class PartTreeR2dbcQueryUnitTests {
|
||||
assertThat(bindableQuery.get()).isEqualTo("DELETE FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
|
||||
}
|
||||
|
||||
@Test // gh-344
|
||||
public void createsQueryToFindAllEntitiesByStringAttributeWithDistinct() throws Exception {
|
||||
|
||||
R2dbcQueryMethod queryMethod = getQueryMethod("findDistinctByFirstName", String.class);
|
||||
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
private R2dbcQueryMethod getQueryMethod(String methodName, Class<?>... parameterTypes) throws Exception {
|
||||
Method method = UserRepository.class.getMethod(methodName, parameterTypes);
|
||||
return new R2dbcQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
|
||||
@@ -682,6 +696,8 @@ public class PartTreeR2dbcQueryUnitTests {
|
||||
Flux<User> findTop3ByFirstName(String firstName);
|
||||
|
||||
Mono<User> findFirstByFirstName(String firstName);
|
||||
|
||||
Mono<User> findDistinctByFirstName(String firstName);
|
||||
|
||||
Mono<Integer> deleteByFirstName(String firstName);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user