Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] Esql implicit casting for date nanos (#118697) #118888

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/118697.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 118697
summary: Esql implicit casting for date nanos
area: ES|QL
type: enhancement
issues:
- 118476
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,137 @@ millis:date | nanos:date_nanos | num:long
2023-10-23T13:33:34.937Z | 2023-10-23T13:33:34.937193000Z | 1698068014937193000
;

implicit casting to nanos, date only
required_capability: date_nanos_type
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE MV_MIN(nanos) > "2023-10-23"
| SORT nanos DESC
| KEEP millis, nanos;

millis:date | nanos:date_nanos
2023-10-23T13:55:01.543Z | 2023-10-23T13:55:01.543123456Z
2023-10-23T13:53:55.832Z | 2023-10-23T13:53:55.832987654Z
2023-10-23T13:52:55.015Z | 2023-10-23T13:52:55.015787878Z
2023-10-23T13:51:54.732Z | 2023-10-23T13:51:54.732102837Z
2023-10-23T13:33:34.937Z | 2023-10-23T13:33:34.937193000Z
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z
2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360103847Z
2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360103847Z
;

implicit casting to nanos, date only, equality test
required_capability: date_nanos_type
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE MV_MIN(nanos) == "2023-10-23"
| SORT nanos DESC
| KEEP millis, nanos;

millis:date | nanos:date_nanos
;


implicit casting to nanos, date plus time to seconds
required_capability: date_nanos_type
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE MV_MIN(nanos) > "2023-10-23T00:00:00"
| SORT nanos DESC
| KEEP millis, nanos;

millis:date | nanos:date_nanos
2023-10-23T13:55:01.543Z | 2023-10-23T13:55:01.543123456Z
2023-10-23T13:53:55.832Z | 2023-10-23T13:53:55.832987654Z
2023-10-23T13:52:55.015Z | 2023-10-23T13:52:55.015787878Z
2023-10-23T13:51:54.732Z | 2023-10-23T13:51:54.732102837Z
2023-10-23T13:33:34.937Z | 2023-10-23T13:33:34.937193000Z
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z
2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360103847Z
2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360103847Z
;

implicit casting to nanos, date plus time to seconds, equality test
required_capability: date_nanos_type
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE MV_MIN(nanos) == "2023-10-23T12:27:28"
| SORT nanos DESC
| KEEP millis, nanos;

millis:date | nanos:date_nanos
;

implicit casting to nanos, date plus time to millis
required_capability: date_nanos_type
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE MV_MIN(nanos) > "2023-10-23T00:00:00.000"
| SORT nanos DESC
| KEEP millis, nanos;

millis:date | nanos:date_nanos
2023-10-23T13:55:01.543Z | 2023-10-23T13:55:01.543123456Z
2023-10-23T13:53:55.832Z | 2023-10-23T13:53:55.832987654Z
2023-10-23T13:52:55.015Z | 2023-10-23T13:52:55.015787878Z
2023-10-23T13:51:54.732Z | 2023-10-23T13:51:54.732102837Z
2023-10-23T13:33:34.937Z | 2023-10-23T13:33:34.937193000Z
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z
2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360103847Z
2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360103847Z
;

implicit casting to nanos, date plus time to millis, equality test
required_capability: date_nanos_type
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE MV_MIN(nanos) == "2023-10-23T12:27:28.948"
| SORT nanos DESC
| KEEP millis, nanos;

millis:date | nanos:date_nanos
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z
;

implicit casting to nanos, date plus time to nanos
required_capability: date_nanos_type
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE MV_MIN(nanos) > "2023-10-23T00:00:00.000000000"
| SORT nanos DESC
| KEEP millis, nanos;

millis:date | nanos:date_nanos
2023-10-23T13:55:01.543Z | 2023-10-23T13:55:01.543123456Z
2023-10-23T13:53:55.832Z | 2023-10-23T13:53:55.832987654Z
2023-10-23T13:52:55.015Z | 2023-10-23T13:52:55.015787878Z
2023-10-23T13:51:54.732Z | 2023-10-23T13:51:54.732102837Z
2023-10-23T13:33:34.937Z | 2023-10-23T13:33:34.937193000Z
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z
2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360103847Z
2023-10-23T12:15:03.360Z | 2023-10-23T12:15:03.360103847Z
;

implicit casting to nanos, date plus time to nanos, equality test
required_capability: date_nanos_type
required_capability: date_nanos_implicit_casting

FROM date_nanos
| WHERE MV_MIN(nanos) == "2023-10-23T12:27:28.948000000"
| SORT nanos DESC
| KEEP millis, nanos;

millis:date | nanos:date_nanos
2023-10-23T12:27:28.948Z | 2023-10-23T12:27:28.948000000Z
;

date nanos greater than millis
required_capability: date_nanos_type
required_capability: date_nanos_compare_to_millis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,10 @@ public enum Cap {
* Support for mixed comparisons between nanosecond and millisecond dates
*/
DATE_NANOS_COMPARE_TO_MILLIS(),

/**
* Support implicit casting of strings to date nanos
*/
DATE_NANOS_IMPLICIT_CASTING(),
/**
* Support Least and Greatest functions on Date Nanos type
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
import static org.elasticsearch.xpack.core.enrich.EnrichPolicy.GEO_MATCH_TYPE;
import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN;
import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME;
import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_NANOS;
import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_PERIOD;
import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE;
import static org.elasticsearch.xpack.esql.core.type.DataType.FLOAT;
Expand Down Expand Up @@ -1050,21 +1051,23 @@ private BitSet gatherPreAnalysisMetrics(LogicalPlan plan, BitSet b) {
/**
* Cast string literals in ScalarFunction, EsqlArithmeticOperation, BinaryComparison, In and GroupingFunction to desired data types.
* For example, the string literals in the following expressions will be cast implicitly to the field data type on the left hand side.
* date > "2024-08-21"
* date in ("2024-08-21", "2024-08-22", "2024-08-23")
* date = "2024-08-21" + 3 days
* ip == "127.0.0.1"
* version != "1.0"
* bucket(dateField, "1 month")
* date_trunc("1 minute", dateField)
*
* <ul>
* <li>date > "2024-08-21"</li>
* <li>date in ("2024-08-21", "2024-08-22", "2024-08-23")</li>
* <li>date = "2024-08-21" + 3 days</li>
* <li>ip == "127.0.0.1"</li>
* <li>version != "1.0"</li>
* <li>bucket(dateField, "1 month")</li>
* <li>date_trunc("1 minute", dateField)</li>
* </ul>
* If the inputs to Coalesce are mixed numeric types, cast the rest of the numeric field or value to the first numeric data type if
* applicable. For example, implicit casting converts:
* Coalesce(Long, Int) to Coalesce(Long, Long)
* Coalesce(null, Long, Int) to Coalesce(null, Long, Long)
* Coalesce(Double, Long, Int) to Coalesce(Double, Double, Double)
* Coalesce(null, Double, Long, Int) to Coalesce(null, Double, Double, Double)
*
* <ul>
* <li>Coalesce(Long, Int) to Coalesce(Long, Long)</li>
* <li>Coalesce(null, Long, Int) to Coalesce(null, Long, Long)</li>
* <li>Coalesce(Double, Long, Int) to Coalesce(Double, Double, Double)</li>
* <li>Coalesce(null, Double, Long, Int) to Coalesce(null, Double, Double, Double)</li>
* </ul>
* Coalesce(Int, Long) will NOT be converted to Coalesce(Long, Long) or Coalesce(Int, Int).
*/
private static class ImplicitCasting extends ParameterizedRule<LogicalPlan, LogicalPlan, AnalyzerContext> {
Expand Down Expand Up @@ -1245,7 +1248,7 @@ private static boolean supportsImplicitTemporalCasting(Expression e, BinaryOpera
}

private static boolean supportsStringImplicitCasting(DataType type) {
return type == DATETIME || type == IP || type == VERSION || type == BOOLEAN;
return type == DATETIME || type == DATE_NANOS || type == IP || type == VERSION || type == BOOLEAN;
}

private static UnresolvedAttribute unresolvedAttribute(Expression value, String type, Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.time.DateFormatter;
import org.elasticsearch.common.time.DateFormatters;
import org.elasticsearch.common.time.DateUtils;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
import org.elasticsearch.xpack.esql.core.QlIllegalArgumentException;
Expand Down Expand Up @@ -51,7 +53,6 @@
import java.time.Period;
import java.time.ZoneId;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -202,6 +203,9 @@ public static Converter converterFor(DataType from, DataType to) {
if (to == DataType.DATETIME) {
return EsqlConverter.STRING_TO_DATETIME;
}
if (to == DATE_NANOS) {
return EsqlConverter.STRING_TO_DATE_NANOS;
}
if (to == DataType.IP) {
return EsqlConverter.STRING_TO_IP;
}
Expand Down Expand Up @@ -521,13 +525,12 @@ public static long dateTimeToLong(String dateTime, DateFormatter formatter) {
}

public static long dateNanosToLong(String dateNano) {
return dateNanosToLong(dateNano, DateFormatter.forPattern("strict_date_optional_time_nanos"));
return dateNanosToLong(dateNano, DEFAULT_DATE_NANOS_FORMATTER);
}

public static long dateNanosToLong(String dateNano, DateFormatter formatter) {
TemporalAccessor parsed = formatter.parse(dateNano);
long nanos = parsed.getLong(ChronoField.INSTANT_SECONDS) * 1_000_000_000 + parsed.getLong(ChronoField.NANO_OF_SECOND);
return nanos;
Instant parsed = DateFormatters.from(formatter.parse(dateNano)).toInstant();
return DateUtils.toLong(parsed);
}

public static String dateTimeToString(long dateTime) {
Expand Down Expand Up @@ -646,6 +649,7 @@ public enum EsqlConverter implements Converter {
STRING_TO_TIME_DURATION(x -> EsqlDataTypeConverter.parseTemporalAmount(x, DataType.TIME_DURATION)),
STRING_TO_CHRONO_FIELD(EsqlDataTypeConverter::stringToChrono),
STRING_TO_DATETIME(x -> EsqlDataTypeConverter.dateTimeToLong((String) x)),
STRING_TO_DATE_NANOS(x -> EsqlDataTypeConverter.dateNanosToLong((String) x)),
STRING_TO_IP(x -> EsqlDataTypeConverter.stringToIP((String) x)),
STRING_TO_VERSION(x -> EsqlDataTypeConverter.stringToVersion((String) x)),
STRING_TO_DOUBLE(x -> EsqlDataTypeConverter.stringToDouble((String) x)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ private Page randomPage(List<ColumnInfoImpl> columns) {
case BOOLEAN -> ((BooleanBlock.Builder) builder).appendBoolean(randomBoolean());
case UNSUPPORTED -> ((BytesRefBlock.Builder) builder).appendNull();
// TODO - add a random instant thing here?
case DATE_NANOS -> ((LongBlock.Builder) builder).appendLong(randomLong());
case DATE_NANOS -> ((LongBlock.Builder) builder).appendLong(randomNonNegativeLong());
case VERSION -> ((BytesRefBlock.Builder) builder).appendBytesRef(new Version(randomIdentifier()).toBytesRef());
case GEO_POINT -> ((BytesRefBlock.Builder) builder).appendBytesRef(GEO.asWkb(GeometryTestUtils.randomPoint()));
case CARTESIAN_POINT -> ((BytesRefBlock.Builder) builder).appendBytesRef(CARTESIAN.asWkb(ShapeTestUtils.randomPoint()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

package org.elasticsearch.xpack.esql.type;

import org.elasticsearch.common.time.DateUtils;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.esql.core.type.DataType;

import java.time.Instant;
import java.util.Arrays;
import java.util.List;

Expand Down Expand Up @@ -52,11 +54,19 @@
public class EsqlDataTypeConverterTests extends ESTestCase {

public void testNanoTimeToString() {
long expected = randomLong();
long expected = randomNonNegativeLong();
long actual = EsqlDataTypeConverter.dateNanosToLong(EsqlDataTypeConverter.nanoTimeToString(expected));
assertEquals(expected, actual);
}

public void testStringToDateNanos() {
assertEquals(
DateUtils.toLong(Instant.parse("2023-01-01T00:00:00.000Z")),
EsqlDataTypeConverter.convert("2023-01-01T00:00:00.000000000", DATE_NANOS)
);
assertEquals(DateUtils.toLong(Instant.parse("2023-01-01T00:00:00.000Z")), EsqlDataTypeConverter.convert("2023-01-01", DATE_NANOS));
}

public void testCommonTypeNull() {
for (DataType dataType : DataType.values()) {
assertEqualsCommonType(dataType, NULL, dataType);
Expand Down