Skip to content

Commit a103000

Browse files
committed
Merge branch 'datacouch_1445_add_n1ql_scope_and_collection_spel' of github.com:spring-projects/spring-data-couchbase into datacouch_1441_consider_scope_and_collection_from_repository_annotation
Needed to test functionality.
2 parents 65f3b01 + 80689ae commit a103000

16 files changed

+314
-170
lines changed

src/main/asciidoc/repository.adoc

+3-1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ A few N1QL-specific values are provided through SpEL:
133133
- `#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).
134134
- `#n1ql.filter` in the WHERE clause adds a criteria matching the entity type with the field that Spring Data uses to store type information.
135135
- `#n1ql.bucket` will be replaced by the name of the bucket the entity is stored in, escaped in backticks.
136+
- `#n1ql.scope` will be replaced by the name of the scope the entity is stored in, escaped in backticks.
137+
- `#n1ql.collection` will be replaced by the name of the collection the entity is stored in, escaped in backticks.
136138
- `#n1ql.fields` will be replaced by the list of fields (eg. for a SELECT clause) necessary to reconstruct the entity.
137139
- `#n1ql.delete` will be replaced by the `delete from` statement.
138140
- `#n1ql.returning` will be replaced by returning clause needed for reconstructing entity.
@@ -164,7 +166,7 @@ This is *NOT* intended for projections to DTOs.
164166
Another example: +
165167
`#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1` +
166168
is equivalent to +
167-
`SELECT #{#n1ql.fields} FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} AND test = $1`
169+
`SELECT #{#n1ql.fields} FROM #{#n1ql.collection} WHERE #{#n1ql.filter} AND test = $1`
168170

169171
.A practical application of SpEL with Spring Security
170172
****

