Skip to content

Add n1ql.scope and n1ql.collection spel expressions for @Query. #1460

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/main/asciidoc/repository.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
****
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public Mono<T> first() {
@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,
Expand Down Expand Up @@ -214,7 +214,7 @@ public QueryOptions buildOptions(QueryOptions options) {
@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,
Expand All @@ -236,8 +236,8 @@ public Mono<Boolean> exists() {
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);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -71,7 +71,7 @@ static class ReactiveRemoveByQuerySupport<T> implements ReactiveRemoveByQuery<T>
@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,
Expand Down Expand Up @@ -119,8 +119,8 @@ public RemoveByQueryConsistentWith<T> withConsistency(final QueryScanConsistency
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ public static <L, R> String buildQuery(ReactiveCouchbaseTemplate template, Strin
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ public static N1QLExpression count(N1QLExpression expression) {
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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -48,8 +48,8 @@ public JsonObject n1ql() {
}

@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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -338,13 +338,17 @@ public String export(int[]... paramIndexPtrHolder) { // used only by tests
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 ...
Expand All @@ -357,9 +361,10 @@ public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String coll
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
Expand All @@ -369,8 +374,8 @@ public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String coll
}

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);
Expand All @@ -382,9 +387,10 @@ public static StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(Reacti
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();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,20 @@
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;

/**
Expand All @@ -38,32 +49,46 @@
*/
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
Expand All @@ -86,6 +111,26 @@ public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String coll
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
*
Expand All @@ -94,7 +139,8 @@ public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String coll
* @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);
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -65,15 +65,16 @@ public Object execute(final Object[] parameters) {
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()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-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.
Expand Down Expand Up @@ -77,8 +77,7 @@ public ReactiveStringBasedCouchbaseQuery(ReactiveCouchbaseQueryMethod method,
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();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-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.
Expand Down Expand Up @@ -75,8 +75,7 @@ public StringBasedCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperation
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()) {
Expand Down
Loading