3838import org .springframework .data .couchbase .core .mapping .CouchbasePersistentProperty ;
3939import org .springframework .data .couchbase .core .mapping .Expiration ;
4040import org .springframework .data .couchbase .core .query .N1QLExpression ;
41+ import org .springframework .data .couchbase .core .query .StringQuery ;
4142import org .springframework .data .couchbase .repository .Query ;
4243import org .springframework .data .couchbase .repository .query .support .N1qlUtils ;
4344import 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 }
0 commit comments