Skip to content

Commit e7474ae

Browse files
committed
Restore StringQuery constructor that takes only string. (#1770)
Closes #1769.
1 parent e4663b3 commit e7474ae

File tree

4 files changed

+223
-26
lines changed

4 files changed

+223
-26
lines changed

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

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,22 @@ public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scop
361361
return statement.toString();
362362
}
363363

364+
public String toN1qlSelectString(CouchbaseConverter converter, String bucketName, String scopeName,
365+
String collectionName, Class domainClass, Class returnClass, boolean isCount, String[] distinctFields,
366+
String[] fields) {
367+
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(converter, bucketName, scopeName,
368+
collectionName, domainClass, returnClass, isCount, distinctFields, fields);
369+
final StringBuilder statement = new StringBuilder();
370+
appendString(statement, n1ql.selectEntity); // select ...
371+
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
372+
appendWhere(statement, new int[] { 0 }, converter); // criteria on this Query
373+
if (!isCount) {
374+
appendSort(statement);
375+
appendSkipAndLimit(statement);
376+
}
377+
return statement.toString();
378+
}
379+
364380
public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
365381
Class domainClass) {
366382
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, scopeName, collectionName, domainClass,
@@ -376,19 +392,27 @@ public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, String scop
376392
public static StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(ReactiveCouchbaseTemplate template,
377393
String scopeName, String collectionName, Class domainClass, Class returnClass, boolean isCount,
378394
String[] distinctFields, String[] fields) {
379-
String typeKey = template.getConverter().getTypeKey();
380-
final CouchbasePersistentEntity<?> persistentEntity = template.getConverter().getMappingContext()
395+
return getN1qlSpelValues(template.getConverter(), template.getBucketName(), scopeName, collectionName,
396+
domainClass, returnClass, isCount, distinctFields, fields);
397+
}
398+
399+
public static StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(CouchbaseConverter converter,
400+
String bucketName,
401+
String scopeName, String collectionName, Class domainClass, Class returnClass, boolean isCount,
402+
String[] distinctFields, String[] fields) {
403+
String typeKey = converter.getTypeKey();
404+
final CouchbasePersistentEntity<?> persistentEntity = converter.getMappingContext()
381405
.getRequiredPersistentEntity(domainClass);
382406
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);
383407
String typeValue = info.getJavaType().getName();
384408
TypeInformation<?> typeInfo = ClassTypeInformation.from(info.getJavaType());
385-
Alias alias = template.getConverter().getTypeAlias(typeInfo);
409+
Alias alias = converter.getTypeAlias(typeInfo);
386410
if (alias != null && alias.isPresent()) {
387411
typeValue = alias.toString();
388412
}
389413

390-
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(template.getBucketName(), scopeName,
391-
collectionName, template.getConverter(), domainClass, returnClass, typeKey, typeValue, isCount, distinctFields,
414+
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(bucketName, scopeName, collectionName,
415+
converter, domainClass, returnClass, typeKey, typeValue, isCount, distinctFields,
392416
fields);
393417
return sbnqp.getStatementContext();
394418
}

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Locale;
1919

2020
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
21+
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
2122
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
2223
import org.springframework.data.couchbase.core.support.TemplateUtils;
2324
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
@@ -65,11 +66,18 @@ public StringQuery(CouchbaseQueryMethod queryMethod, String n1qlString,
6566
this.spelExpressionParser = spelExpressionParser;
6667
}
6768

69+
public StringQuery(String n1qlString) {
70+
this(null,n1qlString, null, null, null);
71+
}
72+
6873
@Override
6974
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scope, String collection,
7075
Class domainClass, Class resultClass, boolean isCount, String[] distinctFields, String[] fields) {
71-
72-
StringBasedN1qlQueryParser parser = getStringN1qlQueryParser(template, scope, collection, domainClass,
76+
return toN1qlSelectString(template.getConverter(), template.getBucketName(), scope, collection, domainClass, resultClass, isCount, distinctFields, fields );
77+
}
78+
public String toN1qlSelectString(CouchbaseConverter converter, String bucketName, String scope, String collection,
79+
Class domainClass, Class resultClass, boolean isCount, String[] distinctFields, String[] fields) {
80+
StringBasedN1qlQueryParser parser = getStringN1qlQueryParser(converter, bucketName, scope, collection, domainClass,
7381
distinctFields, fields);
7482

7583
N1QLExpression parsedExpression = parser.getExpression(inlineN1qlQuery, queryMethod, parameterAccessor,
@@ -100,7 +108,7 @@ public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scop
100108
} else { // named parameters or no parameters, no index required
101109
paramIndexPtr = new int[] { -1 };
102110
}
103-
appendWhere(statement, paramIndexPtr, template.getConverter()); // criteria on this Query - should be empty for
111+
appendWhere(statement, paramIndexPtr, converter); // criteria on this Query - should be empty for
104112
if (!isCount) {
105113
appendSort(statement);
106114
appendSkipAndLimit(statement);
@@ -111,21 +119,21 @@ public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String scop
111119
return statement.toString();
112120
}
113121

114-
private StringBasedN1qlQueryParser getStringN1qlQueryParser(ReactiveCouchbaseTemplate template, String scopeName,
122+
private StringBasedN1qlQueryParser getStringN1qlQueryParser(CouchbaseConverter converter, String bucketName, String scopeName,
115123
String collectionName, Class domainClass, String[] distinctFields, String[] fields) {
116-
String typeKey = template.getConverter().getTypeKey();
117-
final CouchbasePersistentEntity<?> persistentEntity = template.getConverter().getMappingContext()
124+
String typeKey = converter.getTypeKey();
125+
final CouchbasePersistentEntity<?> persistentEntity = converter.getMappingContext()
118126
.getRequiredPersistentEntity(domainClass);
119127
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);
120128
String typeValue = info.getJavaType().getName();
121129
TypeInformation<?> typeInfo = ClassTypeInformation.from(info.getJavaType());
122-
Alias alias = template.getConverter().getTypeAlias(typeInfo);
130+
Alias alias = converter.getTypeAlias(typeInfo);
123131
if (alias != null && alias.isPresent()) {
124132
typeValue = alias.toString();
125133
}
126134
// there are no options for distinct and fields for @Query
127135
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(inlineN1qlQuery, queryMethod,
128-
template.getBucketName(), scopeName, collectionName, template.getConverter(), typeKey, typeValue,
136+
bucketName, scopeName, collectionName, converter, typeKey, typeValue,
129137
parameterAccessor, new SpelExpressionParser(), evaluationContextProvider);
130138

131139
return sbnqp;

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

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
3737
import org.springframework.data.couchbase.core.mapping.Expiration;
3838
import org.springframework.data.couchbase.core.query.N1QLExpression;
39+
import org.springframework.data.couchbase.core.query.StringQuery;
3940
import org.springframework.data.couchbase.repository.Query;
4041
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
4142
import org.springframework.data.mapping.PersistentEntity;
@@ -120,6 +121,12 @@ public class StringBasedN1qlQueryParser {
120121
* regexp that detect positional placeholder ($ followed by digits only)
121122
*/
122123
public static final Pattern POSITIONAL_PLACEHOLDER_PATTERN = Pattern.compile("\\W(\\$\\p{Digit}+)\\b");
124+
125+
/**
126+
* regexp that detect SPEL Expression (#{..})
127+
*/
128+
public static final Pattern SPEL_EXPRESSION_PATTERN = Pattern.compile("(#\\{[^\\}]*\\})");
129+
123130
/**
124131
* regexp that detects " and ' quote boundaries, ignoring escaped quotes
125132
*/
@@ -155,7 +162,8 @@ public StringBasedN1qlQueryParser(String statement, CouchbaseQueryMethod queryMe
155162
this.statement = statement;
156163
this.queryMethod = queryMethod;
157164
this.couchbaseConverter = couchbaseConverter;
158-
this.statementContext = createN1qlSpelValues(collection != null ? collection : bucketName, scope, collection,
165+
this.statementContext = queryMethod == null ? null
166+
: createN1qlSpelValues(collection != null ? collection : bucketName, scope, collection,
159167
queryMethod.getEntityInformation().getJavaType(), typeField, typeValue, queryMethod.isCountQuery(), null, null);
160168
this.parsedExpression = getExpression(statement, queryMethod, accessor, spelExpressionParser,
161169
evaluationContextProvider);
@@ -369,6 +377,9 @@ private void checkPlaceholders(String statement) {
369377
Matcher quoteMatcher = QUOTE_DETECTION_PATTERN.matcher(statement);
370378
Matcher positionMatcher = POSITIONAL_PLACEHOLDER_PATTERN.matcher(statement);
371379
Matcher namedMatcher = NAMED_PLACEHOLDER_PATTERN.matcher(statement);
380+
String queryIdentifier = (this.queryMethod != null ? queryMethod.getClass().getName()
381+
: StringQuery.class.getName()) + "."
382+
+ (this.queryMethod != null ? queryMethod.getName() : this.statement);
372383

373384
List<int[]> quotes = new ArrayList<int[]>();
374385
while (quoteMatcher.find()) {
@@ -381,8 +392,14 @@ private void checkPlaceholders(String statement) {
381392
while (positionMatcher.find()) {
382393
String placeholder = positionMatcher.group(1);
383394
// check not in quoted
384-
if (checkNotQuoted(placeholder, positionMatcher.start(), positionMatcher.end(), quotes)) {
385-
LOGGER.trace("{}: Found positional placeholder {}", this.queryMethod.getName(), placeholder);
395+
if (checkNotQuoted(placeholder, positionMatcher.start(), positionMatcher.end(), quotes, queryIdentifier)) {
396+
if (this.queryMethod == null) {
397+
throw new IllegalArgumentException(
398+
"StringQuery created from StringQuery(String) cannot have parameters. "
399+
+ "They cannot be processed. "
400+
+ "Use an @Query annotated method and the SPEL Expression #{[<n>]} : " + statement);
401+
}
402+
LOGGER.trace("{}: Found positional placeholder {}", queryIdentifier, placeholder);
386403
posCount++;
387404
parameterNames.add(placeholder.substring(1)); // save without the leading $
388405
}
@@ -391,17 +408,21 @@ private void checkPlaceholders(String statement) {
391408
while (namedMatcher.find()) {
392409
String placeholder = namedMatcher.group(1);
393410
// check not in quoted
394-
if (checkNotQuoted(placeholder, namedMatcher.start(), namedMatcher.end(), quotes)) {
395-
LOGGER.trace("{}: Found named placeholder {}", this.queryMethod.getName(), placeholder);
411+
if (checkNotQuoted(placeholder, namedMatcher.start(), namedMatcher.end(), quotes, queryIdentifier)) {
412+
if (this.queryMethod == null) {
413+
throw new IllegalArgumentException(
414+
"StringQuery created from StringQuery(String) cannot have parameters. "
415+
+ "Use an @Query annotated method and the SPEL Expression #{[<n>]} : " + statement);
416+
}
417+
LOGGER.trace("{}: Found named placeholder {}", queryIdentifier, placeholder);
396418
namedCount++;
397419
parameterNames.add(placeholder.substring(1));// save without the leading $
398420
}
399421
}
400422

401423
if (posCount > 0 && namedCount > 0) { // actual values from parameterNames might be more useful
402424
throw new IllegalArgumentException("Using both named (" + namedCount + ") and positional (" + posCount
403-
+ ") placeholders is not supported, please choose one over the other in " + queryMethod.getClass().getName()
404-
+ "." + this.queryMethod.getName() + "()");
425+
+ ") placeholders is not supported, please choose one over the other in " + queryIdentifier + "()");
405426
}
406427

407428
if (posCount > 0) {
@@ -411,12 +432,30 @@ private void checkPlaceholders(String statement) {
411432
} else {
412433
placeHolderType = PlaceholderType.NONE;
413434
}
435+
436+
if (this.queryMethod == null) {
437+
Matcher spelMatcher = SPEL_EXPRESSION_PATTERN.matcher(statement);
438+
while (spelMatcher.find()) {
439+
String placeholder = spelMatcher.group(1);
440+
// check not in quoted
441+
if (checkNotQuoted(placeholder, spelMatcher.start(), spelMatcher.end(), quotes, queryIdentifier)) {
442+
if (this.queryMethod == null) {
443+
throw new IllegalArgumentException(
444+
"StringQuery created from StringQuery(String) cannot SPEL expressions. "
445+
+ "Use an @Query annotated method and the SPEL Expression #{[<n>]} : "
446+
+ statement);
447+
}
448+
LOGGER.trace("{}: Found SPEL Experssion {}", queryIdentifier, placeholder);
449+
}
450+
}
451+
}
452+
414453
}
415454

416-
private boolean checkNotQuoted(String item, int start, int end, List<int[]> quotes) {
455+
private boolean checkNotQuoted(String item, int start, int end, List<int[]> quotes, String queryIdentifier) {
417456
for (int[] quote : quotes) {
418457
if (quote[0] <= start && quote[1] >= end) {
419-
LOGGER.trace("{}: potential placeholder {} is inside quotes, ignored", this.queryMethod.getName(), item);
458+
LOGGER.trace("{}: potential placeholder {} is inside quotes, ignored", queryIdentifier, item);
420459
return false;
421460
}
422461
}
@@ -634,10 +673,15 @@ public N1qlSpelValues(String selectClause, String entityFields, String bucket, S
634673

635674
public N1QLExpression getExpression(String statement, CouchbaseQueryMethod queryMethod, ParameterAccessor accessor,
636675
SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
637-
Object[] runtimeParameters = getParameters(accessor);
638-
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(queryMethod.getParameters(),
639-
runtimeParameters);
640-
N1QLExpression parsedStatement = x(doParse(statement, parser, evaluationContext, this.getStatementContext()));
676+
N1QLExpression parsedStatement;
677+
if (accessor != null && queryMethod != null && parser != null) {
678+
Object[] runtimeParameters = getParameters(accessor);
679+
EvaluationContext evaluationContext = evaluationContextProvider
680+
.getEvaluationContext(queryMethod.getParameters(), runtimeParameters);
681+
parsedStatement = x(doParse(statement, parser, evaluationContext, this.getStatementContext()));
682+
} else {
683+
parsedStatement = x(statement);
684+
}
641685
checkPlaceholders(parsedStatement.toString());
642686
return parsedStatement;
643687
}

0 commit comments

Comments
 (0)