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:
Subhashni Balakrishnan
2016-09-23 15:39:59 -07:00
committed by Oliver Gierke
parent c8324caf7f
commit ce356f827f
12 changed files with 310 additions and 35 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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);
}
/**