Skip to content

Commit

Permalink
ES|QL: Add unit tests and docs for DATE_TRUNC() (#107145)
Browse files Browse the repository at this point in the history
  • Loading branch information
luigidellaquila committed Apr 9, 2024
1 parent d99323e commit 2588c72
Show file tree
Hide file tree
Showing 13 changed files with 187 additions and 99 deletions.
2 changes: 1 addition & 1 deletion docs/reference/esql/functions/date-time-functions.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ include::date_diff.asciidoc[]
include::date_extract.asciidoc[]
include::date_format.asciidoc[]
include::date_parse.asciidoc[]
include::date_trunc.asciidoc[]
include::layout/date_trunc.asciidoc[]
include::now.asciidoc[]
5 changes: 5 additions & 0 deletions docs/reference/esql/functions/description/date_trunc.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.

*Description*

Rounds down a date to the closest interval.
Original file line number Diff line number Diff line change
@@ -1,26 +1,4 @@
[discrete]
[[esql-date_trunc]]
=== `DATE_TRUNC`

*Syntax*

[source,esql]
----
DATE_TRUNC(interval, date)
----

*Parameters*

`interval`::
Interval, expressed using the <<esql-timespan-literals,timespan literal
syntax>>. If `null`, the function returns `null`.

`date`::
Date expression. If `null`, the function returns `null`.

*Description*

Rounds down a date to the closest interval.
// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.

*Examples*

Expand All @@ -32,10 +10,8 @@ include::{esql-specs}/date.csv-spec[tag=docsDateTrunc]
|===
include::{esql-specs}/date.csv-spec[tag=docsDateTrunc-result]
|===

Combine `DATE_TRUNC` with <<esql-stats-by>> to create date histograms. For
example, the number of hires per year:

[source.merge.styled,esql]
----
include::{esql-specs}/date.csv-spec[tag=docsDateTruncHistogram]
Expand All @@ -44,9 +20,7 @@ include::{esql-specs}/date.csv-spec[tag=docsDateTruncHistogram]
|===
include::{esql-specs}/date.csv-spec[tag=docsDateTruncHistogram-result]
|===

Or an hourly error rate:

[source.merge.styled,esql]
----
include::{esql-specs}/conditional.csv-spec[tag=docsCaseHourlyErrorRate]
Expand All @@ -55,3 +29,4 @@ include::{esql-specs}/conditional.csv-spec[tag=docsCaseHourlyErrorRate]
|===
include::{esql-specs}/conditional.csv-spec[tag=docsCaseHourlyErrorRate-result]
|===

15 changes: 15 additions & 0 deletions docs/reference/esql/functions/layout/date_trunc.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.

[discrete]
[[esql-date_trunc]]
=== `DATE_TRUNC`

*Syntax*

[.text-center]
image::esql/functions/signature/date_trunc.svg[Embedded,opts=inline]

include::../parameters/date_trunc.asciidoc[]
include::../description/date_trunc.asciidoc[]
include::../types/date_trunc.asciidoc[]
include::../examples/date_trunc.asciidoc[]
9 changes: 9 additions & 0 deletions docs/reference/esql/functions/parameters/date_trunc.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.

*Parameters*

`interval`::
Interval; expressed using the timespan literal syntax.

`date`::
Date expression
1 change: 1 addition & 0 deletions docs/reference/esql/functions/signature/date_trunc.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions docs/reference/esql/functions/types/date_trunc.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.

*Supported types*

[%header.monospaced.styled,format=dsv,separator=|]
|===
interval | date | result
date_period | datetime | datetime
time_duration | datetime | datetime
|===
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ synopsis:keyword
"long date_extract(datePart:keyword|text, date:date)"
"keyword date_format(?dateFormat:keyword|text, date:date)"
"date date_parse(?datePattern:keyword|text, dateString:keyword|text)"
"date date_trunc(interval:keyword, date:date)"
"date date_trunc(interval:date_period|time_duration, date:date)"
double e()
"boolean ends_with(str:keyword|text, suffix:keyword|text)"
"double|integer|long|unsigned_long floor(number:double|integer|long|unsigned_long)"
Expand Down Expand Up @@ -132,7 +132,7 @@ date_diff |[unit, startTimestamp, endTimestamp]|["keyword|text", date, date]
date_extract |[datePart, date] |["keyword|text", date] |[Part of the date to extract. Can be: aligned_day_of_week_in_month; aligned_day_of_week_in_year; aligned_week_of_month; aligned_week_of_year; ampm_of_day; clock_hour_of_ampm; clock_hour_of_day; day_of_month; day_of_week; day_of_year; epoch_day; era; hour_of_ampm; hour_of_day; instant_seconds; micro_of_day; micro_of_second; milli_of_day; milli_of_second; minute_of_day; minute_of_hour; month_of_year; nano_of_day; nano_of_second; offset_seconds; proleptic_month; second_of_day; second_of_minute; year; or year_of_era., Date expression]
date_format |[dateFormat, date] |["keyword|text", date] |[A valid date pattern, Date expression]
date_parse |[datePattern, dateString] |["keyword|text", "keyword|text"] |[A valid date pattern, A string representing a date]
date_trunc |[interval, date] |[keyword, date] |[Interval; expressed using the timespan literal syntax., Date expression]
date_trunc |[interval, date] |["date_period|time_duration", date] |[Interval; expressed using the timespan literal syntax., Date expression]
e |null |null |null
ends_with |[str, suffix] |["keyword|text", "keyword|text"] |[, ]
floor |number |"double|integer|long|unsigned_long" |[""]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
public @interface Example {

/**
* The description that will appear before the example
*/
String description() default "";

/**
* The test file that contains the example.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
import org.elasticsearch.xpack.esql.expression.function.Example;
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
Expand All @@ -34,14 +35,26 @@

public class DateTrunc extends BinaryDateTimeFunction implements EvaluatorMapper {

@FunctionInfo(returnType = "date", description = "Rounds down a date to the closest interval.")
@FunctionInfo(
returnType = "date",
description = "Rounds down a date to the closest interval.",
examples = {
@Example(file = "date", tag = "docsDateTrunc"),
@Example(
description = "Combine `DATE_TRUNC` with <<esql-stats-by>> to create date histograms. For\n"
+ "example, the number of hires per year:",
file = "date",
tag = "docsDateTruncHistogram"
),
@Example(description = "Or an hourly error rate:", file = "conditional", tag = "docsCaseHourlyErrorRate") }
)
public DateTrunc(
Source source,
// Need to replace the commas in the description here with semi-colon as there's a bug in the CSV parser
// used in the CSVTests and fixing it is not trivial
@Param(
name = "interval",
type = { "keyword" },
type = { "date_period", "time_duration" },
description = "Interval; expressed using the timespan literal syntax."
) Expression interval,
@Param(name = "date", type = { "date" }, description = "Date expression") Expression field
Expand All @@ -55,8 +68,8 @@ protected TypeResolution resolveType() {
return new TypeResolution("Unresolved children");
}

return isDate(timestampField(), sourceText(), FIRST).and(
isType(interval(), EsqlDataTypes::isTemporalAmount, sourceText(), SECOND, "dateperiod", "timeduration")
return isType(interval(), EsqlDataTypes::isTemporalAmount, sourceText(), FIRST, "dateperiod", "timeduration").and(
isDate(timestampField(), sourceText(), SECOND)
);
}

Expand Down Expand Up @@ -105,7 +118,7 @@ private static Rounding.Prepared createRounding(final Period period, final ZoneI

long periods = period.getUnits().stream().filter(unit -> period.get(unit) != 0).count();
if (periods != 1) {
throw new IllegalArgumentException("Time interval is not supported");
throw new IllegalArgumentException("Time interval with multiple periods is not supported");
}

final Rounding.Builder rounding;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1118,36 +1118,36 @@ public void testDateParseOnIntPattern() {
public void testDateTruncOnInt() {
verifyUnsupported("""
from test
| eval date_trunc("1M", int)
""", "first argument of [date_trunc(\"1M\", int)] must be [datetime], found value [int] type [integer]");
| eval date_trunc(1 month, int)
""", "second argument of [date_trunc(1 month, int)] must be [datetime], found value [int] type [integer]");
}

public void testDateTruncOnFloat() {
verifyUnsupported("""
from test
| eval date_trunc("1M", float)
""", "first argument of [date_trunc(\"1M\", float)] must be [datetime], found value [float] type [double]");
| eval date_trunc(1 month, float)
""", "second argument of [date_trunc(1 month, float)] must be [datetime], found value [float] type [double]");
}

public void testDateTruncOnText() {
verifyUnsupported("""
from test
| eval date_trunc("1M", keyword)
""", "first argument of [date_trunc(\"1M\", keyword)] must be [datetime], found value [keyword] type [keyword]");
| eval date_trunc(1 month, keyword)
""", "second argument of [date_trunc(1 month, keyword)] must be [datetime], found value [keyword] type [keyword]");
}

public void testDateTruncWithNumericInterval() {
verifyUnsupported("""
from test
| eval date_trunc(1, date)
""", "second argument of [date_trunc(1, date)] must be [dateperiod or timeduration], found value [1] type [integer]");
""", "first argument of [date_trunc(1, date)] must be [dateperiod or timeduration], found value [1] type [integer]");
}

public void testDateTruncWithDateInterval() {
verifyUnsupported("""
from test
| eval date_trunc(date, date)
""", "second argument of [date_trunc(date, date)] must be [dateperiod or timeduration], found value [date] type [datetime]");
""", "first argument of [date_trunc(date, date)] must be [dateperiod or timeduration], found value [date] type [datetime]");
}

// check field declaration is validated even across duplicated declarations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ public static void testFunctionInfo() {
for (Map.Entry<List<DataType>, DataType> entry : signatures.entrySet()) {
List<DataType> types = entry.getKey();
for (int i = 0; i < args.size() && i < types.size(); i++) {
typesFromSignature.get(i).add(types.get(i).esType());
typesFromSignature.get(i).add(signatureType(types.get(i)));
}
returnFromSignature.add(entry.getValue().esType());
}
Expand All @@ -637,6 +637,10 @@ public static void testFunctionInfo() {

}

private static String signatureType(DataType type) {
return type.esType() != null ? type.esType() : type.typeName();
}

/**
* Adds cases with {@code null} and asserts that the result is {@code null}.
* <p>
Expand Down Expand Up @@ -894,6 +898,7 @@ protected static String typeErrorMessage(boolean includeOrdinal, List<Set<DataTy
),
"numeric, date_period or time_duration"
),
Map.entry(Set.of(DataTypes.DATETIME, DataTypes.NULL), "datetime"),
Map.entry(Set.of(DataTypes.DOUBLE, DataTypes.NULL), "double"),
Map.entry(Set.of(DataTypes.INTEGER, DataTypes.NULL), "integer"),
Map.entry(Set.of(DataTypes.IP, DataTypes.NULL), "ip"),
Expand Down Expand Up @@ -983,7 +988,8 @@ protected static String typeErrorMessage(boolean includeOrdinal, List<Set<DataTy
Set.of(EsqlDataTypes.CARTESIAN_POINT, EsqlDataTypes.CARTESIAN_SHAPE, DataTypes.KEYWORD, DataTypes.TEXT, DataTypes.NULL),
"cartesian_point or cartesian_shape or string"
),
Map.entry(Set.of(EsqlDataTypes.GEO_POINT, EsqlDataTypes.CARTESIAN_POINT, DataTypes.NULL), "geo_point or cartesian_point")
Map.entry(Set.of(EsqlDataTypes.GEO_POINT, EsqlDataTypes.CARTESIAN_POINT, DataTypes.NULL), "geo_point or cartesian_point"),
Map.entry(Set.of(EsqlDataTypes.DATE_PERIOD, EsqlDataTypes.TIME_DURATION, DataTypes.NULL), "dateperiod or timeduration")
);

// TODO: generate this message dynamically, a la AbstractConvertFunction#supportedTypesNames()?
Expand Down Expand Up @@ -1183,6 +1189,10 @@ private static boolean renderExamples(FunctionInfo info) throws IOException {
builder.append("*Examples*\n\n");
}
for (Example example : info.examples()) {
if (example.description().length() > 0) {
builder.append(example.description());
builder.append("\n");
}
builder.append("""
[source.merge.styled,esql]
----
Expand Down

0 comments on commit 2588c72

Please sign in to comment.