Restore StringQuery constructor that takes only string. (#1770)
Closes #1769.
This commit is contained in:
@@ -64,6 +64,10 @@ public class StringQuery extends Query {
|
|||||||
this.spelExpressionParser = spelExpressionParser;
|
this.spelExpressionParser = spelExpressionParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StringQuery(String n1qlString) {
|
||||||
|
this(null,n1qlString, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toN1qlSelectString(CouchbaseConverter converter, String bucketName, String scope, String collection,
|
public String toN1qlSelectString(CouchbaseConverter converter, String bucketName, String scope, String collection,
|
||||||
Class domainClass, Class resultClass, boolean isCount, String[] distinctFields, String[] fields) {
|
Class domainClass, Class resultClass, boolean isCount, String[] distinctFields, String[] fields) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseList;
|
|||||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||||
import org.springframework.data.couchbase.core.mapping.Expiration;
|
import org.springframework.data.couchbase.core.mapping.Expiration;
|
||||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||||
|
import org.springframework.data.couchbase.core.query.StringQuery;
|
||||||
import org.springframework.data.couchbase.repository.Query;
|
import org.springframework.data.couchbase.repository.Query;
|
||||||
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
|
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
|
||||||
import org.springframework.data.mapping.PersistentEntity;
|
import org.springframework.data.mapping.PersistentEntity;
|
||||||
@@ -122,6 +123,12 @@ public class StringBasedN1qlQueryParser {
|
|||||||
* regexp that detect positional placeholder ($ followed by digits only)
|
* regexp that detect positional placeholder ($ followed by digits only)
|
||||||
*/
|
*/
|
||||||
public static final Pattern POSITIONAL_PLACEHOLDER_PATTERN = Pattern.compile("\\W(\\$\\p{Digit}+)\\b");
|
public static final Pattern POSITIONAL_PLACEHOLDER_PATTERN = Pattern.compile("\\W(\\$\\p{Digit}+)\\b");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* regexp that detect SPEL Expression (#{..})
|
||||||
|
*/
|
||||||
|
public static final Pattern SPEL_EXPRESSION_PATTERN = Pattern.compile("(#\\{[^\\}]*\\})");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* regexp that detects " and ' quote boundaries, ignoring escaped quotes
|
* regexp that detects " and ' quote boundaries, ignoring escaped quotes
|
||||||
*/
|
*/
|
||||||
@@ -157,7 +164,8 @@ public class StringBasedN1qlQueryParser {
|
|||||||
this.statement = statement;
|
this.statement = statement;
|
||||||
this.queryMethod = queryMethod;
|
this.queryMethod = queryMethod;
|
||||||
this.couchbaseConverter = couchbaseConverter;
|
this.couchbaseConverter = couchbaseConverter;
|
||||||
this.statementContext = createN1qlSpelValues(collection != null ? collection : bucketName, scope, collection,
|
this.statementContext = queryMethod == null ? null
|
||||||
|
: createN1qlSpelValues(collection != null ? collection : bucketName, scope, collection,
|
||||||
queryMethod.getEntityInformation().getJavaType(), typeField, typeValue, queryMethod.isCountQuery(), null, null);
|
queryMethod.getEntityInformation().getJavaType(), typeField, typeValue, queryMethod.isCountQuery(), null, null);
|
||||||
this.parsedExpression = getExpression(statement, queryMethod, accessor, spelExpressionParser,
|
this.parsedExpression = getExpression(statement, queryMethod, accessor, spelExpressionParser,
|
||||||
evaluationContextProvider);
|
evaluationContextProvider);
|
||||||
@@ -371,6 +379,9 @@ public class StringBasedN1qlQueryParser {
|
|||||||
Matcher quoteMatcher = QUOTE_DETECTION_PATTERN.matcher(statement);
|
Matcher quoteMatcher = QUOTE_DETECTION_PATTERN.matcher(statement);
|
||||||
Matcher positionMatcher = POSITIONAL_PLACEHOLDER_PATTERN.matcher(statement);
|
Matcher positionMatcher = POSITIONAL_PLACEHOLDER_PATTERN.matcher(statement);
|
||||||
Matcher namedMatcher = NAMED_PLACEHOLDER_PATTERN.matcher(statement);
|
Matcher namedMatcher = NAMED_PLACEHOLDER_PATTERN.matcher(statement);
|
||||||
|
String queryIdentifier = (this.queryMethod != null ? queryMethod.getClass().getName()
|
||||||
|
: StringQuery.class.getName()) + "."
|
||||||
|
+ (this.queryMethod != null ? queryMethod.getName() : this.statement);
|
||||||
|
|
||||||
List<int[]> quotes = new ArrayList<int[]>();
|
List<int[]> quotes = new ArrayList<int[]>();
|
||||||
while (quoteMatcher.find()) {
|
while (quoteMatcher.find()) {
|
||||||
@@ -383,8 +394,14 @@ public class StringBasedN1qlQueryParser {
|
|||||||
while (positionMatcher.find()) {
|
while (positionMatcher.find()) {
|
||||||
String placeholder = positionMatcher.group(1);
|
String placeholder = positionMatcher.group(1);
|
||||||
// check not in quoted
|
// check not in quoted
|
||||||
if (checkNotQuoted(placeholder, positionMatcher.start(), positionMatcher.end(), quotes)) {
|
if (checkNotQuoted(placeholder, positionMatcher.start(), positionMatcher.end(), quotes, queryIdentifier)) {
|
||||||
LOGGER.trace("{}: Found positional placeholder {}", this.queryMethod.getName(), placeholder);
|
if (this.queryMethod == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"StringQuery created from StringQuery(String) cannot have parameters. "
|
||||||
|
+ "They cannot be processed. "
|
||||||
|
+ "Use an @Query annotated method and the SPEL Expression #{[<n>]} : " + statement);
|
||||||
|
}
|
||||||
|
LOGGER.trace("{}: Found positional placeholder {}", queryIdentifier, placeholder);
|
||||||
posCount++;
|
posCount++;
|
||||||
parameterNames.add(placeholder.substring(1)); // save without the leading $
|
parameterNames.add(placeholder.substring(1)); // save without the leading $
|
||||||
}
|
}
|
||||||
@@ -393,8 +410,13 @@ public class StringBasedN1qlQueryParser {
|
|||||||
while (namedMatcher.find()) {
|
while (namedMatcher.find()) {
|
||||||
String placeholder = namedMatcher.group(1);
|
String placeholder = namedMatcher.group(1);
|
||||||
// check not in quoted
|
// check not in quoted
|
||||||
if (checkNotQuoted(placeholder, namedMatcher.start(), namedMatcher.end(), quotes)) {
|
if (checkNotQuoted(placeholder, namedMatcher.start(), namedMatcher.end(), quotes, queryIdentifier)) {
|
||||||
LOGGER.trace("{}: Found named placeholder {}", this.queryMethod.getName(), placeholder);
|
if (this.queryMethod == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"StringQuery created from StringQuery(String) cannot have parameters. "
|
||||||
|
+ "Use an @Query annotated method and the SPEL Expression #{[<n>]} : " + statement);
|
||||||
|
}
|
||||||
|
LOGGER.trace("{}: Found named placeholder {}", queryIdentifier, placeholder);
|
||||||
namedCount++;
|
namedCount++;
|
||||||
parameterNames.add(placeholder.substring(1));// save without the leading $
|
parameterNames.add(placeholder.substring(1));// save without the leading $
|
||||||
}
|
}
|
||||||
@@ -402,8 +424,7 @@ public class StringBasedN1qlQueryParser {
|
|||||||
|
|
||||||
if (posCount > 0 && namedCount > 0) { // actual values from parameterNames might be more useful
|
if (posCount > 0 && namedCount > 0) { // actual values from parameterNames might be more useful
|
||||||
throw new IllegalArgumentException("Using both named (" + namedCount + ") and positional (" + posCount
|
throw new IllegalArgumentException("Using both named (" + namedCount + ") and positional (" + posCount
|
||||||
+ ") placeholders is not supported, please choose one over the other in " + queryMethod.getClass().getName()
|
+ ") placeholders is not supported, please choose one over the other in " + queryIdentifier + "()");
|
||||||
+ "." + this.queryMethod.getName() + "()");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (posCount > 0) {
|
if (posCount > 0) {
|
||||||
@@ -413,12 +434,30 @@ public class StringBasedN1qlQueryParser {
|
|||||||
} else {
|
} else {
|
||||||
placeHolderType = PlaceholderType.NONE;
|
placeHolderType = PlaceholderType.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.queryMethod == null) {
|
||||||
|
Matcher spelMatcher = SPEL_EXPRESSION_PATTERN.matcher(statement);
|
||||||
|
while (spelMatcher.find()) {
|
||||||
|
String placeholder = spelMatcher.group(1);
|
||||||
|
// check not in quoted
|
||||||
|
if (checkNotQuoted(placeholder, spelMatcher.start(), spelMatcher.end(), quotes, queryIdentifier)) {
|
||||||
|
if (this.queryMethod == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"StringQuery created from StringQuery(String) cannot SPEL expressions. "
|
||||||
|
+ "Use an @Query annotated method and the SPEL Expression #{[<n>]} : "
|
||||||
|
+ statement);
|
||||||
|
}
|
||||||
|
LOGGER.trace("{}: Found SPEL Experssion {}", queryIdentifier, placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkNotQuoted(String item, int start, int end, List<int[]> quotes) {
|
private boolean checkNotQuoted(String item, int start, int end, List<int[]> quotes, String queryIdentifier) {
|
||||||
for (int[] quote : quotes) {
|
for (int[] quote : quotes) {
|
||||||
if (quote[0] <= start && quote[1] >= end) {
|
if (quote[0] <= start && quote[1] >= end) {
|
||||||
LOGGER.trace("{}: potential placeholder {} is inside quotes, ignored", this.queryMethod.getName(), item);
|
LOGGER.trace("{}: potential placeholder {} is inside quotes, ignored", queryIdentifier, item);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -647,10 +686,15 @@ public class StringBasedN1qlQueryParser {
|
|||||||
|
|
||||||
public N1QLExpression getExpression(String statement, CouchbaseQueryMethod queryMethod, ParameterAccessor accessor,
|
public N1QLExpression getExpression(String statement, CouchbaseQueryMethod queryMethod, ParameterAccessor accessor,
|
||||||
SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||||
Object[] runtimeParameters = getParameters(accessor);
|
N1QLExpression parsedStatement;
|
||||||
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(queryMethod.getParameters(),
|
if (accessor != null && queryMethod != null && parser != null) {
|
||||||
runtimeParameters);
|
Object[] runtimeParameters = getParameters(accessor);
|
||||||
N1QLExpression parsedStatement = x(doParse(statement, parser, evaluationContext, this.getStatementContext()));
|
EvaluationContext evaluationContext = evaluationContextProvider
|
||||||
|
.getEvaluationContext(queryMethod.getParameters(), runtimeParameters);
|
||||||
|
parsedStatement = x(doParse(statement, parser, evaluationContext, this.getStatementContext()));
|
||||||
|
} else {
|
||||||
|
parsedStatement = x(statement);
|
||||||
|
}
|
||||||
checkPlaceholders(parsedStatement.toString());
|
checkPlaceholders(parsedStatement.toString());
|
||||||
return parsedStatement;
|
return parsedStatement;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
package org.springframework.data.couchbase.repository.query;
|
package org.springframework.data.couchbase.repository.query;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -29,6 +30,7 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
|
|||||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||||
import org.springframework.data.couchbase.core.query.Query;
|
import org.springframework.data.couchbase.core.query.Query;
|
||||||
|
import org.springframework.data.couchbase.core.query.StringQuery;
|
||||||
import org.springframework.data.couchbase.domain.User;
|
import org.springframework.data.couchbase.domain.User;
|
||||||
import org.springframework.data.couchbase.domain.UserRepository;
|
import org.springframework.data.couchbase.domain.UserRepository;
|
||||||
import org.springframework.data.mapping.context.MappingContext;
|
import org.springframework.data.mapping.context.MappingContext;
|
||||||
@@ -138,6 +140,59 @@ class StringN1qlQueryCreatorTests {
|
|||||||
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null));
|
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stringQuerycreatesQueryCorrectly() throws Exception {
|
||||||
|
String queryString = "a b c";
|
||||||
|
Query query = new StringQuery(queryString);
|
||||||
|
assertEquals(queryString, query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class,
|
||||||
|
false, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stringQueryNoPositionalParameters() {
|
||||||
|
String queryString = " $1";
|
||||||
|
Query query = new StringQuery(queryString);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> query.toN1qlSelectString(converter, bucketName(), null, null,
|
||||||
|
User.class, User.class, false, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stringQueryNoNamedParameters() {
|
||||||
|
String queryString = " $george";
|
||||||
|
Query query = new StringQuery(queryString);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> query.toN1qlSelectString(converter, bucketName(), null, null,
|
||||||
|
User.class, User.class, false, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stringQueryNoSpelExpressions() {
|
||||||
|
String queryString = "#{#n1ql.filter}";
|
||||||
|
Query query = new StringQuery(queryString);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> query.toN1qlSelectString(converter, bucketName(), null, null,
|
||||||
|
User.class, User.class, false, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stringQueryNoPositionalParametersQuotes() {
|
||||||
|
String queryString = " '$1'";
|
||||||
|
Query query = new StringQuery(queryString);
|
||||||
|
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stringQueryNoNamedParametersQuotes() {
|
||||||
|
String queryString = " '$george'";
|
||||||
|
Query query = new StringQuery(queryString);
|
||||||
|
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stringQueryNoSpelExpressionsQuotes() {
|
||||||
|
String queryString = "'#{#n1ql.filter}'";
|
||||||
|
Query query = new StringQuery(queryString);
|
||||||
|
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void spelTests() throws Exception {
|
void spelTests() throws Exception {
|
||||||
String input = "spelTests";
|
String input = "spelTests";
|
||||||
|
|||||||
Reference in New Issue
Block a user