src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public Mono<T> first() {
169169
@Override
170170
public Flux<T> all() {
171171
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
172-
String statement = assembleEntityQuery(false, distinctFields, pArgs.getCollection());
172+
String statement = assembleEntityQuery(false, distinctFields, pArgs.getScope(), pArgs.getCollection());
173173
LOG.trace("findByQuery {} statement: {}", pArgs, statement);
174174
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
175175
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
@@ -214,7 +214,7 @@ public QueryOptions buildOptions(QueryOptions options) {
214214
@Override
215215
public Mono<Long> count() {
216216
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
217-
String statement = assembleEntityQuery(true, distinctFields, pArgs.getCollection());
217+
String statement = assembleEntityQuery(true, distinctFields, pArgs.getScope(), pArgs.getCollection());
218218
LOG.trace("findByQuery {} statement: {}", pArgs, statement);
219219
Mono<ReactiveQueryResult> countResult = pArgs.getScope() == null
220220
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
@@ -236,8 +236,8 @@ public Mono<Boolean> exists() {
236236
return count().map(count -> count > 0); // not efficient, just need the first one
237237
}
238238

239-
private String assembleEntityQuery(final boolean count, String[] distinctFields, String collection) {
240-
return query.toN1qlSelectString(template, collection, this.domainType, this.returnType, count,
239+
private String assembleEntityQuery(final boolean count, String[] distinctFields, String scope, String collection) {
240+
return query.toN1qlSelectString(template, scope, collection, this.domainType, this.returnType, count,
241241
query.getDistinctFields() != null ? query.getDistinctFields() : distinctFields, fields);
242242
}
243243
}

src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors
2+
* Copyright 2012-2022 the original author or authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -71,7 +71,7 @@ static class ReactiveRemoveByQuerySupport<T> implements ReactiveRemoveByQuery<T>
7171
@Override
7272
public Flux<RemoveResult> all() {
7373
PseudoArgs<QueryOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
74-
String statement = assembleDeleteQuery(pArgs.getCollection());
74+
String statement = assembleDeleteQuery(pArgs.getScope(), pArgs.getCollection());
7575
LOG.trace("removeByQuery {} statement: {}", pArgs, statement);
7676
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
7777
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
@@ -119,8 +119,8 @@ public RemoveByQueryConsistentWith<T> withConsistency(final QueryScanConsistency
119119
options);
120120
}
121121

122-
private String assembleDeleteQuery(String collection) {
123-
return query.toN1qlRemoveString(template, collection, this.domainType);
122+
private String assembleDeleteQuery(String scope, String collection) {
123+
return query.toN1qlRemoveString(template, scope, collection, this.domainType);
124124
}
125125

126126
@Override

src/main/java/org/springframework/data/couchbase/core/convert/join/N1qlJoinResolver.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,14 @@ public static <L, R> String buildQuery(ReactiveCouchbaseTemplate template, Strin
7373
String from = "FROM " + keySpacePair.lhs.keyspace + " lks " + useLKS + joinType + " " + keySpacePair.rhs.keyspace
7474
+ " rks";
7575

76-
StringBasedN1qlQueryParser.N1qlSpelValues n1qlL = Query.getN1qlSpelValues(template, keySpacePair.lhs.collection,
77-
parameters.getEntityTypeInfo().getType(), parameters.getEntityTypeInfo().getType(), false, null, null);
76+
StringBasedN1qlQueryParser.N1qlSpelValues n1qlL = Query.getN1qlSpelValues(template, null,
77+
keySpacePair.lhs.collection, parameters.getEntityTypeInfo().getType(), parameters.getEntityTypeInfo().getType(),
78+
false, null, null);
7879
String onLks = "lks." + n1qlL.filter;
7980

80-
StringBasedN1qlQueryParser.N1qlSpelValues n1qlR = Query.getN1qlSpelValues(template, keySpacePair.rhs.collection,
81-
parameters.getAssociatedEntityTypeInfo().getType(), parameters.getAssociatedEntityTypeInfo().getType(), false,
82-
null, null);
81+
StringBasedN1qlQueryParser.N1qlSpelValues n1qlR = Query.getN1qlSpelValues(template, null,
82+
keySpacePair.rhs.collection, parameters.getAssociatedEntityTypeInfo().getType(),
83+
parameters.getAssociatedEntityTypeInfo().getType(), false, null, null);
8384
String onRks = "rks." + n1qlR.filter;
8485

8586
StringBuilder useRKSBuilder = new StringBuilder();

src/main/java/org/springframework/data/couchbase/core/query/N1QLExpression.java

+7
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@ public static N1QLExpression count(N1QLExpression expression) {
209209
return x("COUNT(" + expression.toString() + ")");
210210
}
211211

212+
/**
213+
* Returned expression results in distinct of the expression
214+
*/
215+
public static N1QLExpression distinct(N1QLExpression expression) {
216+
return x("distinct{" + expression.toString() + "}");
217+
}
218+
212219
/**
213220
* Helper method to wrap varargs with the given character.
214221
*

src/main/java/org/springframework/data/couchbase/core/query/N1QLQuery.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors
2+
* Copyright 2012-2022 the original author or authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -48,8 +48,8 @@ public JsonObject n1ql() {
4848
}
4949

5050
@Override
51-
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass,
52-
Class returnClass, boolean isCount, String[] distinctFields, String[] fields) {
51+
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
52+
Class domainClass, Class returnClass, boolean isCount, String[] distinctFields, String[] fields) {
5353
return expression.toString();
5454
}
5555
}

src/main/java/org/springframework/data/couchbase/core/query/Query.java

+18-12
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,17 @@ public String export(int[]... paramIndexPtrHolder) { // used only by tests
338338
return sb.toString();
339339
}
340340

341+
/**
342+
*
343+
*/
344+
@Deprecated
341345
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, Class domainClass, boolean isCount) {
342-
return toN1qlSelectString(template, null, domainClass, null, isCount, null, null);
346+
return toN1qlSelectString(template, null, null, domainClass, null, isCount, null, null);
343347
}
344348

345-
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass,
346-
Class returnClass, boolean isCount, String[] distinctFields, String[] fields) {
347-
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, collectionName, domainClass,
349+
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
350+
Class domainClass, Class returnClass, boolean isCount, String[] distinctFields, String[] fields) {
351+
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, scopeName, collectionName, domainClass,
348352
returnClass, isCount, distinctFields, fields);
349353
final StringBuilder statement = new StringBuilder();
350354
appendString(statement, n1ql.selectEntity); // select ...
@@ -357,9 +361,10 @@ public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String coll
357361
return statement.toString();
358362
}
359363

360-
public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass) {
361-
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, collectionName, domainClass, null,
362-
false, null, null);
364+
public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
365+
Class domainClass) {
366+
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, scopeName, collectionName, domainClass,
367+
null, false, null, null);
363368
final StringBuilder statement = new StringBuilder();
364369
appendString(statement, n1ql.delete); // delete ...
365370
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
@@ -369,8 +374,8 @@ public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String coll
369374
}
370375

371376
public static StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(ReactiveCouchbaseTemplate template,
372-
String collectionName, Class domainClass, Class returnClass, boolean isCount, String[] distinctFields,
373-
String[] fields) {
377+
String scopeName, String collectionName, Class domainClass, Class returnClass, boolean isCount,
378+
String[] distinctFields, String[] fields) {
374379
String typeKey = template.getConverter().getTypeKey();
375380
final CouchbasePersistentEntity<?> persistentEntity = template.getConverter().getMappingContext()
376381
.getRequiredPersistentEntity(domainClass);
@@ -382,9 +387,10 @@ public static StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(Reacti
382387
typeValue = alias.toString();
383388
}
384389

385-
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(template.getBucketName(), collectionName,
386-
template.getConverter(), domainClass, returnClass, typeKey, typeValue, isCount, distinctFields, fields);
387-
return isCount ? sbnqp.getCountContext() : sbnqp.getStatementContext();
390+
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(template.getBucketName(), scopeName,
391+
collectionName, template.getConverter(), domainClass, returnClass, typeKey, typeValue, isCount, distinctFields,
392+
fields);
393+
return sbnqp.getStatementContext();
388394
}
389395

390396
/**

src/main/java/org/springframework/data/couchbase/core/query/StringQuery.java

+65-19
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,20 @@
1818
import java.util.Locale;
1919

2020
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
21+
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
2122
import org.springframework.data.couchbase.core.support.TemplateUtils;
23+
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
24+
import org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryParser;
25+
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
26+
import org.springframework.data.mapping.Alias;
27+
import org.springframework.data.repository.query.ParameterAccessor;
28+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
29+
import org.springframework.data.util.ClassTypeInformation;
30+
import org.springframework.data.util.TypeInformation;
31+
import org.springframework.expression.spel.standard.SpelExpressionParser;
2232

2333
import com.couchbase.client.java.json.JsonArray;
34+
import com.couchbase.client.java.json.JsonObject;
2435
import com.couchbase.client.java.json.JsonValue;
2536

2637
/**
@@ -38,32 +49,46 @@
3849
*/
3950
public class StringQuery extends Query {
4051

52+
private final CouchbaseQueryMethod queryMethod;
4153
private final String inlineN1qlQuery;
54+
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
55+
private final ParameterAccessor parameterAccessor;
56+
private final SpelExpressionParser spelExpressionParser;
4257

43-
public StringQuery(String n1qlString) {
44-
inlineN1qlQuery = n1qlString;
45-
}
46-
47-
/**
48-
* inlineN1qlQuery (Query Annotation) append the string query to the provided StringBuilder. To be used along with the
49-
* other append*() methods to construct the N1QL statement
50-
*
51-
* @param sb - StringBuilder
52-
*/
53-
private void appendInlineN1qlStatement(final StringBuilder sb) {
54-
sb.append(inlineN1qlQuery);
58+
public StringQuery(CouchbaseQueryMethod queryMethod, String n1qlString,
59+
QueryMethodEvaluationContextProvider queryMethodEvaluationContextProvider, ParameterAccessor parameterAccessor,
60+
SpelExpressionParser spelExpressionParser) {
61+
this.queryMethod = queryMethod;
62+
this.inlineN1qlQuery = n1qlString;
63+
this.evaluationContextProvider = queryMethodEvaluationContextProvider;
64+
this.parameterAccessor = parameterAccessor;
65+
this.spelExpressionParser = spelExpressionParser;
5566
}
5667

5768
@Override
58-
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String collection, Class domainClass,
59-
Class resultClass, boolean isCount, String[] distinctFields, String[] fields) {
69+
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scope, String collection,
70+
Class domainClass, Class resultClass, boolean isCount, String[] distinctFields, String[] fields) {
71+
72+
StringBasedN1qlQueryParser parser = getStringN1qlQueryParser(template, scope, collection, domainClass,
73+
distinctFields, fields);
74+
75+
N1QLExpression parsedExpression = parser.getExpression(inlineN1qlQuery, queryMethod, parameterAccessor,
76+
spelExpressionParser, evaluationContextProvider);
77+
78+
String queryString = parsedExpression.toString();
79+
80+
JsonValue parameters = parser.getPlaceholderValues(parameterAccessor);
81+
if (parameters instanceof JsonArray) {
82+
this.setPositionalParameters((JsonArray) parameters);
83+
} else {
84+
this.setNamedParameters((JsonObject) parameters);
85+
}
6086
final StringBuilder statement = new StringBuilder();
61-
boolean makeCount = isCount && inlineN1qlQuery != null
62-
&& !inlineN1qlQuery.toLowerCase(Locale.ROOT).contains("count(");
87+
boolean makeCount = isCount && queryString != null && !queryString.toLowerCase(Locale.ROOT).contains("count(");
6388
if (makeCount) {
6489
statement.append("SELECT COUNT(*) AS " + TemplateUtils.SELECT_COUNT + " FROM (");
6590
}
66-
appendInlineN1qlStatement(statement); // apply the string statement
91+
statement.append(queryString); // apply the string statement
6792
// To use generated parameters for literals
6893
// we need to figure out if we must use positional or named parameters
6994
// If we are using positional parameters, we need to start where
@@ -86,6 +111,26 @@ public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String coll
86111
return statement.toString();
87112
}
88113

114+
private StringBasedN1qlQueryParser getStringN1qlQueryParser(ReactiveCouchbaseTemplate template, String scopeName,
115+
String collectionName, Class domainClass, String[] distinctFields, String[] fields) {
116+
String typeKey = template.getConverter().getTypeKey();
117+
final CouchbasePersistentEntity<?> persistentEntity = template.getConverter().getMappingContext()
118+
.getRequiredPersistentEntity(domainClass);
119+
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);
120+
String typeValue = info.getJavaType().getName();
121+
TypeInformation<?> typeInfo = ClassTypeInformation.from(info.getJavaType());
122+
Alias alias = template.getConverter().getTypeAlias(typeInfo);
123+
if (alias != null && alias.isPresent()) {
124+
typeValue = alias.toString();
125+
}
126+
// there are no options for distinct and fields for @Query
127+
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(inlineN1qlQuery, queryMethod,
128+
template.getBucketName(), scopeName, collectionName, template.getConverter(), typeKey, typeValue,
129+
parameterAccessor, new SpelExpressionParser(), evaluationContextProvider);
130+
131+
return sbnqp;
132+
}
133+
89134
/**
90135
* toN1qlRemoveString - use toN1qlSelectString
91136
*
@@ -94,7 +139,8 @@ public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String coll
94139
* @param domainClass
95140
*/
96141
@Override
97-
public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass) {
98-
return toN1qlSelectString(template, collectionName, domainClass, domainClass, false, null, null);
142+
public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
143+
Class domainClass) {
144+
return toN1qlSelectString(template, scopeName, collectionName, domainClass, domainClass, false, null, null);
99145
}
100146
}

