Skip to content

Support derived queries from methods named findDistinctF1AndF2ByF3() #1202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public Flux<T> all() {
}).flatMapMany(ReactiveQueryResult::rowsAsObject).flatMap(row -> {
String id = "";
long cas = 0;
if (distinctFields == null) {
if (!query.isDistinct() && distinctFields == null) {
if (row.getString(TemplateUtils.SELECT_ID) == null) {
return Flux.error(new CouchbaseException(
"query did not project " + TemplateUtils.SELECT_ID + ". Either use #{#n1ql.selectEntity} or project "
Expand Down Expand Up @@ -227,7 +227,8 @@ public Mono<Boolean> exists() {
}

private String assembleEntityQuery(final boolean count, String[] distinctFields, String collection) {
return query.toN1qlSelectString(template, collection, this.domainType, this.returnType, count, distinctFields);
return query.toN1qlSelectString(template, collection, this.domainType, this.returnType, count,
query.getDistinctFields() != null ? query.getDistinctFields() : distinctFields);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public class Query {
private JsonValue parameters = JsonValue.ja();
private long skip;
private int limit;
private boolean distinct;
private String[] distinctFields;
private Sort sort = Sort.unsorted();
private QueryScanConsistency queryScanConsistency;
private Meta meta;
Expand Down Expand Up @@ -123,6 +125,46 @@ public Query limit(int limit) {
return this;
}

/**
* Is this a DISTINCT query? {@code distinct}.
*
* @param distinct
* @return
*/
public Query distinct(boolean distinct) {
this.distinct = distinct;
return this;
}

/**
* Is this a DISTINCT query? {@code distinct}.
*
* @return distinct
*/
public boolean isDistinct() {
return distinct;
}

/**
* distinctFields for query (non-null but empty means all fields) ? {@code distinctFields}.
*
* @param distinctFields
* @return
*/
public Query distinct(String[] distinctFields) {
this.distinctFields = distinctFields;
return this;
}

/**
* distinctFields for query (non-null but empty means all fields) ? {@code distinctFields}.
*
* @return distinctFields
*/
public String[] getDistinctFields() {
return distinctFields;
}

/**
* Sets the given pagination information on the {@link Query} instance. Will transparently set {@code skip} and
* {@code limit} as well as applying the {@link Sort} instance defined with the {@link Pageable}.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.data.couchbase.repository.query;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;

/**
* Extend PartTree to parse out distinct fields
*
* @author Michael Reiche
*/
public class CouchbasePartTree extends PartTree {
private static final Pattern DISTINCT_TEMPLATE = Pattern
.compile("^(find|read|get|query|search|stream|count)(Distinct)(\\p{Lu}.*?)(First|Top|By|$)");

String[] distinctFields;

public CouchbasePartTree(String methodName, Class<?> domainType) {
super(methodName, domainType);
maybeInitDistinctFields(methodName, domainType);
}

String[] getDistinctFields() {
return distinctFields;
}

private void maybeInitDistinctFields(String methodName, Class<?> domainType) {
if (isDistinct()) {
Matcher grp = DISTINCT_TEMPLATE.matcher(methodName);
if (grp.matches()) {
String grp3 = grp.group(3);
String[] names = grp.group(3).split("And");
int parameterCount = names.length;
distinctFields = new String[names.length];
for (int i = 0; i < parameterCount; ++i) {
Part.Type type = Part.Type.fromProperty(names[i]);
PropertyPath path = PropertyPath.from(type.extractProperty(names[i]), domainType);
distinctFields[i] = path.toDotPath();
}
} else {
distinctFields = new String[0];
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert;

/**
* @author Michael Nitschinger
Expand All @@ -52,6 +51,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
public static final String META_CAS_PROPERTY = "cas";
public static final String META_EXPIRATION_PROPERTY = "expiration";

private final PartTree tree;
private final ParameterAccessor accessor;
private final MappingContext<?, CouchbasePersistentProperty> context;
private final QueryMethod queryMethod;
Expand All @@ -61,6 +61,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
public N1qlQueryCreator(final PartTree tree, final ParameterAccessor accessor, final QueryMethod queryMethod,
final CouchbaseConverter converter, final String bucketName) {
super(tree, accessor);
this.tree = tree;
this.accessor = accessor;
this.context = converter.getMappingContext();
this.queryMethod = queryMethod;
Expand All @@ -79,10 +80,11 @@ protected QueryCriteria create(final Part part, final Iterator<Object> iterator)
public Query createQuery() {
Query q = this.createQuery((Optional.of(this.accessor).map(ParameterAccessor::getSort).orElse(Sort.unsorted())));
Pageable pageable = accessor.getPageable();
if(pageable.isPaged()) {
if (pageable.isPaged()) {
q.skip(pageable.getOffset());
q.limit(pageable.getPageSize());
}
q.distinct(tree.isDistinct());
return q;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,7 +35,7 @@
*/
public class PartTreeCouchbaseQuery extends AbstractCouchbaseQuery {

private final PartTree tree;
private final CouchbasePartTree tree;
private final CouchbaseConverter converter;

/**
Expand All @@ -52,7 +52,7 @@ public PartTreeCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperations o
super(method, operations, expressionParser, evaluationContextProvider);

ResultProcessor processor = method.getResultProcessor();
this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType());
this.tree = new CouchbasePartTree(method.getName(), processor.getReturnedType().getDomainType());
this.converter = operations.getConverter();
}

Expand All @@ -79,6 +79,9 @@ protected Query createQuery(ParametersParameterAccessor accessor) {
if (tree.isLimiting()) {
query.limit(tree.getMaxResults());
}
if (tree.isDistinct()) {
query.distinct(tree.getDistinctFields());
}
return query;

}
Expand Down Expand Up @@ -128,4 +131,5 @@ protected boolean isDeleteQuery() {
protected boolean isLimiting() {
return tree.isLimiting();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ Long countFancyExpression(@Param("projectIds") List<String> projectIds, @Param("
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Optional<Airport> findByIdAndIata(String id, String iata);

@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findDistinctIcaoBy();

@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findDistinctIcaoAndIataBy();

@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Long countDistinctIcaoAndIataBy();

@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Long countDistinctIcaoBy();

@Query("SELECT 1 FROM `#{#n1ql.bucket}` WHERE #{#n1ql.filter} " + " #{#projectIds != null ? 'AND blah IN $1' : ''} "
+ " #{#planIds != null ? 'AND blahblah IN $2' : ''} " + " #{#active != null ? 'AND false = $3' : ''} ")
Long countOne();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,38 @@ void threadSafeParametersTest() throws Exception {
}
}

@Test
void distinct() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
String[] icaos = { "ic0", "ic1", "ic0", "ic1", "ic0", "ic1", "ic0" };

try {
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, icaos[i] /* icao */);
couchbaseTemplate.insertById(Airport.class).one(airport);
}

// distinct icao - parser requires 'By' on the end or it does not match pattern.
List<Airport> airports1 = airportRepository.findDistinctIcaoBy();
assertEquals(2, airports1.size());

List<Airport> airports2 = airportRepository.findDistinctIcaoAndIataBy();
assertEquals(7, airports2.size());

// count( distinct { iata, icao } )
long count1 = airportRepository.countDistinctIcaoAndIataBy();
assertEquals(7, count1);

// count( distinct { icao } )
long count2 = airportRepository.countDistinctIcaoBy();
assertEquals(2, count2);

} finally {
couchbaseTemplate.removeById()
.all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet()));
}
}

@Test
void stringQueryTest() throws Exception {
Airport airport = new Airport("airports::vie", "vie", "lowx");
Expand Down