Add n1ql.scope and n1ql.collection spel expressions for @Query.
This adds n1ql.scope and n1ql.collection spel expressions for @Query so that n1ql.bucket doesn't need to be overloaded with the collection name. This will make the bucket name available in n1q.bucket. This is a breaking change as queries that used n1ql.bucket to get the collection name (especially in the case of pre-scope- and-collection @Queries that (a) referenced n1ql.bucket instead of referencing other spel expressions that include the bucket/collection name; and (b) still worked after the repository was moved from a bucket onto a collection by virtue of n1ql.bucket having the value of the collection in such instances. Closes #1445.
This commit is contained in:
@@ -133,6 +133,8 @@ A few N1QL-specific values are provided through SpEL:
|
||||
- `#n1ql.selectEntity` allows to easily make sure the statement will select all the fields necessary to build the full entity (including document ID and CAS value).
|
||||
- `#n1ql.filter` in the WHERE clause adds a criteria matching the entity type with the field that Spring Data uses to store type information.
|
||||
- `#n1ql.bucket` will be replaced by the name of the bucket the entity is stored in, escaped in backticks.
|
||||
- `#n1ql.scope` will be replaced by the name of the scope the entity is stored in, escaped in backticks.
|
||||
- `#n1ql.collection` will be replaced by the name of the collection the entity is stored in, escaped in backticks.
|
||||
- `#n1ql.fields` will be replaced by the list of fields (eg. for a SELECT clause) necessary to reconstruct the entity.
|
||||
- `#n1ql.delete` will be replaced by the `delete from` statement.
|
||||
- `#n1ql.returning` will be replaced by returning clause needed for reconstructing entity.
|
||||
@@ -164,7 +166,7 @@ This is *NOT* intended for projections to DTOs.
|
||||
Another example: +
|
||||
`#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1` +
|
||||
is equivalent to +
|
||||
`SELECT #{#n1ql.fields} FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} AND test = $1`
|
||||
`SELECT #{#n1ql.fields} FROM #{#n1ql.collection} WHERE #{#n1ql.filter} AND test = $1`
|
||||
|
||||
.A practical application of SpEL with Spring Security
|
||||
****
|
||||
|
||||
@@ -169,7 +169,7 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
|
||||
@Override
|
||||
public Flux<T> all() {
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
String statement = assembleEntityQuery(false, distinctFields, pArgs.getCollection());
|
||||
String statement = assembleEntityQuery(false, distinctFields, pArgs.getScope(), pArgs.getCollection());
|
||||
LOG.trace("findByQuery {} statement: {}", pArgs, statement);
|
||||
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
|
||||
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
|
||||
@@ -214,7 +214,7 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
String statement = assembleEntityQuery(true, distinctFields, pArgs.getCollection());
|
||||
String statement = assembleEntityQuery(true, distinctFields, pArgs.getScope(), pArgs.getCollection());
|
||||
LOG.trace("findByQuery {} statement: {}", pArgs, statement);
|
||||
Mono<ReactiveQueryResult> countResult = pArgs.getScope() == null
|
||||
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
|
||||
@@ -236,8 +236,8 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
|
||||
return count().map(count -> count > 0); // not efficient, just need the first one
|
||||
}
|
||||
|
||||
private String assembleEntityQuery(final boolean count, String[] distinctFields, String collection) {
|
||||
return query.toN1qlSelectString(template, collection, this.domainType, this.returnType, count,
|
||||
private String assembleEntityQuery(final boolean count, String[] distinctFields, String scope, String collection) {
|
||||
return query.toN1qlSelectString(template, scope, collection, this.domainType, this.returnType, count,
|
||||
query.getDistinctFields() != null ? query.getDistinctFields() : distinctFields, fields);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
* Copyright 2012-2022 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.
|
||||
@@ -71,7 +71,7 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
|
||||
@Override
|
||||
public Flux<RemoveResult> all() {
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
|
||||
String statement = assembleDeleteQuery(pArgs.getCollection());
|
||||
String statement = assembleDeleteQuery(pArgs.getScope(), pArgs.getCollection());
|
||||
LOG.trace("removeByQuery {} statement: {}", pArgs, statement);
|
||||
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
|
||||
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
|
||||
@@ -119,8 +119,8 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
|
||||
options);
|
||||
}
|
||||
|
||||
private String assembleDeleteQuery(String collection) {
|
||||
return query.toN1qlRemoveString(template, collection, this.domainType);
|
||||
private String assembleDeleteQuery(String scope, String collection) {
|
||||
return query.toN1qlRemoveString(template, scope, collection, this.domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -73,13 +73,14 @@ public class N1qlJoinResolver {
|
||||
String from = "FROM " + keySpacePair.lhs.keyspace + " lks " + useLKS + joinType + " " + keySpacePair.rhs.keyspace
|
||||
+ " rks";
|
||||
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1qlL = Query.getN1qlSpelValues(template, keySpacePair.lhs.collection,
|
||||
parameters.getEntityTypeInfo().getType(), parameters.getEntityTypeInfo().getType(), false, null, null);
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1qlL = Query.getN1qlSpelValues(template, null,
|
||||
keySpacePair.lhs.collection, parameters.getEntityTypeInfo().getType(), parameters.getEntityTypeInfo().getType(),
|
||||
false, null, null);
|
||||
String onLks = "lks." + n1qlL.filter;
|
||||
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1qlR = Query.getN1qlSpelValues(template, keySpacePair.rhs.collection,
|
||||
parameters.getAssociatedEntityTypeInfo().getType(), parameters.getAssociatedEntityTypeInfo().getType(), false,
|
||||
null, null);
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1qlR = Query.getN1qlSpelValues(template, null,
|
||||
keySpacePair.rhs.collection, parameters.getAssociatedEntityTypeInfo().getType(),
|
||||
parameters.getAssociatedEntityTypeInfo().getType(), false, null, null);
|
||||
String onRks = "rks." + n1qlR.filter;
|
||||
|
||||
StringBuilder useRKSBuilder = new StringBuilder();
|
||||
|
||||
@@ -209,6 +209,13 @@ public class N1QLExpression {
|
||||
return x("COUNT(" + expression.toString() + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned expression results in distinct of the expression
|
||||
*/
|
||||
public static N1QLExpression distinct(N1QLExpression expression) {
|
||||
return x("distinct{" + expression.toString() + "}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to wrap varargs with the given character.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
* Copyright 2012-2022 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.
|
||||
@@ -48,8 +48,8 @@ public class N1QLQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass,
|
||||
Class returnClass, boolean isCount, String[] distinctFields, String[] fields) {
|
||||
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
|
||||
Class domainClass, Class returnClass, boolean isCount, String[] distinctFields, String[] fields) {
|
||||
return expression.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,13 +338,17 @@ public class Query {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, Class domainClass, boolean isCount) {
|
||||
return toN1qlSelectString(template, null, domainClass, null, isCount, null, null);
|
||||
return toN1qlSelectString(template, null, null, domainClass, null, isCount, null, null);
|
||||
}
|
||||
|
||||
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass,
|
||||
Class returnClass, boolean isCount, String[] distinctFields, String[] fields) {
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, collectionName, domainClass,
|
||||
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
|
||||
Class domainClass, Class returnClass, boolean isCount, String[] distinctFields, String[] fields) {
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, scopeName, collectionName, domainClass,
|
||||
returnClass, isCount, distinctFields, fields);
|
||||
final StringBuilder statement = new StringBuilder();
|
||||
appendString(statement, n1ql.selectEntity); // select ...
|
||||
@@ -357,9 +361,10 @@ public class Query {
|
||||
return statement.toString();
|
||||
}
|
||||
|
||||
public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass) {
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, collectionName, domainClass, null,
|
||||
false, null, null);
|
||||
public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
|
||||
Class domainClass) {
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, scopeName, collectionName, domainClass,
|
||||
null, false, null, null);
|
||||
final StringBuilder statement = new StringBuilder();
|
||||
appendString(statement, n1ql.delete); // delete ...
|
||||
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
|
||||
@@ -369,8 +374,8 @@ public class Query {
|
||||
}
|
||||
|
||||
public static StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(ReactiveCouchbaseTemplate template,
|
||||
String collectionName, Class domainClass, Class returnClass, boolean isCount, String[] distinctFields,
|
||||
String[] fields) {
|
||||
String scopeName, String collectionName, Class domainClass, Class returnClass, boolean isCount,
|
||||
String[] distinctFields, String[] fields) {
|
||||
String typeKey = template.getConverter().getTypeKey();
|
||||
final CouchbasePersistentEntity<?> persistentEntity = template.getConverter().getMappingContext()
|
||||
.getRequiredPersistentEntity(domainClass);
|
||||
@@ -382,9 +387,10 @@ public class Query {
|
||||
typeValue = alias.toString();
|
||||
}
|
||||
|
||||
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(template.getBucketName(), collectionName,
|
||||
template.getConverter(), domainClass, returnClass, typeKey, typeValue, isCount, distinctFields, fields);
|
||||
return isCount ? sbnqp.getCountContext() : sbnqp.getStatementContext();
|
||||
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(template.getBucketName(), scopeName,
|
||||
collectionName, template.getConverter(), domainClass, returnClass, typeKey, typeValue, isCount, distinctFields,
|
||||
fields);
|
||||
return sbnqp.getStatementContext();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,9 +18,20 @@ package org.springframework.data.couchbase.core.query;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.support.TemplateUtils;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
|
||||
import org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryParser;
|
||||
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.json.JsonValue;
|
||||
|
||||
/**
|
||||
@@ -38,32 +49,46 @@ import com.couchbase.client.java.json.JsonValue;
|
||||
*/
|
||||
public class StringQuery extends Query {
|
||||
|
||||
private final CouchbaseQueryMethod queryMethod;
|
||||
private final String inlineN1qlQuery;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final ParameterAccessor parameterAccessor;
|
||||
private final SpelExpressionParser spelExpressionParser;
|
||||
|
||||
public StringQuery(String n1qlString) {
|
||||
inlineN1qlQuery = n1qlString;
|
||||
}
|
||||
|
||||
/**
|
||||
* inlineN1qlQuery (Query Annotation) append the string query to the provided StringBuilder. To be used along with the
|
||||
* other append*() methods to construct the N1QL statement
|
||||
*
|
||||
* @param sb - StringBuilder
|
||||
*/
|
||||
private void appendInlineN1qlStatement(final StringBuilder sb) {
|
||||
sb.append(inlineN1qlQuery);
|
||||
public StringQuery(CouchbaseQueryMethod queryMethod, String n1qlString,
|
||||
QueryMethodEvaluationContextProvider queryMethodEvaluationContextProvider, ParameterAccessor parameterAccessor,
|
||||
SpelExpressionParser spelExpressionParser) {
|
||||
this.queryMethod = queryMethod;
|
||||
this.inlineN1qlQuery = n1qlString;
|
||||
this.evaluationContextProvider = queryMethodEvaluationContextProvider;
|
||||
this.parameterAccessor = parameterAccessor;
|
||||
this.spelExpressionParser = spelExpressionParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String collection, Class domainClass,
|
||||
Class resultClass, boolean isCount, String[] distinctFields, String[] fields) {
|
||||
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scope, String collection,
|
||||
Class domainClass, Class resultClass, boolean isCount, String[] distinctFields, String[] fields) {
|
||||
|
||||
StringBasedN1qlQueryParser parser = getStringN1qlQueryParser(template, scope, collection, domainClass,
|
||||
distinctFields, fields);
|
||||
|
||||
N1QLExpression parsedExpression = parser.getExpression(inlineN1qlQuery, queryMethod, parameterAccessor,
|
||||
spelExpressionParser, evaluationContextProvider);
|
||||
|
||||
String queryString = parsedExpression.toString();
|
||||
|
||||
JsonValue parameters = parser.getPlaceholderValues(parameterAccessor);
|
||||
if (parameters instanceof JsonArray) {
|
||||
this.setPositionalParameters((JsonArray) parameters);
|
||||
} else {
|
||||
this.setNamedParameters((JsonObject) parameters);
|
||||
}
|
||||
final StringBuilder statement = new StringBuilder();
|
||||
boolean makeCount = isCount && inlineN1qlQuery != null
|
||||
&& !inlineN1qlQuery.toLowerCase(Locale.ROOT).contains("count(");
|
||||
boolean makeCount = isCount && queryString != null && !queryString.toLowerCase(Locale.ROOT).contains("count(");
|
||||
if (makeCount) {
|
||||
statement.append("SELECT COUNT(*) AS " + TemplateUtils.SELECT_COUNT + " FROM (");
|
||||
}
|
||||
appendInlineN1qlStatement(statement); // apply the string statement
|
||||
statement.append(queryString); // apply the string statement
|
||||
// To use generated parameters for literals
|
||||
// we need to figure out if we must use positional or named parameters
|
||||
// If we are using positional parameters, we need to start where
|
||||
@@ -86,6 +111,26 @@ public class StringQuery extends Query {
|
||||
return statement.toString();
|
||||
}
|
||||
|
||||
private StringBasedN1qlQueryParser getStringN1qlQueryParser(ReactiveCouchbaseTemplate template, String scopeName,
|
||||
String collectionName, Class domainClass, String[] distinctFields, String[] fields) {
|
||||
String typeKey = template.getConverter().getTypeKey();
|
||||
final CouchbasePersistentEntity<?> persistentEntity = template.getConverter().getMappingContext()
|
||||
.getRequiredPersistentEntity(domainClass);
|
||||
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);
|
||||
String typeValue = info.getJavaType().getName();
|
||||
TypeInformation<?> typeInfo = ClassTypeInformation.from(info.getJavaType());
|
||||
Alias alias = template.getConverter().getTypeAlias(typeInfo);
|
||||
if (alias != null && alias.isPresent()) {
|
||||
typeValue = alias.toString();
|
||||
}
|
||||
// there are no options for distinct and fields for @Query
|
||||
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(inlineN1qlQuery, queryMethod,
|
||||
template.getBucketName(), scopeName, collectionName, template.getConverter(), typeKey, typeValue,
|
||||
parameterAccessor, new SpelExpressionParser(), evaluationContextProvider);
|
||||
|
||||
return sbnqp;
|
||||
}
|
||||
|
||||
/**
|
||||
* toN1qlRemoveString - use toN1qlSelectString
|
||||
*
|
||||
@@ -94,7 +139,8 @@ public class StringQuery extends Query {
|
||||
* @param domainClass
|
||||
*/
|
||||
@Override
|
||||
public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass) {
|
||||
return toN1qlSelectString(template, collectionName, domainClass, domainClass, false, null, null);
|
||||
public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
|
||||
Class domainClass) {
|
||||
return toN1qlSelectString(template, scopeName, collectionName, domainClass, domainClass, false, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
* Copyright 2012-2022 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.
|
||||
@@ -65,15 +65,16 @@ public class N1qlRepositoryQueryExecutor {
|
||||
Query query;
|
||||
ExecutableFindByQuery q;
|
||||
if (queryMethod.hasN1qlAnnotation()) {
|
||||
query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), operations.getBucketName(),
|
||||
SPEL_PARSER, evaluationContextProvider, namedQueries).createQuery();
|
||||
query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), SPEL_PARSER,
|
||||
evaluationContextProvider, namedQueries).createQuery();
|
||||
} else {
|
||||
final PartTree tree = new PartTree(queryMethod.getName(), domainClass);
|
||||
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter(), operations.getBucketName()).createQuery();
|
||||
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter(), operations.getBucketName())
|
||||
.createQuery();
|
||||
}
|
||||
|
||||
ExecutableFindByQuery<?> operation = (ExecutableFindByQuery<?>) operations
|
||||
.findByQuery(domainClass).withConsistency(buildQueryScanConsistency());
|
||||
ExecutableFindByQuery<?> operation = (ExecutableFindByQuery<?>) operations.findByQuery(domainClass)
|
||||
.withConsistency(buildQueryScanConsistency());
|
||||
if (queryMethod.isCountQuery()) {
|
||||
return operation.matching(query).count();
|
||||
} else if (queryMethod.isCollectionQuery()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -77,8 +77,7 @@ public class ReactiveStringBasedCouchbaseQuery extends AbstractReactiveCouchbase
|
||||
protected Query createQuery(ParametersParameterAccessor accessor) {
|
||||
|
||||
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(accessor, getQueryMethod(),
|
||||
getOperations().getConverter(), getOperations().getBucketName(), expressionParser, evaluationContextProvider,
|
||||
namedQueries);
|
||||
getOperations().getConverter(), expressionParser, evaluationContextProvider, namedQueries);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -75,8 +75,7 @@ public class StringBasedCouchbaseQuery extends AbstractCouchbaseQuery {
|
||||
protected Query createQuery(ParametersParameterAccessor accessor) {
|
||||
|
||||
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(accessor, getQueryMethod(),
|
||||
getOperations().getConverter(), getOperations().getBucketName(), expressionParser, evaluationContextProvider,
|
||||
namedQueries);
|
||||
getOperations().getConverter(), expressionParser, evaluationContextProvider, namedQueries);
|
||||
Query query = creator.createQuery();
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
|
||||
@@ -44,7 +44,6 @@ import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
@@ -75,6 +74,18 @@ public class StringBasedN1qlQueryParser {
|
||||
* <code>"SELECT * FROM #{{@value SPEL_BUCKET}} LIMIT 3"</code>.
|
||||
*/
|
||||
public static final String SPEL_BUCKET = "#" + SPEL_PREFIX + ".bucket";
|
||||
/**
|
||||
* Use this variable in a SpEL expression in a {@link Query @Query} annotation's inline statement. This will be
|
||||
* replaced by the (escaped) scope name. Eg.
|
||||
* <code>"SELECT * FROM #{{@value SPEL_SCOPE}}.#{{@value SPEL_COLLECTION}} LIMIT 3"</code>.
|
||||
*/
|
||||
public static final String SPEL_SCOPE = "#" + SPEL_PREFIX + ".scope";
|
||||
/**
|
||||
* Use this variable in a SpEL expression in a {@link Query @Query} annotation's inline statement. This will be
|
||||
* replaced by the (escaped) collection name. Eg.
|
||||
* <code>"SELECT * FROM #{{@value SPEL_SCOPE}}.#{{@value SPEL_COLLECTION}} LIMIT 3"</code>.
|
||||
*/
|
||||
public static final String SPEL_COLLECTION = "#" + SPEL_PREFIX + ".collection";
|
||||
/**
|
||||
* Use this variable in a SpEL expression in a {@link Query @Query} annotation's inline statement. This will be
|
||||
* replaced by the fields allowing to construct the repository's entity (SELECT clause). Eg.
|
||||
@@ -118,48 +129,84 @@ public class StringBasedN1qlQueryParser {
|
||||
private final CouchbaseQueryMethod queryMethod;
|
||||
private PlaceholderType placeHolderType;
|
||||
private final N1qlSpelValues statementContext;
|
||||
private final N1qlSpelValues countContext;
|
||||
private final CouchbaseConverter couchbaseConverter;
|
||||
private final Collection<String> parameterNames = new HashSet<String>();
|
||||
private final Collection<String> parameterNames = new HashSet<>();
|
||||
public final N1QLExpression parsedExpression;
|
||||
|
||||
public StringBasedN1qlQueryParser(String statement, CouchbaseQueryMethod queryMethod, String bucketName,
|
||||
CouchbaseConverter couchbaseConverter, String typeField, String typeValue, ParameterAccessor accessor,
|
||||
SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
/**
|
||||
* This constructor is to allow for generating the n1ql spel expressions from @Queries.
|
||||
*
|
||||
* @param statement
|
||||
* @param queryMethod
|
||||
* @param bucketName
|
||||
* @param scope
|
||||
* @param collection
|
||||
* @param couchbaseConverter
|
||||
* @param typeField
|
||||
* @param typeValue
|
||||
* @param accessor
|
||||
* @param spelExpressionParser
|
||||
* @param evaluationContextProvider
|
||||
*/
|
||||
public StringBasedN1qlQueryParser(String statement, CouchbaseQueryMethod queryMethod, String bucketName, String scope,
|
||||
String collection, CouchbaseConverter couchbaseConverter, String typeField, String typeValue,
|
||||
ParameterAccessor accessor, SpelExpressionParser spelExpressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this.statement = statement;
|
||||
this.queryMethod = queryMethod;
|
||||
this.couchbaseConverter = couchbaseConverter;
|
||||
String collection = queryMethod.getCollection();
|
||||
this.statementContext = createN1qlSpelValues(bucketName, collection,
|
||||
queryMethod.getEntityInformation().getJavaType(), queryMethod.getReturnedObjectType(), typeField, typeValue,
|
||||
false, null, null);
|
||||
this.countContext = createN1qlSpelValues(bucketName, collection, queryMethod.getEntityInformation().getJavaType(),
|
||||
queryMethod.getReturnedObjectType(), typeField, typeValue, true, null, null);
|
||||
this.parsedExpression = getExpression(accessor, null, parser, evaluationContextProvider);
|
||||
checkPlaceholders(this.parsedExpression.toString());
|
||||
this.statementContext = createN1qlSpelValues(collection != null ? collection : bucketName, scope, collection,
|
||||
queryMethod.getEntityInformation().getJavaType(), typeField, typeValue, queryMethod.isCountQuery(), null, null);
|
||||
this.parsedExpression = getExpression(statement, queryMethod, accessor, spelExpressionParser,
|
||||
evaluationContextProvider);
|
||||
}
|
||||
|
||||
public StringBasedN1qlQueryParser(String bucketName, String collection, CouchbaseConverter couchbaseConverter,
|
||||
Class domainClass, Class resultClass, String typeField, String typeValue, boolean isCount,
|
||||
String[] distinctFields, String[] fields) {
|
||||
/**
|
||||
* This constructor is to allow for generating the n1ql spel expressions from NON-@Queries. For selects,
|
||||
* n1ql.selectEntity and n1ql.filter. FOr deletes, n1ql.delete, n1ql.filter and n1ql.returning
|
||||
*
|
||||
* @param bucketName
|
||||
* @param scope
|
||||
* @param collection
|
||||
* @param couchbaseConverter
|
||||
* @param domainClass
|
||||
* @param resultClass
|
||||
* @param typeField
|
||||
* @param typeValue
|
||||
* @param isCount
|
||||
* @param distinctFields
|
||||
* @param fields
|
||||
*/
|
||||
public StringBasedN1qlQueryParser(String bucketName, String scope, String collection,
|
||||
CouchbaseConverter couchbaseConverter, Class<?> domainClass, Class<?> resultClass, String typeField,
|
||||
String typeValue, boolean isCount, String[] distinctFields, String[] fields) {
|
||||
this.statement = null;
|
||||
this.queryMethod = null;
|
||||
this.couchbaseConverter = couchbaseConverter;
|
||||
if (!isCount) {
|
||||
this.statementContext = createN1qlSpelValues(bucketName, collection, domainClass, resultClass, typeField,
|
||||
typeValue, false, distinctFields, fields);
|
||||
this.countContext = null;
|
||||
} else {
|
||||
this.statementContext = null;
|
||||
this.countContext = createN1qlSpelValues(bucketName, collection, domainClass, resultClass, typeField, typeValue,
|
||||
true, distinctFields, fields);
|
||||
}
|
||||
this.statementContext = createN1qlSpelValues(bucketName, scope, collection, domainClass, typeField, typeValue,
|
||||
isCount, distinctFields, fields);
|
||||
this.parsedExpression = null;
|
||||
}
|
||||
|
||||
public N1qlSpelValues createN1qlSpelValues(String bucketName, String collection, Class domainClass, Class resultClass,
|
||||
/**
|
||||
* Create the n1ql spel values. The domainClass is needed, but not the returnClass. Mapping the domainClass to the
|
||||
* returnClass is the responsibility of decoding.
|
||||
*
|
||||
* @param bucketName
|
||||
* @param scope
|
||||
* @param collection
|
||||
* @param domainClass
|
||||
* @param typeField
|
||||
* @param typeValue
|
||||
* @param isCount
|
||||
* @param distinctFields
|
||||
* @param fields
|
||||
* @return
|
||||
*/
|
||||
public N1qlSpelValues createN1qlSpelValues(String bucketName, String scope, String collection, Class domainClass,
|
||||
String typeField, String typeValue, boolean isCount, String[] distinctFields, String[] fields) {
|
||||
String b = collection != null ? collection : bucketName;
|
||||
String b = bucketName;
|
||||
String keyspace = collection != null ? collection : bucketName;
|
||||
Assert.isTrue(!(distinctFields != null && fields != null),
|
||||
"only one of project(fields) and distinct(distinctFields) can be specified");
|
||||
String entityFields = "";
|
||||
@@ -167,24 +214,26 @@ public class StringBasedN1qlQueryParser {
|
||||
if (distinctFields != null) {
|
||||
String distinctFieldsStr = getProjectedOrDistinctFields(b, domainClass, typeField, fields, distinctFields);
|
||||
if (isCount) {
|
||||
selectEntity = "SELECT COUNT( DISTINCT {" + distinctFieldsStr + "} ) " + CountFragment.COUNT_ALIAS + " FROM "
|
||||
+ i(b);
|
||||
selectEntity = N1QLExpression.select(N1QLExpression.count(N1QLExpression.distinct(x(distinctFieldsStr)))
|
||||
.as(i(CountFragment.COUNT_ALIAS)).from(keyspace)).toString();
|
||||
} else {
|
||||
selectEntity = "SELECT DISTINCT " + distinctFieldsStr + " FROM " + i(b);
|
||||
selectEntity = N1QLExpression.select(N1QLExpression.distinct(x(distinctFieldsStr))).from(keyspace).toString();
|
||||
}
|
||||
} else if (isCount) {
|
||||
selectEntity = "SELECT " + "COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM " + i(b);
|
||||
selectEntity = N1QLExpression.select(N1QLExpression.count(x("\"*\"")).as(i(CountFragment.COUNT_ALIAS)))
|
||||
.from(keyspace).toString();
|
||||
} else {
|
||||
String projectedFields = getProjectedOrDistinctFields(b, domainClass, typeField, fields, distinctFields);
|
||||
String projectedFields = getProjectedOrDistinctFields(keyspace, domainClass, typeField, fields, distinctFields);
|
||||
entityFields = projectedFields;
|
||||
selectEntity = "SELECT " + projectedFields + " FROM " + i(b);
|
||||
selectEntity = N1QLExpression.select(x(projectedFields)).from(keyspace).toString();
|
||||
}
|
||||
String typeSelection = "`" + typeField + "` = \"" + typeValue + "\"";
|
||||
|
||||
String delete = N1QLExpression.delete().from(b).toString();
|
||||
String returning = " returning " + N1qlUtils.createReturningExpressionForDelete(b).toString();
|
||||
String delete = N1QLExpression.delete().from(keyspace).toString();
|
||||
String returning = " returning " + N1qlUtils.createReturningExpressionForDelete(keyspace);
|
||||
|
||||
return new N1qlSpelValues(selectEntity, entityFields, i(b).toString(), typeSelection, delete, returning);
|
||||
return new N1qlSpelValues(selectEntity, entityFields, i(b).toString(), i(scope).toString(),
|
||||
i(collection).toString(), typeSelection, delete, returning);
|
||||
}
|
||||
|
||||
private String getProjectedOrDistinctFields(String b, Class resultClass, String typeField, String[] fields,
|
||||
@@ -193,8 +242,15 @@ public class StringBasedN1qlQueryParser {
|
||||
return i(distinctFields).toString();
|
||||
}
|
||||
String projectedFields;
|
||||
PersistentEntity persistentEntity = null;
|
||||
if (resultClass != null && !Modifier.isAbstract(resultClass.getModifiers())) {
|
||||
PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass);
|
||||
try {
|
||||
persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (persistentEntity != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
getProjectedFieldsInternal(b, null, sb, persistentEntity, typeField, fields, distinctFields != null);
|
||||
projectedFields = sb.toString();
|
||||
@@ -300,14 +356,11 @@ public class StringBasedN1qlQueryParser {
|
||||
|
||||
// this static method can be used to test the parsing behavior for Couchbase specific spel variables
|
||||
// in isolation from the rest of the spel parser initialization chain.
|
||||
public String doParse(SpelExpressionParser parser, EvaluationContext evaluationContext, boolean isCountQuery) {
|
||||
org.springframework.expression.Expression parsedExpression = parser.parseExpression(this.getStatement(),
|
||||
public static String doParse(String statement, SpelExpressionParser parser, EvaluationContext evaluationContext,
|
||||
N1qlSpelValues n1qlSpelValues) {
|
||||
org.springframework.expression.Expression parsedExpression = parser.parseExpression(statement,
|
||||
new TemplateParserContext());
|
||||
if (isCountQuery) {
|
||||
evaluationContext.setVariable(SPEL_PREFIX, this.getCountContext());
|
||||
} else {
|
||||
evaluationContext.setVariable(SPEL_PREFIX, this.getStatementContext());
|
||||
}
|
||||
evaluationContext.setVariable(SPEL_PREFIX, n1qlSpelValues);
|
||||
return parsedExpression.getValue(evaluationContext, String.class);
|
||||
}
|
||||
|
||||
@@ -425,7 +478,7 @@ public class StringBasedN1qlQueryParser {
|
||||
return namedValues;
|
||||
}
|
||||
|
||||
protected JsonValue getPlaceholderValues(ParameterAccessor accessor) {
|
||||
public JsonValue getPlaceholderValues(ParameterAccessor accessor) {
|
||||
switch (this.placeHolderType) {
|
||||
case NAMED:
|
||||
return getNamedPlaceholderValues(accessor);
|
||||
@@ -483,10 +536,6 @@ public class StringBasedN1qlQueryParser {
|
||||
return this.statement.contains(SPEL_SELECT_FROM_CLAUSE);
|
||||
}
|
||||
|
||||
public N1qlSpelValues getCountContext() {
|
||||
return this.countContext;
|
||||
}
|
||||
|
||||
public N1qlSpelValues getStatementContext() {
|
||||
return this.statementContext;
|
||||
}
|
||||
@@ -527,6 +576,18 @@ public class StringBasedN1qlQueryParser {
|
||||
*/
|
||||
public final String bucket;
|
||||
|
||||
/**
|
||||
* <code>#{{@value SPEL_SCOPE}.
|
||||
* bucket</code> will be replaced by (escaped) scope name in which the entity is stored.
|
||||
*/
|
||||
public final String scope;
|
||||
|
||||
/**
|
||||
* <code>#{{@value SPEL_COLLECTION}.
|
||||
* bucket</code> will be replaced by (escaped) collection name in which the entity is stored.
|
||||
*/
|
||||
public final String collection;
|
||||
|
||||
/**
|
||||
* <code>#{{@value SPEL_FILTER}}.
|
||||
* filter</code> will be replaced by an expression allowing to select only entries matching the entity in a WHERE
|
||||
@@ -547,25 +608,37 @@ public class StringBasedN1qlQueryParser {
|
||||
*/
|
||||
public final String returning;
|
||||
|
||||
public N1qlSpelValues(String selectClause, String entityFields, String bucket, String filter, String delete,
|
||||
String returning) {
|
||||
public N1qlSpelValues(String selectClause, String entityFields, String bucket, String scope, String collection,
|
||||
String filter, String delete, String returning) {
|
||||
this.selectEntity = selectClause;
|
||||
this.fields = entityFields;
|
||||
this.bucket = bucket;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.filter = filter;
|
||||
this.delete = delete;
|
||||
this.returning = returning;
|
||||
}
|
||||
}
|
||||
|
||||
// copied from StringN1qlBasedQuery
|
||||
private N1QLExpression getExpression(ParameterAccessor accessor, ReturnedType returnedType,
|
||||
/**
|
||||
* Creates the N1QLExpression and parameterNames
|
||||
*
|
||||
* @param statement
|
||||
* @param queryMethod
|
||||
* @param accessor
|
||||
* @param parser
|
||||
* @param evaluationContextProvider
|
||||
* @return
|
||||
*/
|
||||
|
||||
public N1QLExpression getExpression(String statement, CouchbaseQueryMethod queryMethod, ParameterAccessor accessor,
|
||||
SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
boolean isCountQuery = queryMethod.isCountQuery();
|
||||
Object[] runtimeParameters = getParameters(accessor);
|
||||
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(queryMethod.getParameters(),
|
||||
runtimeParameters);
|
||||
N1QLExpression parsedStatement = x(this.doParse(parser, evaluationContext, isCountQuery));
|
||||
N1QLExpression parsedStatement = x(doParse(statement, parser, evaluationContext, this.getStatementContext()));
|
||||
checkPlaceholders(parsedStatement.toString());
|
||||
return parsedStatement;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors
|
||||
* Copyright 2021-2022 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.
|
||||
@@ -23,13 +23,11 @@ import java.util.Optional;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.couchbase.core.query.StringQuery;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
@@ -39,31 +37,25 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextPro
|
||||
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.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.json.JsonValue;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
* @author Mauro Monti
|
||||
*/
|
||||
public class StringN1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria> {
|
||||
|
||||
// everything we need in the StringQuery such that we can doParse() later when we have the scope and collection
|
||||
private final ParameterAccessor accessor;
|
||||
private final MappingContext<?, CouchbasePersistentProperty> context;
|
||||
private final SpelExpressionParser parser;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final StringBasedN1qlQueryParser queryParser;
|
||||
private final QueryMethod queryMethod;
|
||||
private final CouchbaseQueryMethod queryMethod;
|
||||
private final CouchbaseConverter couchbaseConverter;
|
||||
private final N1QLExpression parsedExpression;
|
||||
private final String queryString;
|
||||
private final SpelExpressionParser spelExpressionParser;
|
||||
|
||||
public StringN1qlQueryCreator(final ParameterAccessor accessor, CouchbaseQueryMethod queryMethod,
|
||||
CouchbaseConverter couchbaseConverter, String bucketName, SpelExpressionParser spelExpressionParser,
|
||||
CouchbaseConverter couchbaseConverter, SpelExpressionParser spelExpressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider, NamedQueries namedQueries) {
|
||||
|
||||
// AbstractQueryCreator needs a PartTree, so we give it a dummy one.
|
||||
@@ -76,27 +68,19 @@ public class StringN1qlQueryCreator extends AbstractQueryCreator<Query, QueryCri
|
||||
this.context = couchbaseConverter.getMappingContext();
|
||||
this.queryMethod = queryMethod;
|
||||
this.couchbaseConverter = couchbaseConverter;
|
||||
this.spelExpressionParser = spelExpressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
final String namedQueryName = queryMethod.getNamedQueryName();
|
||||
String queryString;
|
||||
String qString;
|
||||
if (queryMethod.hasInlineN1qlQuery()) {
|
||||
queryString = queryMethod.getInlineN1qlQuery();
|
||||
qString = queryMethod.getInlineN1qlQuery();
|
||||
} else if (namedQueries.hasQuery(namedQueryName)) {
|
||||
queryString = namedQueries.getQuery(namedQueryName);
|
||||
qString = namedQueries.getQuery(namedQueryName);
|
||||
} else {
|
||||
throw new IllegalArgumentException("query has no inline Query or named Query not found");
|
||||
}
|
||||
Class javaType = getType();
|
||||
String typeValue = javaType.getName();
|
||||
TypeInformation<?> typeInfo = ClassTypeInformation.from(javaType);
|
||||
Alias alias = couchbaseConverter.getTypeAlias(typeInfo);
|
||||
if (alias != null && alias.isPresent()) {
|
||||
typeValue = alias.toString();
|
||||
}
|
||||
this.queryParser = new StringBasedN1qlQueryParser(queryString, queryMethod, bucketName, couchbaseConverter,
|
||||
getTypeField(), typeValue, accessor, spelExpressionParser, evaluationContextProvider);
|
||||
this.parser = spelExpressionParser;
|
||||
this.parsedExpression = this.queryParser.parsedExpression;
|
||||
// Save the query string to be parsed later after we have the scope and collection to be used in the query
|
||||
this.queryString = qString;
|
||||
}
|
||||
|
||||
protected QueryMethod getQueryMethod() {
|
||||
@@ -148,13 +132,9 @@ public class StringN1qlQueryCreator extends AbstractQueryCreator<Query, QueryCri
|
||||
|
||||
@Override
|
||||
protected Query complete(QueryCriteria criteria, Sort sort) {
|
||||
Query q = new StringQuery(parsedExpression.toString()).with(sort);
|
||||
JsonValue params = queryParser.getPlaceholderValues(accessor);
|
||||
if (params instanceof JsonArray) {
|
||||
q.setPositionalParameters((JsonArray) params);
|
||||
} else {
|
||||
q.setNamedParameters((JsonObject) params);
|
||||
}
|
||||
// everything we need in the StringQuery such that we can doParse() later when we have the scope and collection
|
||||
Query q = new StringQuery(queryMethod, queryString, evaluationContextProvider, accessor, spelExpressionParser)
|
||||
.with(sort);
|
||||
return q;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
* Copyright 2012-2022 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.
|
||||
@@ -20,9 +20,11 @@ import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -59,6 +61,11 @@ public interface UserRepository extends CouchbaseRepository<User, String> {
|
||||
|
||||
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
|
||||
|
||||
@Query("#{#n1ql.selectEntity}|#{#n1ql.filter}|#{#n1ql.bucket}|#{#n1ql.scope}|#{#n1ql.collection}")
|
||||
@Scope("thisScope")
|
||||
@Collection("thisCollection")
|
||||
List<User> spelTests();
|
||||
|
||||
// simulate a slow operation
|
||||
@Cacheable("mySpringCache")
|
||||
default List<User> getByFirstname(String firstname) {
|
||||
|
||||
@@ -66,9 +66,9 @@ import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@SpringJUnitConfig(StringN1qlQueryCreatorTests.Config.class)
|
||||
@SpringJUnitConfig(StringN1qlQueryCreatorIntegrationTests.Config.class)
|
||||
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
|
||||
class StringN1qlQueryCreatorTests extends ClusterAwareIntegrationTests {
|
||||
class StringN1qlQueryCreatorIntegrationTests extends ClusterAwareIntegrationTests {
|
||||
|
||||
MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> context;
|
||||
CouchbaseConverter converter;
|
||||
@@ -98,8 +98,8 @@ class StringN1qlQueryCreatorTests extends ClusterAwareIntegrationTests {
|
||||
converter.getMappingContext());
|
||||
|
||||
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Continental"),
|
||||
queryMethod, converter, config().bucketname(), new SpelExpressionParser(),
|
||||
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
|
||||
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT,
|
||||
namedQueries);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
|
||||
@@ -131,8 +131,8 @@ class StringN1qlQueryCreatorTests extends ClusterAwareIntegrationTests {
|
||||
converter.getMappingContext());
|
||||
|
||||
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Continental"),
|
||||
queryMethod, converter, config().bucketname(), new SpelExpressionParser(),
|
||||
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
|
||||
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT,
|
||||
namedQueries);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
|
||||
@@ -84,13 +84,15 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
|
||||
converter.getMappingContext());
|
||||
|
||||
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"),
|
||||
queryMethod, converter, "travel-sample", new SpelExpressionParser(),
|
||||
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
|
||||
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
assertEquals(
|
||||
"SELECT `_class`, META(`travel-sample`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`travel-sample`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `travel-sample` where `_class` = \"abstractuser\" and firstname = $1 and lastname = $2",
|
||||
query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false));
|
||||
"SELECT `_class`, META(`" + bucketName()
|
||||
+ "`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`"
|
||||
+ bucketName() + "`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `" + bucketName()
|
||||
+ "` where `_class` = \"abstractuser\" and firstname = $1 and lastname = $2",
|
||||
query.toN1qlSelectString(couchbaseTemplate.reactive(), null, null, User.class, User.class, false, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,13 +105,15 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
|
||||
converter.getMappingContext());
|
||||
|
||||
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"),
|
||||
queryMethod, converter, "travel-sample", new SpelExpressionParser(),
|
||||
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
|
||||
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
assertEquals(
|
||||
"SELECT `_class`, META(`travel-sample`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`travel-sample`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `travel-sample` where `_class` = \"abstractuser\" and (firstname = $first or lastname = $last)",
|
||||
query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false));
|
||||
"SELECT `_class`, META(`" + bucketName()
|
||||
+ "`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`"
|
||||
+ bucketName() + "`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `" + bucketName()
|
||||
+ "` where `_class` = \"abstractuser\" and (firstname = $first or lastname = $last)",
|
||||
query.toN1qlSelectString(couchbaseTemplate.reactive(), null, null, User.class, User.class, false, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -123,8 +127,8 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
|
||||
|
||||
try {
|
||||
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"),
|
||||
queryMethod, converter, "travel-sample", new SpelExpressionParser(),
|
||||
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
|
||||
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT,
|
||||
namedQueries);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return;
|
||||
}
|
||||
@@ -141,14 +145,33 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
|
||||
|
||||
try {
|
||||
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"),
|
||||
queryMethod, converter, "travel-sample", new SpelExpressionParser(),
|
||||
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
|
||||
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT,
|
||||
namedQueries);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return;
|
||||
}
|
||||
fail("should have failed with IllegalArgumentException: query has no inline Query or named Query not found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void spelTests() throws Exception {
|
||||
String input = "spelTests";
|
||||
Method method = UserRepository.class.getMethod(input);
|
||||
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
|
||||
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
|
||||
converter.getMappingContext());
|
||||
|
||||
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method)), queryMethod,
|
||||
converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
|
||||
String s = query.toN1qlSelectString(couchbaseTemplate.reactive(), "myScope", "myCollection", User.class, null,
|
||||
false, null, null);
|
||||
System.out.println("query: " + s);
|
||||
|
||||
}
|
||||
|
||||
private ParameterAccessor getAccessor(Parameters<?, ?> params, Object... values) {
|
||||
return new ParametersParameterAccessor(params, values);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user