Skip to content

Commit 0ba6423

Browse files
authored
Restore StringQuery constructor that takes only string. (#1770)
Closes #1769.
1 parent 0ee1f8a commit 0ba6423

File tree

3 files changed

+116
-13
lines changed

3 files changed

+116
-13
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public StringQuery(CouchbaseQueryMethod queryMethod, String n1qlString,
6464
this.spelExpressionParser = spelExpressionParser;
6565
}
6666

67+
public StringQuery(String n1qlString) {
68+
this(null,n1qlString, null, null, null);
69+
}
70+
6771
@Override
6872
public String toN1qlSelectString(CouchbaseConverter converter, String bucketName, String scope, String collection,
6973
Class domainClass, Class resultClass, boolean isCount, String[] distinctFields, String[] fields) {

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

+57-13
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
3939
import org.springframework.data.couchbase.core.mapping.Expiration;
4040
import org.springframework.data.couchbase.core.query.N1QLExpression;
41+
import org.springframework.data.couchbase.core.query.StringQuery;
4142
import org.springframework.data.couchbase.repository.Query;
4243
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
4344
import org.springframework.data.mapping.PersistentEntity;
@@ -122,6 +123,12 @@ public class StringBasedN1qlQueryParser {
122123
* regexp that detect positional placeholder ($ followed by digits only)
123124
*/
124125
public static final Pattern POSITIONAL_PLACEHOLDER_PATTERN = Pattern.compile("\\W(\\$\\p{Digit}+)\\b");
126+
127+
/**
128+
* regexp that detect SPEL Expression (#{..})
129+
*/
130+
public static final Pattern SPEL_EXPRESSION_PATTERN = Pattern.compile("(#\\{[^\\}]*\\})");
131+
125132
/**
126133
* regexp that detects " and ' quote boundaries, ignoring escaped quotes
127134
*/
@@ -157,7 +164,8 @@ public StringBasedN1qlQueryParser(String statement, CouchbaseQueryMethod queryMe
157164
this.statement = statement;
158165
this.queryMethod = queryMethod;
159166
this.couchbaseConverter = couchbaseConverter;
160-
this.statementContext = createN1qlSpelValues(collection != null ? collection : bucketName, scope, collection,
167+
this.statementContext = queryMethod == null ? null
168+
: createN1qlSpelValues(collection != null ? collection : bucketName, scope, collection,
161169
queryMethod.getEntityInformation().getJavaType(), typeField, typeValue, queryMethod.isCountQuery(), null, null);
162170
this.parsedExpression = getExpression(statement, queryMethod, accessor, spelExpressionParser,
163171
evaluationContextProvider);
@@ -371,6 +379,9 @@ private void checkPlaceholders(String statement) {
371379
Matcher quoteMatcher = QUOTE_DETECTION_PATTERN.matcher(statement);
372380
Matcher positionMatcher = POSITIONAL_PLACEHOLDER_PATTERN.matcher(statement);
373381
Matcher namedMatcher = NAMED_PLACEHOLDER_PATTERN.matcher(statement);
382+
String queryIdentifier = (this.queryMethod != null ? queryMethod.getClass().getName()
383+
: StringQuery.class.getName()) + "."
384+
+ (this.queryMethod != null ? queryMethod.getName() : this.statement);
374385

375386
List<int[]> quotes = new ArrayList<int[]>();
376387
while (quoteMatcher.find()) {
@@ -383,8 +394,14 @@ private void checkPlaceholders(String statement) {
383394
while (positionMatcher.find()) {
384395
String placeholder = positionMatcher.group(1);
385396
// check not in quoted
386-
if (checkNotQuoted(placeholder, positionMatcher.start(), positionMatcher.end(), quotes)) {
387-
LOGGER.trace("{}: Found positional placeholder {}", this.queryMethod.getName(), placeholder);
397+
if (checkNotQuoted(placeholder, positionMatcher.start(), positionMatcher.end(), quotes, queryIdentifier)) {
398+
if (this.queryMethod == null) {
399+
throw new IllegalArgumentException(
400+
"StringQuery created from StringQuery(String) cannot have parameters. "
401+
+ "They cannot be processed. "
402+
+ "Use an @Query annotated method and the SPEL Expression #{[<n>]} : " + statement);
403+
}
404+
LOGGER.trace("{}: Found positional placeholder {}", queryIdentifier, placeholder);
388405
posCount++;
389406
parameterNames.add(placeholder.substring(1)); // save without the leading $
390407
}
@@ -393,17 +410,21 @@ private void checkPlaceholders(String statement) {
393410
while (namedMatcher.find()) {
394411
String placeholder = namedMatcher.group(1);
395412
// check not in quoted
396-
if (checkNotQuoted(placeholder, namedMatcher.start(), namedMatcher.end(), quotes)) {
397-
LOGGER.trace("{}: Found named placeholder {}", this.queryMethod.getName(), placeholder);
413+
if (checkNotQuoted(placeholder, namedMatcher.start(), namedMatcher.end(), quotes, queryIdentifier)) {
414+
if (this.queryMethod == null) {
415+
throw new IllegalArgumentException(
416+
"StringQuery created from StringQuery(String) cannot have parameters. "
417+
+ "Use an @Query annotated method and the SPEL Expression #{[<n>]} : " + statement);
418+
}
419+
LOGGER.trace("{}: Found named placeholder {}", queryIdentifier, placeholder);
398420
namedCount++;
399421
parameterNames.add(placeholder.substring(1));// save without the leading $
400422
}
401423
}
402424

403425
if (posCount > 0 && namedCount > 0) { // actual values from parameterNames might be more useful
404426
throw new IllegalArgumentException("Using both named (" + namedCount + ") and positional (" + posCount
405-
+ ") placeholders is not supported, please choose one over the other in " + queryMethod.getClass().getName()
406-
+ "." + this.queryMethod.getName() + "()");
427+
+ ") placeholders is not supported, please choose one over the other in " + queryIdentifier + "()");
407428
}
408429

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

418-
private boolean checkNotQuoted(String item, int start, int end, List<int[]> quotes) {
457+
private boolean checkNotQuoted(String item, int start, int end, List<int[]> quotes, String queryIdentifier) {
419458
for (int[] quote : quotes) {
420459
if (quote[0] <= start && quote[1] >= end) {
421-
LOGGER.trace("{}: potential placeholder {} is inside quotes, ignored", this.queryMethod.getName(), item);
460+
LOGGER.trace("{}: potential placeholder {} is inside quotes, ignored", queryIdentifier, item);
422461
return false;
423462
}
424463
}
@@ -647,10 +686,15 @@ public N1qlSpelValues(String selectClause, String entityFields, String bucket, S
647686

648687
public N1QLExpression getExpression(String statement, CouchbaseQueryMethod queryMethod, ParameterAccessor accessor,
649688
SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
650-
Object[] runtimeParameters = getParameters(accessor);
651-
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(queryMethod.getParameters(),
652-
runtimeParameters);
653-
N1QLExpression parsedStatement = x(doParse(statement, parser, evaluationContext, this.getStatementContext()));
689+
N1QLExpression parsedStatement;
690+
if (accessor != null && queryMethod != null && parser != null) {
691+
Object[] runtimeParameters = getParameters(accessor);
692+
EvaluationContext evaluationContext = evaluationContextProvider
693+
.getEvaluationContext(queryMethod.getParameters(), runtimeParameters);
694+
parsedStatement = x(doParse(statement, parser, evaluationContext, this.getStatementContext()));
695+
} else {
696+
parsedStatement = x(statement);
697+
}
654698
checkPlaceholders(parsedStatement.toString());
655699
return parsedStatement;
656700
}

src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java

+55
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.couchbase.repository.query;
1717

1818
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
import static org.junit.jupiter.api.Assertions.assertThrows;
1920
import static org.junit.jupiter.api.Assertions.fail;
2021

2122
import java.lang.reflect.Method;
@@ -29,6 +30,7 @@
2930
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
3031
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
3132
import org.springframework.data.couchbase.core.query.Query;
33+
import org.springframework.data.couchbase.core.query.StringQuery;
3234
import org.springframework.data.couchbase.domain.User;
3335
import org.springframework.data.couchbase.domain.UserRepository;
3436
import org.springframework.data.mapping.context.MappingContext;
@@ -138,6 +140,59 @@ void createsQueryCorrectly2() throws Exception {
138140
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null));
139141
}
140142

143+
@Test
144+
void stringQuerycreatesQueryCorrectly() throws Exception {
145+
String queryString = "a b c";
146+
Query query = new StringQuery(queryString);
147+
assertEquals(queryString, query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class,
148+
false, null, null));
149+
}
150+
151+
@Test
152+
void stringQueryNoPositionalParameters() {
153+
String queryString = " $1";
154+
Query query = new StringQuery(queryString);
155+
assertThrows(IllegalArgumentException.class, () -> query.toN1qlSelectString(converter, bucketName(), null, null,
156+
User.class, User.class, false, null, null));
157+
}
158+
159+
@Test
160+
void stringQueryNoNamedParameters() {
161+
String queryString = " $george";
162+
Query query = new StringQuery(queryString);
163+
assertThrows(IllegalArgumentException.class, () -> query.toN1qlSelectString(converter, bucketName(), null, null,
164+
User.class, User.class, false, null, null));
165+
}
166+
167+
@Test
168+
void stringQueryNoSpelExpressions() {
169+
String queryString = "#{#n1ql.filter}";
170+
Query query = new StringQuery(queryString);
171+
assertThrows(IllegalArgumentException.class, () -> query.toN1qlSelectString(converter, bucketName(), null, null,
172+
User.class, User.class, false, null, null));
173+
}
174+
175+
@Test
176+
void stringQueryNoPositionalParametersQuotes() {
177+
String queryString = " '$1'";
178+
Query query = new StringQuery(queryString);
179+
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null);
180+
}
181+
182+
@Test
183+
void stringQueryNoNamedParametersQuotes() {
184+
String queryString = " '$george'";
185+
Query query = new StringQuery(queryString);
186+
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null);
187+
}
188+
189+
@Test
190+
void stringQueryNoSpelExpressionsQuotes() {
191+
String queryString = "'#{#n1ql.filter}'";
192+
Query query = new StringQuery(queryString);
193+
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null);
194+
}
195+
141196
@Test
142197
void spelTests() throws Exception {
143198
String input = "spelTests";

0 commit comments

Comments
 (0)