20
20
import java .util .LinkedList ;
21
21
import java .util .List ;
22
22
23
+ import com .couchbase .client .core .error .InvalidArgumentException ;
24
+ import com .couchbase .client .java .json .JsonArray ;
25
+ import com .couchbase .client .java .json .JsonObject ;
26
+ import com .couchbase .client .java .json .JsonValue ;
27
+ import org .springframework .data .couchbase .core .convert .CouchbaseConverter ;
23
28
import org .springframework .lang .Nullable ;
24
29
25
30
/**
@@ -60,6 +65,10 @@ public class QueryCriteria implements QueryCriteriaDefinition {
60
65
this .format = format ;
61
66
}
62
67
68
+ Object [] getValue () {
69
+ return value ;
70
+ }
71
+
63
72
/**
64
73
* Static factory method to create a Criteria using the provided key.
65
74
*/
@@ -68,8 +77,8 @@ public static QueryCriteria where(String key) {
68
77
}
69
78
70
79
private static QueryCriteria wrap (QueryCriteria criteria ) {
71
- QueryCriteria qc = new QueryCriteria (new LinkedList <QueryCriteria >(), criteria .key , criteria .value , null ,
72
- criteria .operator , criteria . format );
80
+ QueryCriteria qc = new QueryCriteria (new LinkedList <>(), criteria .key , criteria .value , null , criteria . operator ,
81
+ criteria .format );
73
82
return qc ;
74
83
}
75
84
@@ -167,7 +176,7 @@ public QueryCriteria containing(@Nullable Object o) {
167
176
public QueryCriteria notContaining (@ Nullable Object o ) {
168
177
value = new QueryCriteria [] { wrap (containing (o )) };
169
178
operator = "NOT" ;
170
- format = format = "not( %3$s )" ;
179
+ format = "not( %3$s )" ;
171
180
return this ;
172
181
}
173
182
@@ -196,7 +205,7 @@ public QueryCriteria isNotNull() {
196
205
operator = "IS_NOT_NULL" ;
197
206
value = null ;
198
207
format = "%1$s is not null" ;
199
- return ( QueryCriteria ) this ;
208
+ return this ;
200
209
}
201
210
202
211
public QueryCriteria isMissing () {
@@ -210,7 +219,7 @@ public QueryCriteria isNotMissing() {
210
219
operator = "IS_NOT_MiSSING" ;
211
220
value = null ;
212
221
format = "%1$s is not missing" ;
213
- return ( QueryCriteria ) this ;
222
+ return this ;
214
223
}
215
224
216
225
public QueryCriteria isValued () {
@@ -224,68 +233,74 @@ public QueryCriteria isNotValued() {
224
233
operator = "IS_NOT_VALUED" ;
225
234
value = null ;
226
235
format = "%1$s is not valued" ;
227
- return ( QueryCriteria ) this ;
236
+ return this ;
228
237
}
229
238
230
239
public QueryCriteria within (@ Nullable Object o ) {
231
240
operator = "WITHIN" ;
232
241
value = new Object [] { o };
233
- format = "%1$s within $ 3$s" ;
234
- return ( QueryCriteria ) this ;
242
+ format = "%1$s within % 3$s" ;
243
+ return this ;
235
244
}
236
245
237
246
public QueryCriteria between (@ Nullable Object o1 , @ Nullable Object o2 ) {
238
247
operator = "BETWEEN" ;
239
248
value = new Object [] { o1 , o2 };
240
249
format = "%1$s between %3$s and %4$s" ;
241
- return ( QueryCriteria ) this ;
250
+ return this ;
242
251
}
243
252
244
253
public QueryCriteria in (@ Nullable Object ... o ) {
245
254
operator = "IN" ;
246
- value = o ;
247
- StringBuilder sb = new StringBuilder ("%1$s in ( [ " );
248
- for (int i = 1 ; i <= value .length ; i ++) { // format indices start at 1
249
- if (i > 1 )
250
- sb .append (", " );
251
- sb .append ("%" + (i + 2 ) + "$s" ); // the first is fieldName, second is operator, args start at 3
255
+ format = "%1$s in ( %3$s )" ;
256
+ // IN takes a single argument that is a list
257
+ if (o .length > 0 ) {
258
+ if (o [0 ] instanceof JsonArray || o [0 ] instanceof List || o [0 ] instanceof Object []) {
259
+ if (o .length != 1 ) {
260
+ throw new RuntimeException ("IN cannot take multiple lists" );
261
+ }
262
+ value = o ;
263
+ } else {
264
+ value = new Object [1 ];
265
+ value [0 ] = o ; // JsonArray.from(o);
266
+ }
252
267
}
253
- format = sb .append (" ] )" ).toString ();
254
- return (QueryCriteria ) this ;
268
+ return this ;
255
269
}
256
270
257
271
public QueryCriteria notIn (@ Nullable Object ... o ) {
258
272
value = new QueryCriteria [] { wrap (in (o )) };
259
273
operator = "NOT" ;
260
- format = format = "not( %3$s )" ; // field = 1$, operator = 2$, value=$3, $4, ...
261
- return ( QueryCriteria ) this ;
274
+ format = "not( %3$s )" ; // field = 1$, operator = 2$, value=$3, $4, ...
275
+ return this ;
262
276
}
263
277
264
278
public QueryCriteria TRUE () { // true/false are reserved, use TRUE/FALSE
265
279
value = null ;
266
280
operator = null ;
267
- format = format = "%1$s" ; // field = 1$, operator = 2$, value=$3, $4, ...
268
- return ( QueryCriteria ) this ;
281
+ format = "%1$s" ; // field = 1$, operator = 2$, value=$3, $4, ...
282
+ return this ;
269
283
}
270
284
271
285
public QueryCriteria FALSE () {
272
286
value = new QueryCriteria [] { wrap (TRUE ()) };
273
287
operator = "not" ;
274
- format = format = "not( %3$s )" ;
275
- return ( QueryCriteria ) this ;
288
+ format = "not( %3$s )" ;
289
+ return this ;
276
290
}
277
291
278
292
/**
279
- * This exports the query criteria into a string to be appended to the beginning of an N1QL statement
293
+ * This exports the query criteria chain into a string to be appended to the beginning of an N1QL statement
280
294
*
281
- * @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters
282
- * There may already be positional parameters in the beginning of the statement,
283
- * so it may not always start at 1. If it has the value -1, the query is using
284
- * named parameters. If the pointer is null, the query is not using parameters.
295
+ * @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters There may
296
+ * already be positional parameters in the beginning of the statement, so it may not always start at 1. If it
297
+ * has the value -1, the query is using named parameters. If the pointer is null, the query is not using
298
+ * parameters.
299
+ * @param parameters - parameters of the query. If operands are parameterized, their values are added to parameters
285
300
* @return string containing part of N1QL query
286
301
*/
287
302
@ Override
288
- public String export (int [] paramIndexPtr ) {
303
+ public String export (int [] paramIndexPtr , JsonValue parameters , CouchbaseConverter converter ) {
289
304
StringBuilder output = new StringBuilder ();
290
305
boolean first = true ;
291
306
for (QueryCriteria c : this .criteriaChain ) {
@@ -298,7 +313,7 @@ public String export(int[] paramIndexPtr) {
298
313
} else {
299
314
first = false ;
300
315
}
301
- c .exportSingle (output , paramIndexPtr );
316
+ c .exportSingle (output , paramIndexPtr , parameters , converter );
302
317
}
303
318
304
319
return output .toString ();
@@ -310,22 +325,34 @@ public String export(int[] paramIndexPtr) {
310
325
* @return string containing part of N1QL query
311
326
*/
312
327
@ Override
313
- public String export () {
314
- return export (null );
328
+ public String export () { // used only by tests
329
+ return export (null , null , null );
315
330
316
331
}
317
332
318
- private StringBuilder exportSingle (StringBuilder sb , int [] paramIndexPtr ) {
333
+ /**
334
+ * Appends the query criteria to a StringBuilder which will be appended to a N1QL statement
335
+ *
336
+ * @param sb - the string builder
337
+ * @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters There may
338
+ * already be positional parameters in the beginning of the statement, so it may not always start at 1. If it
339
+ * has the value -1, the query is using named parameters. If the pointer is null, the query is not using
340
+ * parameters.
341
+ * @param parameters - parameters of the query. If operands are parameterized, their values are added to parameters
342
+ * @return string containing part of N1QL query
343
+ */
344
+ private StringBuilder exportSingle (StringBuilder sb , int [] paramIndexPtr , JsonValue parameters ,
345
+ CouchbaseConverter converter ) {
319
346
String fieldName = maybeQuote (key );
320
347
int valueLen = value == null ? 0 : value .length ;
321
348
Object [] v = new Object [valueLen + 2 ];
322
349
v [0 ] = fieldName ;
323
350
v [1 ] = operator ;
324
351
for (int i = 0 ; i < valueLen ; i ++) {
325
352
if (value [i ] instanceof QueryCriteria ) {
326
- v [i + 2 ] = "(" + ((QueryCriteria ) value [i ]).export (paramIndexPtr ) + ")" ;
353
+ v [i + 2 ] = "(" + ((QueryCriteria ) value [i ]).export (paramIndexPtr , parameters , converter ) + ")" ;
327
354
} else {
328
- v [i + 2 ] = maybeWrapValue (key , value [i ], paramIndexPtr );
355
+ v [i + 2 ] = maybeWrapValue (key , value [i ], paramIndexPtr , parameters , converter );
329
356
}
330
357
}
331
358
@@ -340,24 +367,84 @@ private StringBuilder exportSingle(StringBuilder sb, int[] paramIndexPtr) {
340
367
return sb ;
341
368
}
342
369
343
- private String maybeWrapValue (String key , Object value , int [] paramIndexPtr ) {
370
+ /**
371
+ * Possibly convert an operand to a positional or named parameter
372
+ *
373
+ * @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters There may
374
+ * already be positional parameters in the beginning of the statement, so it may not always start at 1. If it
375
+ * has the value -1, the query is using named parameters. If the pointer is null, the query is not using
376
+ * parameters.
377
+ * @param parameters - parameters of the query. If operands are parameterized, their values are added to parameters
378
+ * @return string containing part of N1QL query
379
+ */
380
+ private String maybeWrapValue (String key , Object value , int [] paramIndexPtr , JsonValue parameters ,
381
+ CouchbaseConverter converter ) {
344
382
if (paramIndexPtr != null ) {
345
383
if (paramIndexPtr [0 ] >= 0 ) {
384
+ JsonArray params = (JsonArray ) parameters ;
385
+ // from StringBasedN1qlQueryParser.getPositionalPlaceholderValues()
386
+ try {
387
+ params .add (convert (converter , value ));
388
+ } catch (InvalidArgumentException iae ) {
389
+ if (value instanceof Object []) {
390
+ addAsArray (params , value , converter );
391
+ } else {
392
+ throw iae ;
393
+ }
394
+ }
346
395
return "$" + (++paramIndexPtr [0 ]); // these are generated in order
347
396
} else {
397
+ JsonObject params = (JsonObject ) parameters ;
398
+ // from StringBasedN1qlQueryParser.getNamedPlaceholderValues()
399
+ try {
400
+ params .put (key , convert (converter , value ));
401
+ } catch (InvalidArgumentException iae ) {
402
+ if (value instanceof Object []) {
403
+ params .put (key , JsonArray .from ((Object []) value ));
404
+ } else {
405
+ throw iae ;
406
+ }
407
+ }
348
408
return "$" + key ;
349
409
}
350
410
}
351
411
412
+ // Did not convert to a parameter. Add quotes or whatever it might need.
413
+
352
414
if (value instanceof String ) {
353
415
return "\" " + value + "\" " ;
354
416
} else if (value == null ) {
355
417
return "null" ;
418
+ } else if (value instanceof Object []) { // convert array into sequence of comma-separated values
419
+ StringBuffer l = new StringBuffer ();
420
+ l .append ("[" );
421
+ Object [] array = (Object []) value ;
422
+ for (int i = 0 ; i < array .length ; i ++) {
423
+ if (i > 0 ) {
424
+ l .append ("," );
425
+ }
426
+ l .append (maybeWrapValue (null , array [i ], null , null , converter ));
427
+ }
428
+ l .append ("]" );
429
+ return l .toString ();
356
430
} else {
357
431
return value .toString ();
358
432
}
359
433
}
360
434
435
+ private static Object convert (CouchbaseConverter converter , Object value ) {
436
+ return converter != null ? converter .convertForWriteIfNeeded (value ) : value ;
437
+ }
438
+
439
+ private void addAsArray (JsonArray posValues , Object o , CouchbaseConverter converter ) {
440
+ Object [] array = (Object []) o ;
441
+ JsonArray ja = JsonValue .ja ();
442
+ for (Object e : array ) {
443
+ ja .add (String .valueOf (convert (converter , e )));
444
+ }
445
+ posValues .add (ja );
446
+ }
447
+
361
448
private String maybeQuote (String value ) {
362
449
if (value == null || (value .startsWith ("\" " ) && value .endsWith ("\" " ))) {
363
450
return value ;
0 commit comments