24
24
import org .apache .lucene .search .spans .SpanQuery ;
25
25
import org .elasticsearch .common .ParseField ;
26
26
import org .elasticsearch .common .ParsingException ;
27
+ import org .elasticsearch .common .Strings ;
27
28
import org .elasticsearch .common .io .stream .StreamInput ;
28
29
import org .elasticsearch .common .io .stream .StreamOutput ;
29
30
import org .elasticsearch .common .xcontent .XContentBuilder ;
31
+ import org .elasticsearch .common .xcontent .XContentLocation ;
30
32
import org .elasticsearch .common .xcontent .XContentParser ;
31
33
32
34
import java .io .IOException ;
@@ -203,18 +205,54 @@ public static SpanNearQueryBuilder fromXContent(XContentParser parser) throws IO
203
205
204
206
@ Override
205
207
protected Query doToQuery (QueryShardContext context ) throws IOException {
206
- if (clauses .size () == 1 ) {
207
- Query query = clauses .get (0 ).toQuery (context );
208
+ SpanQueryBuilder queryBuilder = clauses .get (0 );
209
+ boolean isGap = queryBuilder instanceof SpanGapQueryBuilder ;
210
+ Query query = null ;
211
+ if (!isGap ) {
212
+ query = queryBuilder .toQuery (context );
208
213
assert query instanceof SpanQuery ;
214
+ }
215
+ if (clauses .size () == 1 ) {
216
+ assert !isGap ;
209
217
return query ;
210
218
}
211
- SpanQuery [] spanQueries = new SpanQuery [ clauses . size ()] ;
212
- for ( int i = 0 ; i < clauses . size (); i ++ ) {
213
- Query query = clauses . get ( i ). toQuery ( context );
214
- assert query instanceof SpanQuery ;
215
- spanQueries [ i ] = (SpanQuery ) query ;
219
+ String spanNearFieldName = null ;
220
+ if ( isGap ) {
221
+ spanNearFieldName = (( SpanGapQueryBuilder ) queryBuilder ). fieldName ( );
222
+ } else {
223
+ spanNearFieldName = (( SpanQuery ) query ). getField () ;
216
224
}
217
- return new SpanNearQuery (spanQueries , slop , inOrder );
225
+
226
+ SpanNearQuery .Builder builder = new SpanNearQuery .Builder (spanNearFieldName , inOrder );
227
+ builder .setSlop (slop );
228
+ /*
229
+ * Lucene SpanNearQuery throws exceptions for certain use cases like adding gap to a
230
+ * unordered SpanNearQuery. Should ES have the same checks or wrap those thrown exceptions?
231
+ */
232
+ if (isGap ) {
233
+ int gap = ((SpanGapQueryBuilder ) queryBuilder ).width ();
234
+ builder .addGap (gap );
235
+ } else {
236
+ builder .addClause ((SpanQuery ) query );
237
+ }
238
+
239
+ for (int i = 1 ; i < clauses .size (); i ++) {
240
+ queryBuilder = clauses .get (i );
241
+ isGap = queryBuilder instanceof SpanGapQueryBuilder ;
242
+ if (isGap ) {
243
+ String fieldName = ((SpanGapQueryBuilder ) queryBuilder ).fieldName ();
244
+ if (!spanNearFieldName .equals (fieldName )) {
245
+ throw new IllegalArgumentException ("[span_near] clauses must have same field" );
246
+ }
247
+ int gap = ((SpanGapQueryBuilder ) queryBuilder ).width ();
248
+ builder .addGap (gap );
249
+ } else {
250
+ query = clauses .get (i ).toQuery (context );
251
+ assert query instanceof SpanQuery ;
252
+ builder .addClause ((SpanQuery )query );
253
+ }
254
+ }
255
+ return builder .build ();
218
256
}
219
257
220
258
@ Override
@@ -233,4 +271,168 @@ protected boolean doEquals(SpanNearQueryBuilder other) {
233
271
public String getWriteableName () {
234
272
return NAME ;
235
273
}
274
+
275
+ /**
276
+ * SpanGapQueryBuilder enables gaps in a SpanNearQuery.
277
+ * Since, SpanGapQuery is private to SpanNearQuery, SpanGapQueryBuilder cannot
278
+ * be used to generate a Query (SpanGapQuery) like another QueryBuilder.
279
+ * Instead, it just identifies a span_gap clause so that SpanNearQuery.addGap(int)
280
+ * can be invoked for it.
281
+ * This QueryBuilder is only applicable as a clause in SpanGapQueryBuilder but
282
+ * yet to enforce this restriction.
283
+ */
284
+ public static class SpanGapQueryBuilder implements SpanQueryBuilder {
285
+ public static final String NAME = "span_gap" ;
286
+
287
+ /** Name of field to match against. */
288
+ private final String fieldName ;
289
+
290
+ /** Width of the gap introduced. */
291
+ private final int width ;
292
+
293
+ /**
294
+ * Constructs a new SpanGapQueryBuilder term query.
295
+ *
296
+ * @param fieldName The name of the field
297
+ * @param width The width of the gap introduced
298
+ */
299
+ public SpanGapQueryBuilder (String fieldName , int width ) {
300
+ if (Strings .isEmpty (fieldName )) {
301
+ throw new IllegalArgumentException ("[span_gap] field name is null or empty" );
302
+ }
303
+ //lucene has not coded any restriction on value of width.
304
+ //to-do : find if theoretically it makes sense to apply restrictions.
305
+ this .fieldName = fieldName ;
306
+ this .width = width ;
307
+ }
308
+
309
+ /**
310
+ * Read from a stream.
311
+ */
312
+ public SpanGapQueryBuilder (StreamInput in ) throws IOException {
313
+ fieldName = in .readString ();
314
+ width = in .readInt ();
315
+ }
316
+
317
+ /**
318
+ * @return fieldName The name of the field
319
+ */
320
+ public String fieldName () {
321
+ return fieldName ;
322
+ }
323
+
324
+ /**
325
+ * @return width The width of the gap introduced
326
+ */
327
+ public int width () {
328
+ return width ;
329
+ }
330
+
331
+ @ Override
332
+ public Query toQuery (QueryShardContext context ) throws IOException {
333
+ throw new UnsupportedOperationException ();
334
+ }
335
+
336
+ @ Override
337
+ public Query toFilter (QueryShardContext context ) throws IOException {
338
+ throw new UnsupportedOperationException ();
339
+ }
340
+
341
+ @ Override
342
+ public String queryName () {
343
+ throw new UnsupportedOperationException ();
344
+ }
345
+
346
+ @ Override
347
+ public QueryBuilder queryName (String queryName ) {
348
+ throw new UnsupportedOperationException ();
349
+ }
350
+
351
+ @ Override
352
+ public float boost () {
353
+ throw new UnsupportedOperationException ();
354
+ }
355
+
356
+ @ Override
357
+ public QueryBuilder boost (float boost ) {
358
+ throw new UnsupportedOperationException ();
359
+ }
360
+
361
+ @ Override
362
+ public String getName () {
363
+ return NAME ;
364
+ }
365
+
366
+ @ Override
367
+ public String getWriteableName () {
368
+ return NAME ;
369
+ }
370
+
371
+ @ Override
372
+ public final void writeTo (StreamOutput out ) throws IOException {
373
+ out .writeString (fieldName );
374
+ out .writeInt (width );
375
+ }
376
+
377
+ @ Override
378
+ public XContentBuilder toXContent (XContentBuilder builder , Params params ) throws IOException {
379
+ builder .startObject ();
380
+ builder .startObject (getName ());
381
+ builder .field (fieldName , width );
382
+ builder .endObject ();
383
+ builder .endObject ();
384
+ return builder ;
385
+ }
386
+
387
+ public static SpanGapQueryBuilder fromXContent (XContentParser parser ) throws IOException {
388
+ String fieldName = null ;
389
+ int width = 0 ;
390
+ String currentFieldName = null ;
391
+ XContentParser .Token token ;
392
+ while ((token = parser .nextToken ()) != XContentParser .Token .END_OBJECT ) {
393
+ if (token == XContentParser .Token .FIELD_NAME ) {
394
+ currentFieldName = parser .currentName ();
395
+ throwParsingExceptionOnMultipleFields (NAME , parser .getTokenLocation (), fieldName , currentFieldName );
396
+ fieldName = currentFieldName ;
397
+ } else if (token .isValue ()) {
398
+ width = parser .intValue ();
399
+ }
400
+ }
401
+ SpanGapQueryBuilder result = new SpanGapQueryBuilder (fieldName , width );
402
+ return result ;
403
+ }
404
+
405
+ @ Override
406
+ public final boolean equals (Object obj ) {
407
+ if (this == obj ) {
408
+ return true ;
409
+ }
410
+ if (obj == null || getClass () != obj .getClass ()) {
411
+ return false ;
412
+ }
413
+ SpanGapQueryBuilder other = (SpanGapQueryBuilder ) obj ;
414
+ return Objects .equals (fieldName , other .fieldName ) &&
415
+ Objects .equals (width , other .width );
416
+ }
417
+
418
+ @ Override
419
+ public final int hashCode () {
420
+ return Objects .hash (getClass (), fieldName , width );
421
+ }
422
+
423
+
424
+ @ Override
425
+ public final String toString () {
426
+ return Strings .toString (this , true , true );
427
+ }
428
+
429
+ //copied from AbstractQueryBuilder
430
+ protected static void throwParsingExceptionOnMultipleFields (String queryName , XContentLocation contentLocation ,
431
+ String processedFieldName , String currentFieldName ) {
432
+ if (processedFieldName != null ) {
433
+ throw new ParsingException (contentLocation , "[" + queryName + "] query doesn't support multiple fields, found ["
434
+ + processedFieldName + "] and [" + currentFieldName + "]" );
435
+ }
436
+ }
437
+ }
236
438
}
0 commit comments