Skip to content

Commit e9f2d78

Browse files
Esql additional date format testing (#120000)
This wires up the randomized testing for DateFormat. Prior to this PR, none of the randomized testing was hitting the one parameter version of the function, so I wired that up as well. This required some compromises on the type signatures, see comments in line.less --------- Co-authored-by: elasticsearchmachine <[email protected]>
1 parent 0ec55e5 commit e9f2d78

File tree

6 files changed

+78
-31
lines changed

6 files changed

+78
-31
lines changed

docs/reference/esql/functions/kibana/definition/date_format.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/types/date_format.asciidoc

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateFormat.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public class DateFormat extends EsqlConfigurationFunction implements OptionalArg
5555
)
5656
public DateFormat(
5757
Source source,
58-
@Param(optional = true, name = "dateFormat", type = { "keyword", "text" }, description = """
58+
@Param(optional = true, name = "dateFormat", type = { "keyword", "text", "date" }, description = """
5959
Date format (optional). If no format is specified, the `yyyy-MM-dd'T'HH:mm:ss.SSSZ` format is used.
6060
If `null`, the function returns `null`.""") Expression format,
6161
@Param(name = "date", type = { "date" }, description = "Date expression. If `null`, the function returns `null`.") Expression date,

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,17 @@ public static List<TypedDataSupplier> dateCases() {
10071007
return dateCases(Long.MIN_VALUE, Long.MAX_VALUE);
10081008
}
10091009

1010+
/**
1011+
* Generate cases for {@link DataType#DATETIME}.
1012+
* <p>
1013+
* For multi-row parameters, see {@link MultiRowTestCaseSupplier#dateCases}.
1014+
* </p>
1015+
* Helper function for if you want to specify your min and max range as dates instead of longs.
1016+
*/
1017+
public static List<TypedDataSupplier> dateCases(Instant min, Instant max) {
1018+
return dateCases(min.toEpochMilli(), max.toEpochMilli());
1019+
}
1020+
10101021
/**
10111022
* Generate cases for {@link DataType#DATETIME}.
10121023
* <p>
@@ -1045,6 +1056,19 @@ public static List<TypedDataSupplier> dateCases(long min, long max) {
10451056
return cases;
10461057
}
10471058

1059+
/**
1060+
*
1061+
* @return randomized valid date formats
1062+
*/
1063+
public static List<TypedDataSupplier> dateFormatCases() {
1064+
return List.of(
1065+
new TypedDataSupplier("<format as KEYWORD>", () -> new BytesRef(ESTestCase.randomDateFormatterPattern()), DataType.KEYWORD),
1066+
new TypedDataSupplier("<format as TEXT>", () -> new BytesRef(ESTestCase.randomDateFormatterPattern()), DataType.TEXT),
1067+
new TypedDataSupplier("<format as KEYWORD>", () -> new BytesRef("yyyy"), DataType.KEYWORD),
1068+
new TypedDataSupplier("<format as TEXT>", () -> new BytesRef("yyyy"), DataType.TEXT)
1069+
);
1070+
}
1071+
10481072
/**
10491073
* Generate cases for {@link DataType#DATE_NANOS}.
10501074
*

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateFormatErrorTests.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,22 @@ protected List<TestCaseSupplier> cases() {
2828

2929
@Override
3030
protected Expression build(Source source, List<Expression> args) {
31-
return new DateFormat(source, args.get(0), args.get(1), EsqlTestUtils.TEST_CFG);
31+
return new DateFormat(source, args.get(0), args.size() == 2 ? args.get(1) : null, EsqlTestUtils.TEST_CFG);
3232
}
3333

3434
@Override
3535
protected Matcher<String> expectedTypeErrorMatcher(List<Set<DataType>> validPerPosition, List<DataType> signature) {
36+
// Single argument version
37+
String source = sourceForSignature(signature);
38+
String name = signature.get(0).typeName();
39+
if (signature.size() == 1) {
40+
return equalTo("first argument of [" + source + "] must be [datetime], found value [] type [" + name + "]");
41+
}
42+
// Two argument version
43+
// Handle the weird case where we're calling the two argument version with the date first instead of the format.
44+
if (signature.get(0).isDate()) {
45+
return equalTo("first argument of [" + source + "] must be [string], found value [] type [" + name + "]");
46+
}
3647
return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> switch (p) {
3748
case 0 -> "string";
3849
case 1 -> "datetime";

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateFormatTests.java

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,21 @@
1111
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
1212

1313
import org.apache.lucene.util.BytesRef;
14-
import org.elasticsearch.common.lucene.BytesRefs;
14+
import org.elasticsearch.common.time.DateFormatter;
1515
import org.elasticsearch.xpack.esql.core.expression.Expression;
1616
import org.elasticsearch.xpack.esql.core.tree.Source;
1717
import org.elasticsearch.xpack.esql.core.type.DataType;
1818
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
1919
import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractConfigurationFunctionTestCase;
2020
import org.elasticsearch.xpack.esql.session.Configuration;
21+
import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
2122

23+
import java.time.Instant;
24+
import java.util.ArrayList;
2225
import java.util.List;
2326
import java.util.function.Supplier;
2427

25-
import static org.hamcrest.Matchers.equalTo;
28+
import static org.hamcrest.Matchers.matchesPattern;
2629

2730
public class DateFormatTests extends AbstractConfigurationFunctionTestCase {
2831
public DateFormatTests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> testCaseSupplier) {
@@ -31,39 +34,35 @@ public DateFormatTests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> tes
3134

3235
@ParametersFactory
3336
public static Iterable<Object[]> parameters() {
34-
return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(
35-
true,
36-
List.of(
37-
new TestCaseSupplier(
38-
List.of(DataType.KEYWORD, DataType.DATETIME),
39-
() -> new TestCaseSupplier.TestCase(
40-
List.of(
41-
new TestCaseSupplier.TypedData(new BytesRef("yyyy"), DataType.KEYWORD, "formatter"),
42-
new TestCaseSupplier.TypedData(1687944333000L, DataType.DATETIME, "val")
43-
),
44-
"DateFormatEvaluator[val=Attribute[channel=1], formatter=Attribute[channel=0], locale=en_US]",
45-
DataType.KEYWORD,
46-
equalTo(BytesRefs.toBytesRef("2023"))
47-
)
37+
List<TestCaseSupplier> suppliers = new ArrayList<>();
38+
// Formatter supplied cases
39+
suppliers.addAll(
40+
TestCaseSupplier.forBinaryNotCasting(
41+
(format, value) -> new BytesRef(
42+
DateFormatter.forPattern(((BytesRef) format).utf8ToString()).formatMillis(((Instant) value).toEpochMilli())
4843
),
49-
new TestCaseSupplier(
50-
List.of(DataType.TEXT, DataType.DATETIME),
51-
() -> new TestCaseSupplier.TestCase(
52-
List.of(
53-
new TestCaseSupplier.TypedData(new BytesRef("yyyy"), DataType.TEXT, "formatter"),
54-
new TestCaseSupplier.TypedData(1687944333000L, DataType.DATETIME, "val")
55-
),
56-
"DateFormatEvaluator[val=Attribute[channel=1], formatter=Attribute[channel=0], locale=en_US]",
57-
DataType.KEYWORD,
58-
equalTo(BytesRefs.toBytesRef("2023"))
59-
)
60-
)
44+
DataType.KEYWORD,
45+
TestCaseSupplier.dateFormatCases(),
46+
TestCaseSupplier.dateCases(Instant.parse("1900-01-01T00:00:00.00Z"), Instant.parse("9999-12-31T00:00:00.00Z")),
47+
matchesPattern("DateFormatEvaluator\\[val=Attribute\\[channel=1], formatter=Attribute\\[(channel=0|\\w+)], locale=en_US]"),
48+
(lhs, rhs) -> List.of(),
49+
false
6150
)
6251
);
52+
// Default formatter cases
53+
TestCaseSupplier.unary(
54+
suppliers,
55+
"DateFormatConstantEvaluator[val=Attribute[channel=0], formatter=format[strict_date_optional_time] locale[]]",
56+
TestCaseSupplier.dateCases(Instant.parse("1900-01-01T00:00:00.00Z"), Instant.parse("9999-12-31T00:00:00.00Z")),
57+
DataType.KEYWORD,
58+
(value) -> new BytesRef(EsqlDataTypeConverter.DEFAULT_DATE_TIME_FORMATTER.formatMillis(((Instant) value).toEpochMilli())),
59+
List.of()
60+
);
61+
return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(true, suppliers);
6362
}
6463

6564
@Override
6665
protected Expression buildWithConfiguration(Source source, List<Expression> args, Configuration configuration) {
67-
return new DateFormat(source, args.get(0), args.get(1), configuration);
66+
return new DateFormat(source, args.get(0), args.size() == 2 ? args.get(1) : null, configuration);
6867
}
6968
}

0 commit comments

Comments
 (0)