@@ -250,13 +250,11 @@ public void testValueForSearch() {
250250 assertEquals (date , ft .valueForDisplay (instant ));
251251 }
252252
253+ /**
254+ * If the term field is a string of date-time format with exact seconds (no sub-seconds), any data within a 1second range will match.
255+ */
253256 public void testTermQuery () {
254- Settings indexSettings = indexSettings (IndexVersion .current (), 1 , 1 ).build ();
255- SearchExecutionContext context = SearchExecutionContextHelper .createSimple (
256- new IndexSettings (IndexMetadata .builder ("foo" ).settings (indexSettings ).build (), indexSettings ),
257- parserConfig (),
258- writableRegistry ()
259- );
257+ SearchExecutionContext context = prepareIndexForTermQuery ();
260258 MappedFieldType ft = new DateFieldType ("field" );
261259 String date = "2015-10-12T14:10:55" ;
262260 long instant = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date )).toInstant ().toEpochMilli ();
@@ -270,45 +268,99 @@ public void testTermQuery() {
270268 expected = SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant , instant + 999 );
271269 assertEquals (expected , ft .termQuery (date , context ));
272270
273- MappedFieldType unsearchable = new DateFieldType (
274- "field" ,
275- false ,
276- false ,
277- false ,
278- DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER ,
279- Resolution .MILLISECONDS ,
280- null ,
281- null ,
282- Collections .emptyMap ()
271+ assertIndexUnsearchable (Resolution .MILLISECONDS , (unsearchable ) -> unsearchable .termQuery (date , context ));
272+ }
273+
274+ /**
275+ * If the term field is a string of date-time format with sub-seconds, only data with exact ms precision will match.
276+ */
277+ public void testTermQuerySubseconds () {
278+ SearchExecutionContext context = prepareIndexForTermQuery ();
279+ MappedFieldType ft = new DateFieldType ("field" );
280+ String date = "2015-10-12T14:10:55.01" ;
281+ long instant = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date )).toInstant ().toEpochMilli ();
282+ Query expected = new IndexOrDocValuesQuery (
283+ LongPoint .newRangeQuery ("field" , instant , instant ),
284+ SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant , instant )
283285 );
284- IllegalArgumentException e = expectThrows (IllegalArgumentException .class , () -> unsearchable .termQuery (date , context ));
285- assertEquals ("Cannot search on field [field] since it is not indexed nor has doc values." , e .getMessage ());
286+ assertEquals (expected , ft .termQuery (date , context ));
287+
288+ ft = new DateFieldType ("field" , false );
289+ expected = SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant , instant );
290+ assertEquals (expected , ft .termQuery (date , context ));
291+
292+ assertIndexUnsearchable (Resolution .MILLISECONDS , (unsearchable ) -> unsearchable .termQuery (date , context ));
286293 }
287294
288- public void testRangeQuery () throws IOException {
289- Settings indexSettings = indexSettings (IndexVersion .current (), 1 , 1 ).build ();
290- SearchExecutionContext context = new SearchExecutionContext (
291- 0 ,
292- 0 ,
293- new IndexSettings (IndexMetadata .builder ("foo" ).settings (indexSettings ).build (), indexSettings ),
294- null ,
295- null ,
296- null ,
297- MappingLookup .EMPTY ,
298- null ,
299- null ,
300- parserConfig (),
301- writableRegistry (),
302- null ,
303- null ,
304- () -> nowInMillis ,
305- null ,
306- null ,
307- () -> true ,
308- null ,
309- Collections .emptyMap (),
310- MapperMetrics .NOOP
295+ /**
296+ * If the term field is a string of the long value (ms since epoch), only data with exact ms precision will match.
297+ */
298+ public void testTermQueryMillis () {
299+ SearchExecutionContext context = prepareIndexForTermQuery ();
300+ MappedFieldType ft = new DateFieldType ("field" );
301+ String date = "2015-10-12T14:10:55" ;
302+ long instant = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date )).toInstant ().toEpochMilli ();
303+ Query expected = new IndexOrDocValuesQuery (
304+ LongPoint .newRangeQuery ("field" , instant , instant ),
305+ SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant , instant )
311306 );
307+ assertEquals (expected , ft .termQuery (instant , context ));
308+
309+ ft = new DateFieldType ("field" , false );
310+ expected = SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant , instant );
311+ assertEquals (expected , ft .termQuery (instant , context ));
312+
313+ assertIndexUnsearchable (Resolution .MILLISECONDS , (unsearchable ) -> unsearchable .termQuery (instant , context ));
314+ }
315+
316+ /**
317+ * This query has similar behaviour to passing a String containing a long to termQuery, only data with exact ms precision will match.
318+ */
319+ public void testEqualityQuery () {
320+ SearchExecutionContext context = prepareIndexForTermQuery ();
321+ DateFieldType ft = new DateFieldType ("field" );
322+ String date = "2015-10-12T14:10:55" ;
323+ long instant = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date )).toInstant ().toEpochMilli ();
324+ Query expected = new IndexOrDocValuesQuery (
325+ LongPoint .newRangeQuery ("field" , instant , instant ),
326+ SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant , instant )
327+ );
328+ assertEquals (expected , ft .equalityQuery (instant , context ));
329+
330+ ft = new DateFieldType ("field" , false );
331+ expected = SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant , instant );
332+ assertEquals (expected , ft .equalityQuery (instant , context ));
333+
334+ assertIndexUnsearchable (Resolution .MILLISECONDS , (unsearchable ) -> unsearchable .equalityQuery (instant , context ));
335+ }
336+
337+ /**
338+ * This query supports passing a ns value, and only data with exact ns precision will match.
339+ */
340+ public void testEqualityNanosQuery () {
341+ SearchExecutionContext context = prepareIndexForTermQuery ();
342+ DateFieldType ft = new DateFieldType ("field" , Resolution .NANOSECONDS );
343+ String date = "2015-10-12T14:10:55" ;
344+ long instant = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date )).toInstant ().toEpochMilli () * 1000000L ;
345+ Query expected = new IndexOrDocValuesQuery (
346+ LongPoint .newRangeQuery ("field" , instant , instant ),
347+ SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant , instant )
348+ );
349+ assertEquals (expected , ft .equalityQuery (instant , context ));
350+
351+ ft = new DateFieldType ("field" , false );
352+ expected = SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant , instant );
353+ assertEquals (expected , ft .equalityQuery (instant , context ));
354+
355+ assertIndexUnsearchable (Resolution .NANOSECONDS , (unsearchable ) -> unsearchable .equalityQuery (instant , context ));
356+ }
357+
358+ /**
359+ * If the term fields are strings of date-time format with exact seconds (no sub-seconds),
360+ * the second field will be rounded up to the next second.
361+ */
362+ public void testRangeQuery () throws IOException {
363+ SearchExecutionContext context = prepareIndexForRangeQuery ();
312364 MappedFieldType ft = new DateFieldType ("field" );
313365 String date1 = "2015-10-12T14:10:55" ;
314366 String date2 = "2016-04-28T11:33:52" ;
@@ -340,22 +392,105 @@ public void testRangeQuery() throws IOException {
340392 expected2 = new DateRangeIncludingNowQuery (SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant1 , instant2 ));
341393 assertEquals (expected2 , ft2 .rangeQuery ("now" , instant2 , true , true , null , null , null , context ));
342394
343- MappedFieldType unsearchable = new DateFieldType (
344- "field" ,
345- false ,
346- false ,
347- false ,
348- DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER ,
395+ assertIndexUnsearchable (
349396 Resolution .MILLISECONDS ,
350- null ,
351- null ,
352- Collections .emptyMap ()
397+ (unsearchable ) -> unsearchable .rangeQuery (date1 , date2 , true , true , null , null , null , context )
353398 );
354- IllegalArgumentException e = expectThrows (
355- IllegalArgumentException .class ,
356- () -> unsearchable .rangeQuery (date1 , date2 , true , true , null , null , null , context )
399+ }
400+
401+ /**
402+ * If the term fields are strings of date-time format with sub-seconds,
403+ * the lower and upper values will be matched inclusively to the ms.
404+ */
405+ public void testRangeQuerySubseconds () throws IOException {
406+ SearchExecutionContext context = prepareIndexForRangeQuery ();
407+ MappedFieldType ft = new DateFieldType ("field" );
408+ String date1 = "2015-10-12T14:10:55.01" ;
409+ String date2 = "2016-04-28T11:33:52.01" ;
410+ long instant1 = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date1 )).toInstant ().toEpochMilli ();
411+ long instant2 = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date2 )).toInstant ().toEpochMilli ();
412+ Query expected = new IndexOrDocValuesQuery (
413+ LongPoint .newRangeQuery ("field" , instant1 , instant2 ),
414+ SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant1 , instant2 )
415+ );
416+ assertEquals (expected , ft .rangeQuery (date1 , date2 , true , true , null , null , null , context ).rewrite (newSearcher (new MultiReader ())));
417+
418+ MappedFieldType ft2 = new DateFieldType ("field" , false );
419+ Query expected2 = SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant1 , instant2 );
420+ assertEquals (
421+ expected2 ,
422+ ft2 .rangeQuery (date1 , date2 , true , true , null , null , null , context ).rewrite (newSearcher (new MultiReader ()))
423+ );
424+
425+ instant1 = nowInMillis ;
426+ instant2 = instant1 + 100 ;
427+ expected = new DateRangeIncludingNowQuery (
428+ new IndexOrDocValuesQuery (
429+ LongPoint .newRangeQuery ("field" , instant1 , instant2 ),
430+ SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant1 , instant2 )
431+ )
432+ );
433+ assertEquals (expected , ft .rangeQuery ("now" , instant2 , true , true , null , null , null , context ));
434+
435+ expected2 = new DateRangeIncludingNowQuery (SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant1 , instant2 ));
436+ assertEquals (expected2 , ft2 .rangeQuery ("now" , instant2 , true , true , null , null , null , context ));
437+
438+ assertIndexUnsearchable (
439+ Resolution .MILLISECONDS ,
440+ (unsearchable ) -> unsearchable .rangeQuery (date1 , date2 , true , true , null , null , null , context )
357441 );
358- assertEquals ("Cannot search on field [field] since it is not indexed nor has doc values." , e .getMessage ());
442+ }
443+
444+ /**
445+ * If the term fields are strings of long ms, the lower and upper values will be matched inclusively to the ms.
446+ */
447+ public void testRangeQueryMillis () throws IOException {
448+ SearchExecutionContext context = prepareIndexForRangeQuery ();
449+ DateFieldType ft = new DateFieldType ("field" );
450+ String date1 = "2015-10-12T14:10:55.01" ;
451+ String date2 = "2016-04-28T11:33:52.01" ;
452+ long instant1 = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date1 )).toInstant ().toEpochMilli ();
453+ long instant2 = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date2 )).toInstant ().toEpochMilli ();
454+ Query expected = new IndexOrDocValuesQuery (
455+ LongPoint .newRangeQuery ("field" , instant1 , instant2 ),
456+ SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant1 , instant2 )
457+ );
458+ assertEquals (expected , ft .rangeQuery (instant1 , instant2 , true , true , context ).rewrite (newSearcher (new MultiReader ())));
459+
460+ DateFieldType ft2 = new DateFieldType ("field" , false );
461+ Query expected2 = SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant1 , instant2 );
462+ assertEquals (expected2 , ft2 .rangeQuery (instant1 , instant2 , true , true , context ).rewrite (newSearcher (new MultiReader ())));
463+
464+ assertIndexUnsearchable (
465+ Resolution .MILLISECONDS ,
466+ (unsearchable ) -> unsearchable .rangeQuery (instant1 , instant2 , true , true , context )
467+ );
468+ }
469+
470+ /**
471+ * If the term fields are strings of long ns, the lower and upper values will be matched inclusively to the ns.
472+ */
473+ public void testRangeQueryNanos () throws IOException {
474+ SearchExecutionContext context = prepareIndexForRangeQuery ();
475+ DateFieldType ft = new DateFieldType ("field" , Resolution .NANOSECONDS );
476+ String date1 = "2015-10-12T14:10:55.01" ;
477+ String date2 = "2016-04-28T11:33:52.01" ;
478+ long instant1 = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date1 )).toInstant ().toEpochMilli () * 1000000L ;
479+ long instant2 = DateFormatters .from (DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER .parse (date2 )).toInstant ().toEpochMilli () * 1000000L ;
480+ Query expected = new IndexOrDocValuesQuery (
481+ LongPoint .newRangeQuery ("field" , instant1 , instant2 ),
482+ SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant1 , instant2 )
483+ );
484+ assertEquals (expected , ft .rangeQuery (instant1 , instant2 , true , true , context ).rewrite (newSearcher (new MultiReader ())));
485+
486+ DateFieldType ft2 = new DateFieldType ("field" , false , Resolution .NANOSECONDS );
487+ Query expected2 = SortedNumericDocValuesField .newSlowRangeQuery ("field" , instant1 , instant2 );
488+ assertEquals (
489+ expected2 ,
490+ ft2 .rangeQuery (date1 , date2 , true , true , null , null , null , context ).rewrite (newSearcher (new MultiReader ()))
491+ );
492+
493+ assertIndexUnsearchable (Resolution .NANOSECONDS , (unsearchable ) -> unsearchable .rangeQuery (instant1 , instant2 , true , true , context ));
359494 }
360495
361496 public void testRangeQueryWithIndexSort () {
@@ -462,4 +597,55 @@ public void testParseSourceValueNanos() throws IOException {
462597 MappedFieldType nullValueMapper = fieldType (Resolution .NANOSECONDS , "strict_date_time||epoch_millis" , nullValueDate );
463598 assertEquals (List .of (nullValueDate ), fetchSourceValue (nullValueMapper , null ));
464599 }
600+
601+ private SearchExecutionContext prepareIndexForTermQuery () {
602+ Settings indexSettings = indexSettings (IndexVersion .current (), 1 , 1 ).build ();
603+ return SearchExecutionContextHelper .createSimple (
604+ new IndexSettings (IndexMetadata .builder ("foo" ).settings (indexSettings ).build (), indexSettings ),
605+ parserConfig (),
606+ writableRegistry ()
607+ );
608+ }
609+
610+ private SearchExecutionContext prepareIndexForRangeQuery () {
611+ Settings indexSettings = indexSettings (IndexVersion .current (), 1 , 1 ).build ();
612+ return new SearchExecutionContext (
613+ 0 ,
614+ 0 ,
615+ new IndexSettings (IndexMetadata .builder ("foo" ).settings (indexSettings ).build (), indexSettings ),
616+ null ,
617+ null ,
618+ null ,
619+ MappingLookup .EMPTY ,
620+ null ,
621+ null ,
622+ parserConfig (),
623+ writableRegistry (),
624+ null ,
625+ null ,
626+ () -> nowInMillis ,
627+ null ,
628+ null ,
629+ () -> true ,
630+ null ,
631+ Collections .emptyMap (),
632+ MapperMetrics .NOOP
633+ );
634+ }
635+
636+ private void assertIndexUnsearchable (Resolution resolution , ThrowingConsumer <DateFieldType > runnable ) {
637+ DateFieldType unsearchable = new DateFieldType (
638+ "field" ,
639+ false ,
640+ false ,
641+ false ,
642+ DateFieldMapper .DEFAULT_DATE_TIME_FORMATTER ,
643+ resolution ,
644+ null ,
645+ null ,
646+ Collections .emptyMap ()
647+ );
648+ IllegalArgumentException e = expectThrows (IllegalArgumentException .class , () -> runnable .accept (unsearchable ));
649+ assertEquals ("Cannot search on field [field] since it is not indexed nor has doc values." , e .getMessage ());
650+ }
465651}
0 commit comments