src/main/java/org/springframework/data/couchbase/repository/query/N1qlRepositoryQueryExecutor.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors
2+
* Copyright 2012-2022 the original author or authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -65,15 +65,16 @@ public Object execute(final Object[] parameters) {
6565
Query query;
6666
ExecutableFindByQuery q;
6767
if (queryMethod.hasN1qlAnnotation()) {
68-
query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), operations.getBucketName(),
69-
SPEL_PARSER, evaluationContextProvider, namedQueries).createQuery();
68+
query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), SPEL_PARSER,
69+
evaluationContextProvider, namedQueries).createQuery();
7070
} else {
7171
final PartTree tree = new PartTree(queryMethod.getName(), domainClass);
72-
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter(), operations.getBucketName()).createQuery();
72+
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter(), operations.getBucketName())
73+
.createQuery();
7374
}
7475

75-
ExecutableFindByQuery<?> operation = (ExecutableFindByQuery<?>) operations
76-
.findByQuery(domainClass).withConsistency(buildQueryScanConsistency());
76+
ExecutableFindByQuery<?> operation = (ExecutableFindByQuery<?>) operations.findByQuery(domainClass)
77+
.withConsistency(buildQueryScanConsistency());
7778
if (queryMethod.isCountQuery()) {
7879
return operation.matching(query).count();
7980
} else if (queryMethod.isCollectionQuery()) {

src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 the original author or authors.
2+
* Copyright 2020-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -77,8 +77,7 @@ public ReactiveStringBasedCouchbaseQuery(ReactiveCouchbaseQueryMethod method,
7777
protected Query createQuery(ParametersParameterAccessor accessor) {
7878

7979
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(accessor, getQueryMethod(),
80-
getOperations().getConverter(), getOperations().getBucketName(), expressionParser, evaluationContextProvider,
81-
namedQueries);
80+
getOperations().getConverter(), expressionParser, evaluationContextProvider, namedQueries);
8281

8382
Query query = creator.createQuery();
8483

src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 the original author or authors.
2+
* Copyright 2020-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -75,8 +75,7 @@ public StringBasedCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperation
7575
protected Query createQuery(ParametersParameterAccessor accessor) {
7676

7777
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(accessor, getQueryMethod(),
78-
getOperations().getConverter(), getOperations().getBucketName(), expressionParser, evaluationContextProvider,
79-
namedQueries);
78+
getOperations().getConverter(), expressionParser, evaluationContextProvider, namedQueries);
8079
Query query = creator.createQuery();
8180

8281
if (LOG.isTraceEnabled()) {

0 commit comments

Comments
 (0)