DATACOUCH-246 - Projections support for query derivation.
Used the result processor projecting information to just fetch the selected fields and again to post process results to projection object/interfaces. Added test for projection using DTO. Original pull request: #122.
This commit is contained in:
committed by
Oliver Gierke
parent
c8324caf7f
commit
ce356f827f
@@ -20,7 +20,6 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.couchbase.client.core.lang.Tuple2;
|
||||
import com.couchbase.client.java.document.json.JsonArray;
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.document.json.JsonValue;
|
||||
@@ -40,12 +39,17 @@ import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base for all Couchbase {@link RepositoryQuery}. It is in charge of inspecting the parameters
|
||||
* and choosing the correct {@link N1qlQuery} implementation to use.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
|
||||
@@ -53,6 +57,7 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
|
||||
protected final CouchbaseQueryMethod queryMethod;
|
||||
private final CouchbaseOperations couchbaseOperations;
|
||||
private Class<?> returnedType;
|
||||
|
||||
protected AbstractN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations) {
|
||||
this.queryMethod = queryMethod;
|
||||
@@ -61,6 +66,7 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
|
||||
/**
|
||||
* The statement for a count() query. This must aggregate using count with the alias {@link CountFragment#COUNT_ALIAS}.
|
||||
*
|
||||
* @see CountFragment
|
||||
*/
|
||||
protected abstract Statement getCount(ParameterAccessor accessor, Object[] runtimeParameters);
|
||||
@@ -71,14 +77,23 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
*/
|
||||
protected abstract boolean useGeneratedCountQuery();
|
||||
|
||||
protected abstract Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters);
|
||||
protected abstract Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType);
|
||||
|
||||
protected abstract JsonValue getPlaceholderValues(ParameterAccessor accessor);
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
|
||||
Statement statement = getStatement(accessor, parameters);
|
||||
|
||||
ResultProcessor processor = this.queryMethod.getResultProcessor().withDynamicProjection(accessor);
|
||||
ReturnedType returnedType = processor.getReturnedType();
|
||||
this.returnedType = returnedType.getReturnedType();
|
||||
|
||||
if (this.returnedType.isInterface()) {
|
||||
this.returnedType = returnedType.getDomainType();
|
||||
}
|
||||
|
||||
Statement statement = getStatement(accessor, parameters, returnedType);
|
||||
JsonValue queryPlaceholderValues = getPlaceholderValues(accessor);
|
||||
|
||||
//prepare the final query
|
||||
@@ -89,9 +104,8 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
Statement countStatement = getCount(accessor, parameters);
|
||||
N1qlQuery countQuery = buildQuery(countStatement, queryPlaceholderValues,
|
||||
getCouchbaseOperations().getDefaultConsistency().n1qlConsistency());
|
||||
|
||||
return executeDependingOnType(query, countQuery, queryMethod, accessor.getPageable(),
|
||||
queryMethod.isPageQuery(), queryMethod.isSliceQuery(), queryMethod.isModifyingQuery());
|
||||
return processor.processResult(executeDependingOnType(query, countQuery, queryMethod, accessor.getPageable(),
|
||||
queryMethod.isPageQuery(), queryMethod.isSliceQuery(), queryMethod.isModifyingQuery()));
|
||||
}
|
||||
|
||||
protected static N1qlQuery buildQuery(Statement statement, JsonValue queryPlaceholderValues, ScanConsistency scanConsistency) {
|
||||
@@ -144,7 +158,7 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
|
||||
protected List<?> executeCollection(N1qlQuery query) {
|
||||
logIfNecessary(query);
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, queryMethod.getEntityInformation().getJavaType());
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, this.returnedType);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -169,14 +183,14 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
}
|
||||
|
||||
logIfNecessary(query);
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, queryMethod.getEntityInformation().getJavaType());
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, this.returnedType);
|
||||
return new PageImpl(result, pageable, total);
|
||||
}
|
||||
|
||||
protected Object executeSliced(N1qlQuery query, N1qlQuery countQuery, Pageable pageable) {
|
||||
Assert.notNull(pageable);
|
||||
logIfNecessary(query);
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, queryMethod.getEntityInformation().getJavaType());
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, this.returnedType);
|
||||
int pageSize = pageable.getPageSize();
|
||||
boolean hasNext = result.size() > pageSize;
|
||||
|
||||
|
||||
@@ -19,12 +19,7 @@ package org.springframework.data.couchbase.repository.query;
|
||||
import static com.couchbase.client.java.query.Select.select;
|
||||
import static com.couchbase.client.java.query.dsl.Expression.i;
|
||||
import static com.couchbase.client.java.query.dsl.functions.AggregateFunctions.count;
|
||||
import static com.couchbase.client.java.query.dsl.functions.MetaFunctions.meta;
|
||||
|
||||
import com.couchbase.client.core.lang.Tuple;
|
||||
import com.couchbase.client.core.lang.Tuple2;
|
||||
import com.couchbase.client.java.document.json.JsonArray;
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.document.json.JsonValue;
|
||||
import com.couchbase.client.java.query.Statement;
|
||||
import com.couchbase.client.java.query.dsl.Expression;
|
||||
@@ -36,9 +31,18 @@ import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link RepositoryQuery} for Couchbase, based on query derivation
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
|
||||
public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
|
||||
private final PartTree partTree;
|
||||
@@ -63,16 +67,15 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
return queryCreator.createQuery();
|
||||
}
|
||||
@Override
|
||||
protected Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters) {
|
||||
protected Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) {
|
||||
String bucketName = getCouchbaseOperations().getCouchbaseBucket().name();
|
||||
Expression bucket = N1qlUtils.escapedBucket(bucketName);
|
||||
|
||||
|
||||
FromPath select;
|
||||
if (partTree.isCountProjection()) {
|
||||
select = select(count("*"));
|
||||
} else {
|
||||
select = N1qlUtils.createSelectClauseForEntity(bucketName);
|
||||
select = N1qlUtils.createSelectClauseForEntity(bucketName, returnedType, this.getCouchbaseOperations().getConverter());
|
||||
}
|
||||
WherePath selectFrom = select.from(bucket);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
@@ -53,6 +54,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
* along with a variable for {@link #SPEL_FILTER WHERE clause filtering} of the correct entity.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class StringN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
|
||||
@@ -264,7 +266,7 @@ public class StringN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters) {
|
||||
public Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) {
|
||||
String parsedStatement = parseSpel(this.originalStatement, false, runtimeParameters);
|
||||
return N1qlQuery.simple(parsedStatement).statement();
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@ import com.couchbase.client.java.query.dsl.functions.TypeFunctions;
|
||||
import com.couchbase.client.java.query.dsl.path.FromPath;
|
||||
import com.couchbase.client.java.query.dsl.path.WherePath;
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.couchbase.repository.query.CountFragment;
|
||||
@@ -44,10 +44,14 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.repository.core.EntityMetadata;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
|
||||
/**
|
||||
* Utility class to deal with constructing well formed N1QL queries around Spring Data entities, so that
|
||||
* the framework can use N1QL to find such entities (eg. restrict the bucket search to a particular type).
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class N1qlUtils {
|
||||
|
||||
@@ -70,6 +74,41 @@ public class N1qlUtils {
|
||||
return i(bucketName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a {@link Statement} that corresponds to the SELECT clause for looking for Spring Data entities
|
||||
* stored in Couchbase. Notably it will select the content of the document AND its id and cas and use custom
|
||||
* construction of query if required.
|
||||
*
|
||||
* @param bucketName the bucket that stores the entity documents (will be escaped).
|
||||
* @param returnedType Returned type projection information from result processor.
|
||||
* @param converter couchbase converter
|
||||
* @return the needed SELECT clause of the statement.
|
||||
*/
|
||||
public static FromPath createSelectClauseForEntity(String bucketName, ReturnedType returnedType, CouchbaseConverter converter) {
|
||||
Expression bucket = escapedBucket(bucketName);
|
||||
Expression metaId = path(meta(bucket), "id").as(CouchbaseOperations.SELECT_ID);
|
||||
Expression metaCas = path(meta(bucket), "cas").as(CouchbaseOperations.SELECT_CAS);
|
||||
List<Expression> expList = new ArrayList<Expression>();
|
||||
expList.add(metaId);
|
||||
expList.add(metaCas);
|
||||
|
||||
if (returnedType != null && returnedType.needsCustomConstruction()) {
|
||||
List<String> properties = returnedType.getInputProperties();
|
||||
CouchbasePersistentEntity<?> entity = converter.getMappingContext().getPersistentEntity(returnedType.getDomainType());
|
||||
|
||||
for (String property : properties) {
|
||||
expList.add(path(bucket, i(entity.getPersistentProperty(property).getFieldName())));
|
||||
}
|
||||
} else {
|
||||
expList.add(path(bucket, "*"));
|
||||
}
|
||||
|
||||
Expression[] propertiesExp = new Expression[expList.size()];
|
||||
propertiesExp = expList.toArray(propertiesExp);
|
||||
|
||||
return select(propertiesExp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a {@link Statement} that corresponds to the SELECT clause for looking for Spring Data entities
|
||||
* stored in Couchbase. Notably it will select the content of the document AND its id and cas.
|
||||
@@ -78,11 +117,7 @@ public class N1qlUtils {
|
||||
* @return the needed SELECT clause of the statement.
|
||||
*/
|
||||
public static FromPath createSelectClauseForEntity(String bucketName) {
|
||||
Expression bucket = escapedBucket(bucketName);
|
||||
Expression metaId = path(meta(bucket), "id").as(CouchbaseOperations.SELECT_ID);
|
||||
Expression metaCas = path(meta(bucket), "cas").as(CouchbaseOperations.SELECT_CAS);
|
||||
|
||||
return select(metaId, metaCas, path(bucket, "*"));
|
||||
return createSelectClauseForEntity(bucketName, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user