Skip to content

Commit e72cdba

Browse files
committed
DATACOUCH-616 - Fix parsing of String Query containing conditionals in quotes
For a query as below that has conditional portions, the parsing for parameters was being done before the conditional portions were being resolved, which left the parameters between quotes where they wre not being recognized as query parameters. @query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} " + " #{#projectIds != null ? 'AND iata IN $1' : ''} ") Long count(@param("projectIds") List<String> projectIds)
1 parent 82edad5 commit e72cdba

File tree

8 files changed

+66
-248
lines changed

8 files changed

+66
-248
lines changed

src/main/java/org/springframework/data/couchbase/repository/Query.java

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
import org.springframework.data.annotation.QueryAnnotation;
2525
import org.springframework.data.couchbase.core.CouchbaseTemplate;
26-
import org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery;
2726

2827
/**
2928
* Annotation to support the use of N1QL queries with Couchbase.

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

-81
This file was deleted.

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

+49-7
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,21 @@
3232
import org.springframework.data.couchbase.core.query.N1QLExpression;
3333
import org.springframework.data.couchbase.repository.Query;
3434
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
35+
import org.springframework.data.domain.Pageable;
36+
import org.springframework.data.domain.Sort;
3537
import org.springframework.data.repository.query.Parameter;
3638
import org.springframework.data.repository.query.ParameterAccessor;
3739
import org.springframework.data.repository.query.QueryMethod;
40+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
41+
import org.springframework.data.repository.query.ReturnedType;
3842
import org.springframework.expression.EvaluationContext;
3943
import org.springframework.expression.common.TemplateParserContext;
4044
import org.springframework.expression.spel.standard.SpelExpressionParser;
4145

4246
import com.couchbase.client.java.json.JsonArray;
4347
import com.couchbase.client.java.json.JsonObject;
4448
import com.couchbase.client.java.json.JsonValue;
49+
import org.springframework.util.Assert;
4550

4651
/**
4752
* @author Subhashni Balakrishnan
@@ -103,20 +108,23 @@ public class StringBasedN1qlQueryParser {
103108
private static final Logger LOGGER = LoggerFactory.getLogger(StringBasedN1qlQueryParser.class);
104109
private final String statement;
105110
private final QueryMethod queryMethod;
106-
private final PlaceholderType placeHolderType;
111+
private PlaceholderType placeHolderType;
107112
private final N1qlSpelValues statementContext;
108113
private final N1qlSpelValues countContext;
109114
private final CouchbaseConverter couchbaseConverter;
110115
private final Collection<String> parameterNames = new HashSet<String>();
116+
public final N1QLExpression parsedExpression;
111117

112118
public StringBasedN1qlQueryParser(String statement, QueryMethod queryMethod, String bucketName,
113-
CouchbaseConverter couchbaseConverter, String typeField, String typeValue) {
119+
CouchbaseConverter couchbaseConverter, String typeField, String typeValue, ParameterAccessor accessor,
120+
SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
114121
this.statement = statement;
115122
this.queryMethod = queryMethod;
116-
this.placeHolderType = checkPlaceholders(statement);
117123
this.statementContext = createN1qlSpelValues(bucketName, typeField, typeValue, false);
118124
this.countContext = createN1qlSpelValues(bucketName, typeField, typeValue, true);
119125
this.couchbaseConverter = couchbaseConverter;
126+
this.parsedExpression = getExpression(accessor, getParameters(accessor), null, parser, evaluationContextProvider);
127+
checkPlaceholders( this.parsedExpression.toString() );
120128
}
121129

122130
public static N1qlSpelValues createN1qlSpelValues(String bucketName, String typeField, String typeValue,
@@ -151,7 +159,7 @@ public String doParse(SpelExpressionParser parser, EvaluationContext evaluationC
151159
return parsedExpression.getValue(evaluationContext, String.class);
152160
}
153161

154-
private PlaceholderType checkPlaceholders(String statement) {
162+
private void checkPlaceholders(String statement) {
155163

156164
Matcher quoteMatcher = QUOTE_DETECTION_PATTERN.matcher(statement);
157165
Matcher positionMatcher = POSITIONAL_PLACEHOLDER_PATTERN.matcher(statement);
@@ -192,11 +200,11 @@ private PlaceholderType checkPlaceholders(String statement) {
192200
}
193201

194202
if (posCount > 0) {
195-
return PlaceholderType.POSITIONAL;
203+
placeHolderType = PlaceholderType.POSITIONAL;
196204
} else if (namedCount > 0) {
197-
return PlaceholderType.NAMED;
205+
placeHolderType = PlaceholderType.NAMED;
198206
} else {
199-
return PlaceholderType.NONE;
207+
placeHolderType = PlaceholderType.NONE;
200208
}
201209
}
202210

@@ -402,5 +410,39 @@ public N1qlSpelValues(String selectClause, String entityFields, String bucket, S
402410
this.returning = returning;
403411
}
404412
}
413+
// copied from StringN1qlBasedQuery
414+
private N1QLExpression getExpression(ParameterAccessor accessor, Object[] runtimeParameters,
415+
ReturnedType returnedType, SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
416+
boolean isCountQuery = queryMethod.getName().toLowerCase().startsWith("count"); // should be queryMethod.isCountQuery()
417+
EvaluationContext evaluationContext = evaluationContextProvider
418+
.getEvaluationContext(queryMethod.getParameters(), runtimeParameters);
419+
N1QLExpression parsedStatement = x(this.doParse(parser, evaluationContext, isCountQuery));
420+
421+
Sort sort = accessor.getSort();
422+
if (sort.isSorted()) {
423+
N1QLExpression[] cbSorts = N1qlUtils.createSort(sort);
424+
parsedStatement = parsedStatement.orderBy(cbSorts);
425+
}
426+
if (queryMethod.isPageQuery()) {
427+
Pageable pageable = accessor.getPageable();
428+
Assert.notNull(pageable, "Pageable must not be null!");
429+
parsedStatement = parsedStatement.limit(pageable.getPageSize()).offset(
430+
Math.toIntExact(pageable.getOffset()));
431+
} else if (queryMethod.isSliceQuery()) {
432+
Pageable pageable = accessor.getPageable();
433+
Assert.notNull(pageable, "Pageable must not be null!");
434+
parsedStatement = parsedStatement.limit(pageable.getPageSize() + 1).offset(
435+
Math.toIntExact(pageable.getOffset()));
436+
}
437+
return parsedStatement;
438+
}
405439

440+
// getExpression() could do this itself, but pass as an arg to be consistent with StringN1qlBasedQuery
441+
private static Object[] getParameters(ParameterAccessor accessor) {
442+
ArrayList<Object> params = new ArrayList<>();
443+
for (Object o : accessor) {
444+
params.add(o);
445+
}
446+
return params.toArray();
447+
}
406448
}

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

-117
This file was deleted.

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

+3-38
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public class StringN1qlQueryCreator extends AbstractQueryCreator<Query, QueryCri
5757
private final QueryMethod queryMethod;
5858
private final CouchbaseConverter couchbaseConverter;
5959
private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser();
60+
private final N1QLExpression parsedExpression;
6061

6162
public StringN1qlQueryCreator(final ParameterAccessor accessor, CouchbaseQueryMethod queryMethod,
6263
CouchbaseConverter couchbaseConverter, String bucketName,
@@ -83,8 +84,9 @@ public StringN1qlQueryCreator(final ParameterAccessor accessor, CouchbaseQueryMe
8384
throw new IllegalArgumentException("query has no inline Query or named Query not found");
8485
}
8586
this.queryParser = new StringBasedN1qlQueryParser(queryString, queryMethod, bucketName, couchbaseConverter,
86-
getTypeField(), getTypeValue());
87+
getTypeField(), getTypeValue(), accessor, SPEL_PARSER, evaluationContextProvider);
8788
this.parser = SPEL_PARSER;
89+
this.parsedExpression = this.queryParser.parsedExpression;
8890
}
8991

9092
protected QueryMethod getQueryMethod() {
@@ -127,7 +129,6 @@ protected QueryCriteria or(QueryCriteria base, QueryCriteria criteria) {
127129

128130
@Override
129131
protected Query complete(QueryCriteria criteria, Sort sort) {
130-
N1QLExpression parsedExpression = getExpression(accessor, getParameters(accessor), null /* returnedType */);
131132
Query q = new StringQuery(parsedExpression.toString()).with(sort);
132133
JsonValue params = queryParser.getPlaceholderValues(accessor);
133134
if (params instanceof JsonArray) {
@@ -150,40 +151,4 @@ private QueryCriteria from(final Part part, final CouchbasePersistentProperty pr
150151
}
151152
}
152153

