Skip to content

Commit 7de8ee7

Browse files
committed
Add unit tests for new DateFieldType methods
1 parent f478122 commit 7de8ee7

File tree

2 files changed

+251
-58
lines changed

2 files changed

+251
-58
lines changed

server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ public DateFieldType(String name) {
589589
);
590590
}
591591

592-
public DateFieldType(String name, boolean isIndexed) {
592+
public DateFieldType(String name, boolean isIndexed, Resolution resolution) {
593593
this(
594594
name,
595595
isIndexed,
@@ -599,13 +599,17 @@ public DateFieldType(String name, boolean isIndexed) {
599599
false,
600600
false,
601601
DEFAULT_DATE_TIME_FORMATTER,
602-
Resolution.MILLISECONDS,
602+
resolution,
603603
null,
604604
null,
605605
Collections.emptyMap()
606606
);
607607
}
608608

609+
public DateFieldType(String name, boolean isIndexed) {
610+
this(name, isIndexed, Resolution.MILLISECONDS);
611+
}
612+
609613
public DateFieldType(String name, DateFormatter dateFormatter) {
610614
this(name, true, true, false, true, false, false, dateFormatter, Resolution.MILLISECONDS, null, null, Collections.emptyMap());
611615
}
@@ -822,14 +826,17 @@ public static long parseToLong(
822826
}
823827

824828
/**
825-
* When the date value is already fully parsed and available as a long, use this method to skip parsing.
829+
* Similar to the {@link DateFieldType#termQuery} method, but works on dates that are already parsed to a long
830+
* in the same precision as the field mapper.
826831
*/
827832
public Query equalityQuery(Long value, @Nullable SearchExecutionContext context) {
828833
return rangeQuery(value, value, true, true, context);
829834
}
830835

831836
/**
832-
* When the date value is already fully parsed and available as a long, use this method to skip parsing.
837+
* Similar to the existing
838+
* {@link DateFieldType#rangeQuery(Object, Object, boolean, boolean, ShapeRelation, ZoneId, DateMathParser, SearchExecutionContext)}
839+
* method, but works on dates that are already parsed to a long in the same precision as the field mapper.
833840
*/
834841
public Query rangeQuery(
835842
Long lowerTerm,

server/src/test/java/org/elasticsearch/index/mapper/DateFieldTypeTests.java

Lines changed: 240 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)