153-
// copied from StringN1qlBasedQuery
154-
private N1QLExpression getExpression(ParameterAccessor accessor, Object[] runtimeParameters,
155-
ReturnedType returnedType) {
156-
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(
157-
getQueryMethod().getParameters(), runtimeParameters);
158-
N1QLExpression parsedStatement = x(this.queryParser.doParse(parser, evaluationContext, false));
159-
160-
Sort sort = accessor.getSort();
161-
if (sort.isSorted()) {
162-
N1QLExpression[] cbSorts = N1qlUtils.createSort(sort);
163-
parsedStatement = parsedStatement.orderBy(cbSorts);
164-
}
165-
if (queryMethod.isPageQuery()) {
166-
Pageable pageable = accessor.getPageable();
167-
Assert.notNull(pageable, "Pageable must not be null!");
168-
parsedStatement = parsedStatement.limit(pageable.getPageSize()).offset(
169-
Math.toIntExact(pageable.getOffset()));
170-
} else if (queryMethod.isSliceQuery()) {
171-
Pageable pageable = accessor.getPageable();
172-
Assert.notNull(pageable, "Pageable must not be null!");
173-
parsedStatement = parsedStatement.limit(pageable.getPageSize() + 1).offset(
174-
Math.toIntExact(pageable.getOffset()));
175-
}
176-
return parsedStatement;
177-
}
178-
179-
// getExpression() could do this itself, but pass as an arg to be consistent with StringN1qlBasedQuery
180-
private static Object[] getParameters(ParameterAccessor accessor) {
181-
ArrayList<Object> params = new ArrayList<>();
182-
for (Object o : accessor) {
183-
params.add(o);
184-
}
185-
return params.toArray();
186-
}
187154
}
188-
189-

0 commit comments

Comments
 (0)