mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-03-31 04:18:39 +00:00
SQL: Introduce SQL TIME data type (#39802)
Support ANSI SQL's TIME type by introductin a runtime-only ES SQL time type. Closes: #38174 (cherry picked from commit 046ccd4cf0a251b2a3ddff6b072ab539a6711900)
This commit is contained in:
parent
12bf3b2025
commit
899ed2bf81
docs/reference/sql
x-pack/plugin/sql
jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc
qa/src/main
java/org/elasticsearch/xpack/sql/qa
resources
sql-proto/src/main/java/org/elasticsearch/xpack/sql/proto
src
main
java/org/elasticsearch/xpack/sql
analysis/analyzer
execution/search/extractor
expression
TypeResolutions.java
function
aggregate
scalar
gen/script
predicate/operator/arithmetic
parser
planner
querydsl/agg
type
util
resources/org/elasticsearch/xpack/sql/plugin
test/java/org/elasticsearch/xpack/sql
analysis/analyzer
expression
function/scalar/datetime
predicate/operator/arithmetic
parser
plan/logical/command/sys
planner
type
@ -80,3 +80,7 @@ When the histogram in SQL is applied on **DATE** type instead of **DATETIME**, t
|
||||
the multiple of a day. E.g.: for `HISTOGRAM(CAST(birth_date AS DATE), INTERVAL '2 3:04' DAY TO MINUTE)` the interval
|
||||
actually used will be `INTERVAL '2' DAY`. If the interval specified is less than 1 day, e.g.:
|
||||
`HISTOGRAM(CAST(birth_date AS DATE), INTERVAL '20' HOUR)` then the interval used will be `INTERVAL '1' DAY`.
|
||||
|
||||
[IMPORTANT]
|
||||
Histogram in SQL cannot be applied applied on **TIME** type.
|
||||
E.g.: `HISTOGRAM(CAST(birth_date AS TIME), INTERVAL '10' MINUTES)` is currently not supported.
|
||||
|
@ -44,9 +44,10 @@ s|SQL precision
|
||||
Most of {es} <<mapping-types, data types>> are available in {es-sql}, as indicated above.
|
||||
As one can see, all of {es} <<mapping-types, data types>> are mapped to the data type with the same
|
||||
name in {es-sql}, with the exception of **date** data type which is mapped to **datetime** in {es-sql}.
|
||||
This is to avoid confusion with the ANSI SQL **DATE** (date only) type, which is also supported by {es-sql}
|
||||
in queries (with the use of <<sql-functions-type-conversion-cast>>/<<sql-functions-type-conversion-convert>>),
|
||||
but doesn't correspond to an actual mapping in {es} (see the <<es-sql-only-types, `table`>> below).
|
||||
This is to avoid confusion with the ANSI SQL types **DATE** (date only) and **TIME** (time only), which are also
|
||||
supported by {es-sql} in queries (with the use of
|
||||
<<sql-functions-type-conversion-cast>>/<<sql-functions-type-conversion-convert>>), but don't correspond to an
|
||||
actual mapping in {es} (see the <<es-sql-only-types, `table`>> below).
|
||||
|
||||
Obviously, not all types in {es} have an equivalent in SQL and vice-versa hence why, {es-sql}
|
||||
uses the data type _particularities_ of the former over the latter as ultimately {es} is the backing store.
|
||||
@ -66,6 +67,7 @@ s|SQL precision
|
||||
|
||||
|
||||
| date | 24
|
||||
| time | 18
|
||||
| interval_year | 7
|
||||
| interval_month | 7
|
||||
| interval_day | 23
|
||||
|
@ -113,3 +113,28 @@ FROM (SELECT ...) WHERE [simple_condition]`, this is currently **un-supported**.
|
||||
Using `FIRST` and `LAST` in the `HAVING` clause is not supported. The same applies to
|
||||
<<sql-functions-aggs-min,`MIN`>> and <<sql-functions-aggs-max,`MAX`>> when their target column
|
||||
is of type <<keyword, `keyword`>> as they are internally translated to `FIRST` and `LAST`.
|
||||
|
||||
[float]
|
||||
=== Using TIME data type in GROUP BY or <<sql-functions-grouping-histogram>>
|
||||
|
||||
Using `TIME` data type as a grouping key is currently not supported. For example:
|
||||
|
||||
[source, sql]
|
||||
-------------------------------------------------------------
|
||||
SELECT count(*) FROM test GROUP BY CAST(date_created AS TIME);
|
||||
-------------------------------------------------------------
|
||||
|
||||
On the other hand, it can still be used if it's wrapped with a scalar function that returns another data type,
|
||||
for example:
|
||||
|
||||
[source, sql]
|
||||
-------------------------------------------------------------
|
||||
SELECT count(*) FROM test GROUP BY MINUTE((CAST(date_created AS TIME));
|
||||
-------------------------------------------------------------
|
||||
|
||||
`TIME` data type is also currently not supported in histogram grouping function. For example:
|
||||
|
||||
[source, sql]
|
||||
-------------------------------------------------------------
|
||||
SELECT HISTOGRAM(CAST(birth_date AS TIME), INTERVAL '10' MINUTES) as h, COUNT(*) FROM t GROUP BY h
|
||||
-------------------------------------------------------------
|
||||
|
@ -29,6 +29,7 @@ public enum EsType implements SQLType {
|
||||
NESTED( Types.STRUCT),
|
||||
BINARY( Types.VARBINARY),
|
||||
DATE( Types.DATE),
|
||||
TIME( Types.TIME),
|
||||
DATETIME( Types.TIMESTAMP),
|
||||
IP( Types.VARCHAR),
|
||||
INTERVAL_YEAR( ExtraTypes.INTERVAL_YEAR),
|
||||
|
@ -10,17 +10,12 @@ import java.sql.Date;
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
|
||||
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
|
||||
import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
|
||||
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
|
||||
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
|
||||
import static org.elasticsearch.xpack.sql.proto.StringUtils.ISO_DATE_WITH_MILLIS;
|
||||
import static org.elasticsearch.xpack.sql.proto.StringUtils.ISO_TIME_WITH_MILLIS;
|
||||
|
||||
/**
|
||||
* JDBC specific datetime specific utility methods. Because of lack of visibility, this class borrows code
|
||||
@ -30,29 +25,21 @@ final class JdbcDateUtils {
|
||||
|
||||
private JdbcDateUtils() {}
|
||||
|
||||
// In Java 8 LocalDate.EPOCH is not available, introduced with later Java versions
|
||||
private static final LocalDate EPOCH = LocalDate.of(1970, 1, 1);
|
||||
|
||||
static final DateTimeFormatter ISO_WITH_MILLIS = new DateTimeFormatterBuilder()
|
||||
.parseCaseInsensitive()
|
||||
.append(ISO_LOCAL_DATE)
|
||||
.appendLiteral('T')
|
||||
.appendValue(HOUR_OF_DAY, 2)
|
||||
.appendLiteral(':')
|
||||
.appendValue(MINUTE_OF_HOUR, 2)
|
||||
.appendLiteral(':')
|
||||
.appendValue(SECOND_OF_MINUTE, 2)
|
||||
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
|
||||
.appendOffsetId()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
private static ZonedDateTime asDateTime(String date) {
|
||||
return ISO_WITH_MILLIS.parse(date, ZonedDateTime::from);
|
||||
return ISO_DATE_WITH_MILLIS.parse(date, ZonedDateTime::from);
|
||||
}
|
||||
|
||||
static long asMillisSinceEpoch(String date) {
|
||||
static long dateTimeAsMillisSinceEpoch(String date) {
|
||||
return asDateTime(date).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
static long timeAsMillisSinceEpoch(String date) {
|
||||
return ISO_TIME_WITH_MILLIS.parse(date, OffsetTime::from).atDate(EPOCH).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
static Date asDate(String date) {
|
||||
ZonedDateTime zdt = asDateTime(date);
|
||||
return new Date(zdt.toLocalDate().atStartOfDay(zdt.getZone()).toInstant().toEpochMilli());
|
||||
@ -63,14 +50,22 @@ final class JdbcDateUtils {
|
||||
return new Time(zdt.toLocalTime().atDate(EPOCH).atZone(zdt.getZone()).toInstant().toEpochMilli());
|
||||
}
|
||||
|
||||
static Time timeAsTime(String date) {
|
||||
OffsetTime ot = ISO_TIME_WITH_MILLIS.parse(date, OffsetTime::from);
|
||||
return new Time(ot.atDate(EPOCH).toInstant().toEpochMilli());
|
||||
}
|
||||
|
||||
static Timestamp asTimestamp(long millisSinceEpoch) {
|
||||
return new Timestamp(millisSinceEpoch);
|
||||
}
|
||||
|
||||
static Timestamp asTimestamp(String date) {
|
||||
return new Timestamp(asMillisSinceEpoch(date));
|
||||
return new Timestamp(dateTimeAsMillisSinceEpoch(date));
|
||||
}
|
||||
|
||||
static Timestamp timeAsTimestamp(String date) {
|
||||
return new Timestamp(timeAsMillisSinceEpoch(date));
|
||||
}
|
||||
/*
|
||||
* Handles the value received as parameter, as either String (a ZonedDateTime formatted in ISO 8601 standard with millis) -
|
||||
* date fields being returned formatted like this. Or a Long value, in case of Histograms.
|
||||
|
@ -33,8 +33,15 @@ import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.EsType.DATE;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.EsType.DATETIME;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.EsType.TIME;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.asDateTimeField;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.asMillisSinceEpoch;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.dateTimeAsMillisSinceEpoch;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.asTimestamp;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.timeAsMillisSinceEpoch;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.timeAsTime;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.timeAsTimestamp;
|
||||
|
||||
class JdbcResultSet implements ResultSet, JdbcWrapper {
|
||||
|
||||
@ -251,17 +258,20 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
||||
// TODO: the B6 appendix of the jdbc spec does mention CHAR, VARCHAR, LONGVARCHAR, DATE, TIMESTAMP as supported
|
||||
// jdbc types that should be handled by getDate and getTime methods. From all of those we support VARCHAR and
|
||||
// TIMESTAMP. Should we consider the VARCHAR conversion as a later enhancement?
|
||||
if (EsType.DATETIME == type) {
|
||||
if (DATETIME == type) {
|
||||
// the cursor can return an Integer if the date-since-epoch is small enough, XContentParser (Jackson) will
|
||||
// return the "smallest" data type for numbers when parsing
|
||||
// TODO: this should probably be handled server side
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
return asDateTimeField(val, JdbcDateUtils::asMillisSinceEpoch, Function.identity());
|
||||
return asDateTimeField(val, JdbcDateUtils::dateTimeAsMillisSinceEpoch, Function.identity());
|
||||
}
|
||||
if (EsType.DATE == type) {
|
||||
return asMillisSinceEpoch(val.toString());
|
||||
if (DATE == type) {
|
||||
return dateTimeAsMillisSinceEpoch(val.toString());
|
||||
}
|
||||
if (TIME == type) {
|
||||
return timeAsMillisSinceEpoch(val.toString());
|
||||
}
|
||||
return val == null ? null : (Long) val;
|
||||
} catch (ClassCastException cce) {
|
||||
@ -277,10 +287,15 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
||||
return null;
|
||||
}
|
||||
|
||||
EsType type = columnType(columnIndex);
|
||||
if (type == TIME) {
|
||||
return new Date(0L);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
return JdbcDateUtils.asDate(val.toString());
|
||||
} catch (Exception e) {
|
||||
EsType type = columnType(columnIndex);
|
||||
throw new SQLException(
|
||||
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Date", val, type.getName()), e);
|
||||
}
|
||||
@ -294,11 +309,14 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
||||
}
|
||||
|
||||
EsType type = columnType(columnIndex);
|
||||
if (type == EsType.DATE) {
|
||||
if (type == DATE) {
|
||||
return new Time(0L);
|
||||
}
|
||||
|
||||
try {
|
||||
if (type == TIME) {
|
||||
return timeAsTime(val.toString());
|
||||
}
|
||||
return JdbcDateUtils.asTime(val.toString());
|
||||
} catch (Exception e) {
|
||||
throw new SQLException(
|
||||
@ -313,13 +331,16 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
||||
return null;
|
||||
}
|
||||
|
||||
EsType type = columnType(columnIndex);
|
||||
try {
|
||||
if (val instanceof Number) {
|
||||
return JdbcDateUtils.asTimestamp(((Number) val).longValue());
|
||||
return asTimestamp(((Number) val).longValue());
|
||||
}
|
||||
return JdbcDateUtils.asTimestamp(val.toString());
|
||||
if (type == TIME) {
|
||||
return timeAsTimestamp(val.toString());
|
||||
}
|
||||
return asTimestamp(val.toString());
|
||||
} catch (Exception e) {
|
||||
EsType type = columnType(columnIndex);
|
||||
throw new SQLException(
|
||||
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Timestamp", val, type.getName()), e);
|
||||
}
|
||||
@ -342,7 +363,7 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
||||
@Override
|
||||
public Time getTime(int columnIndex, Calendar cal) throws SQLException {
|
||||
EsType type = columnType(columnIndex);
|
||||
if (type == EsType.DATE) {
|
||||
if (type == DATE) {
|
||||
return new Time(0L);
|
||||
}
|
||||
return TypeConverter.convertTime(dateTimeAsMillis(columnIndex), safeCalendar(cal));
|
||||
|
@ -35,6 +35,10 @@ import static java.util.Calendar.MINUTE;
|
||||
import static java.util.Calendar.MONTH;
|
||||
import static java.util.Calendar.SECOND;
|
||||
import static java.util.Calendar.YEAR;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.EsType.DATE;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.EsType.DATETIME;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.EsType.TIME;
|
||||
import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.asDateTimeField;
|
||||
|
||||
/**
|
||||
* Conversion utilities for conversion of JDBC types to Java type and back
|
||||
@ -214,9 +218,11 @@ final class TypeConverter {
|
||||
case FLOAT:
|
||||
return floatValue(v); // Float might be represented as string for infinity and NaN values
|
||||
case DATE:
|
||||
return JdbcDateUtils.asDateTimeField(v, JdbcDateUtils::asDate, Date::new);
|
||||
return asDateTimeField(v, JdbcDateUtils::asDate, Date::new);
|
||||
case TIME:
|
||||
return asDateTimeField(v, JdbcDateUtils::asTime, Time::new);
|
||||
case DATETIME:
|
||||
return JdbcDateUtils.asDateTimeField(v, JdbcDateUtils::asTimestamp, Timestamp::new);
|
||||
return asDateTimeField(v, JdbcDateUtils::asTimestamp, Timestamp::new);
|
||||
case INTERVAL_YEAR:
|
||||
case INTERVAL_MONTH:
|
||||
case INTERVAL_YEAR_TO_MONTH:
|
||||
@ -471,25 +477,34 @@ final class TypeConverter {
|
||||
}
|
||||
|
||||
private static Date asDate(Object val, EsType columnType, String typeString) throws SQLException {
|
||||
if (columnType == EsType.DATETIME || columnType == EsType.DATE) {
|
||||
return JdbcDateUtils.asDateTimeField(val, JdbcDateUtils::asDate, Date::new);
|
||||
if (columnType == DATETIME || columnType == DATE) {
|
||||
return asDateTimeField(val, JdbcDateUtils::asDate, Date::new);
|
||||
}
|
||||
if (columnType == TIME) {
|
||||
return new Date(0L);
|
||||
}
|
||||
return failConversion(val, columnType, typeString, Date.class);
|
||||
}
|
||||
|
||||
private static Time asTime(Object val, EsType columnType, String typeString) throws SQLException {
|
||||
if (columnType == EsType.DATETIME) {
|
||||
return JdbcDateUtils.asDateTimeField(val, JdbcDateUtils::asTime, Time::new);
|
||||
if (columnType == DATETIME) {
|
||||
return asDateTimeField(val, JdbcDateUtils::asTime, Time::new);
|
||||
}
|
||||
if (columnType == EsType.DATE) {
|
||||
if (columnType == TIME) {
|
||||
return asDateTimeField(val, JdbcDateUtils::timeAsTime, Time::new);
|
||||
}
|
||||
if (columnType == DATE) {
|
||||
return new Time(0L);
|
||||
}
|
||||
return failConversion(val, columnType, typeString, Time.class);
|
||||
}
|
||||
|
||||
private static Timestamp asTimestamp(Object val, EsType columnType, String typeString) throws SQLException {
|
||||
if (columnType == EsType.DATETIME || columnType == EsType.DATE) {
|
||||
return JdbcDateUtils.asDateTimeField(val, JdbcDateUtils::asTimestamp, Timestamp::new);
|
||||
if (columnType == DATETIME || columnType == DATE) {
|
||||
return asDateTimeField(val, JdbcDateUtils::asTimestamp, Timestamp::new);
|
||||
}
|
||||
if (columnType == TIME) {
|
||||
return asDateTimeField(val, JdbcDateUtils::timeAsTimestamp, Timestamp::new);
|
||||
}
|
||||
return failConversion(val, columnType, typeString, Timestamp.class);
|
||||
}
|
||||
|
@ -72,6 +72,18 @@ public abstract class SqlProtocolTestCase extends ESRestTestCase {
|
||||
"datetime", "1119-01-15T12:37:29.000Z", 24);
|
||||
assertQuery("SELECT CAST(CAST('-26853765751000' AS BIGINT) AS DATETIME)", "CAST(CAST('-26853765751000' AS BIGINT) AS DATETIME)",
|
||||
"datetime", "1119-01-15T12:37:29.000Z", 24);
|
||||
|
||||
assertQuery("SELECT CAST('2019-01-14' AS DATE)", "CAST('2019-01-14' AS DATE)",
|
||||
"date", "2019-01-14T00:00:00.000Z", 24);
|
||||
assertQuery("SELECT CAST(-26853765751000 AS DATE)", "CAST(-26853765751000 AS DATE)",
|
||||
"date", "1119-01-15T00:00:00.000Z", 24);
|
||||
|
||||
assertQuery("SELECT CAST('12:29:25.123Z' AS TIME)", "CAST('12:29:25.123Z' AS TIME)",
|
||||
"time", "12:29:25.123Z", 18);
|
||||
assertQuery("SELECT CAST('12:29:25.123456789+05:00' AS TIME)", "CAST('12:29:25.123456789+05:00' AS TIME)",
|
||||
"time", "12:29:25.123+05:00", 18);
|
||||
assertQuery("SELECT CAST(-26853765751000 AS TIME)", "CAST(-26853765751000 AS TIME)",
|
||||
"time", "12:37:29.000Z", 18);
|
||||
}
|
||||
|
||||
public void testIPs() throws IOException {
|
||||
|
@ -6,7 +6,6 @@
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.CsvTestUtils.CsvTestCase;
|
||||
|
||||
|
@ -7,7 +7,6 @@
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc;
|
||||
|
||||
import com.carrotsearch.hppc.IntObjectHashMap;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.elasticsearch.xpack.sql.jdbc.EsType;
|
||||
import org.elasticsearch.xpack.sql.proto.StringUtils;
|
||||
@ -208,6 +207,9 @@ public class JdbcAssert {
|
||||
case "Date":
|
||||
columnClassName = "java.sql.Date";
|
||||
break;
|
||||
case "Time":
|
||||
columnClassName = "java.sql.Time";
|
||||
break;
|
||||
case "Timestamp":
|
||||
columnClassName = "java.sql.Timestamp";
|
||||
break;
|
||||
|
@ -1151,6 +1151,35 @@ public class ResultSetTestCase extends JdbcIntegrationTestCase {
|
||||
assertEquals(expectedTimestamp, results.getObject("date", java.sql.Timestamp.class));
|
||||
});
|
||||
}
|
||||
|
||||
public void testGetTimeType() throws Exception {
|
||||
createIndex("test");
|
||||
updateMapping("test", builder -> builder.startObject("test_date").field("type", "date").endObject());
|
||||
|
||||
// 2018-03-12 17:20:30.123 UTC
|
||||
Long timeInMillis = 1520875230123L;
|
||||
index("test", "1", builder -> builder.field("test_date", timeInMillis));
|
||||
|
||||
// UTC +10 hours
|
||||
String timeZoneId1 = "Etc/GMT-10";
|
||||
|
||||
doWithQueryAndTimezone("SELECT CAST(test_date AS TIME) as time FROM test", timeZoneId1, results -> {
|
||||
results.next();
|
||||
|
||||
java.sql.Date expectedDate = new java.sql.Date(0L);
|
||||
assertEquals(expectedDate, results.getDate("time"));
|
||||
assertEquals(expectedDate, results.getObject("time", java.sql.Date.class));
|
||||
|
||||
java.sql.Time expectedTime = asTime(timeInMillis, ZoneId.of("Etc/GMT-10"));
|
||||
assertEquals(expectedTime, results.getTime("time"));
|
||||
assertEquals(expectedTime, results.getObject("time", java.sql.Time.class));
|
||||
|
||||
java.sql.Timestamp expectedTimestamp = new java.sql.Timestamp(expectedTime.getTime());
|
||||
assertEquals(expectedTimestamp, results.getTimestamp("time"));
|
||||
assertEquals(expectedTimestamp, results.getObject("time", java.sql.Timestamp.class));
|
||||
});
|
||||
}
|
||||
|
||||
public void testValidGetObjectCalls() throws Exception {
|
||||
createIndex("test");
|
||||
updateMappingForNumericValuesTests("test");
|
||||
|
83
x-pack/plugin/sql/qa/src/main/resources/time.csv-spec
Normal file
83
x-pack/plugin/sql/qa/src/main/resources/time.csv-spec
Normal file
@ -0,0 +1,83 @@
|
||||
//
|
||||
// TIME
|
||||
//
|
||||
|
||||
|
||||
timeExtractTimeParts
|
||||
SELECT
|
||||
SECOND(CAST(birth_date AS TIME)) d,
|
||||
MINUTE(CAST(birth_date AS TIME)) m,
|
||||
HOUR(CAST(birth_date AS TIME)) h
|
||||
FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no;
|
||||
|
||||
d:i | m:i | h:i
|
||||
0 |0 |0
|
||||
0 |0 |0
|
||||
0 |0 |0
|
||||
0 |0 |0
|
||||
0 |0 |0
|
||||
0 |0 |0
|
||||
0 |0 |0
|
||||
0 |0 |0
|
||||
0 |0 |0
|
||||
;
|
||||
|
||||
timeAsFilter
|
||||
SELECT birth_date, last_name FROM "test_emp" WHERE birth_date::TIME = CAST('00:00:00' AS TIME) ORDER BY emp_no LIMIT 5;
|
||||
|
||||
birth_date:ts | last_name:s
|
||||
1953-09-02 00:00:00Z | Facello
|
||||
1964-06-02 00:00:00Z | Simmel
|
||||
1959-12-03 00:00:00Z | Bamford
|
||||
1954-05-01 00:00:00Z | Koblick
|
||||
1955-01-21 00:00:00Z | Maliniak
|
||||
;
|
||||
|
||||
timeAsFilter_NoMatch
|
||||
SELECT count(*) FROM "test_emp" WHERE birth_date::TIME = CAST('12:34:56.789' AS TIME);
|
||||
|
||||
count(*):l
|
||||
0
|
||||
;
|
||||
|
||||
timeAsOrderBy
|
||||
SELECT last_name FROM "test_emp" ORDER BY birth_date::TIME, emp_no LIMIT 5;
|
||||
|
||||
last_name:s
|
||||
Meriste
|
||||
Lenart
|
||||
Stamatiou
|
||||
Tzvieli
|
||||
Casley
|
||||
;
|
||||
|
||||
timeAndFunctionAsGroupingKey
|
||||
SELECT HOUR(CAST(birth_date AS TIME)) AS m, CAST(SUM(emp_no) AS INT) s FROM test_emp GROUP BY m ORDER BY m LIMIT 5;
|
||||
|
||||
m:i | s:i
|
||||
null |100445
|
||||
0 |904605
|
||||
;
|
||||
|
||||
|
||||
timeAsHavingFilter
|
||||
SELECT MINUTE_OF_HOUR(MAX(birth_date)::TIME + INTERVAL 10 MINUTES) as minute, gender FROM test_emp GROUP BY gender HAVING CAST(MAX(birth_date) AS TIME) = CAST('00:00:00.000' AS TIME) ORDER BY gender;
|
||||
|
||||
minute:i | gender:s
|
||||
10 | null
|
||||
10 | F
|
||||
10 | M
|
||||
;
|
||||
|
||||
timeAsHavingFilterNoMatch
|
||||
SELECT MINUTE_OF_HOUR(MAX(birth_date)::TIME) as minute, gender FROM test_emp GROUP BY gender HAVING CAST(MAX(birth_date) AS TIME) > CAST('00:00:00.000' AS TIME);
|
||||
|
||||
minute:i | gender:s
|
||||
;
|
||||
|
||||
timeAndInterval
|
||||
SELECT HOUR(CAST('10:11:12.345' AS TIME) + INTERVAL '20' HOURS) AS h, SECOND(INTERVAL '40' SECONDS + CAST('10:11:12.345' AS TIME)) AS m;
|
||||
|
||||
h:i | m:i
|
||||
6 | 52
|
||||
;
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.proto;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Duration;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@ -26,7 +27,7 @@ public final class StringUtils {
|
||||
|
||||
public static final String EMPTY = "";
|
||||
|
||||
private static final DateTimeFormatter ISO_WITH_MILLIS = new DateTimeFormatterBuilder()
|
||||
public static final DateTimeFormatter ISO_DATE_WITH_MILLIS = new DateTimeFormatterBuilder()
|
||||
.parseCaseInsensitive()
|
||||
.append(ISO_LOCAL_DATE)
|
||||
.appendLiteral('T')
|
||||
@ -39,6 +40,17 @@ public final class StringUtils {
|
||||
.appendOffsetId()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
public static final DateTimeFormatter ISO_TIME_WITH_MILLIS = new DateTimeFormatterBuilder()
|
||||
.parseCaseInsensitive()
|
||||
.appendValue(HOUR_OF_DAY, 2)
|
||||
.appendLiteral(':')
|
||||
.appendValue(MINUTE_OF_HOUR, 2)
|
||||
.appendLiteral(':')
|
||||
.appendValue(SECOND_OF_MINUTE, 2)
|
||||
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
|
||||
.appendOffsetId()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
private static final int SECONDS_PER_MINUTE = 60;
|
||||
private static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * 60;
|
||||
private static final int SECONDS_PER_DAY = SECONDS_PER_HOUR * 24;
|
||||
@ -49,16 +61,18 @@ public final class StringUtils {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
|
||||
if (value instanceof ZonedDateTime) {
|
||||
return ((ZonedDateTime) value).format(ISO_DATE_WITH_MILLIS);
|
||||
}
|
||||
if (value instanceof OffsetTime) {
|
||||
return ((OffsetTime) value).format(ISO_TIME_WITH_MILLIS);
|
||||
}
|
||||
if (value instanceof Timestamp) {
|
||||
Timestamp ts = (Timestamp) value;
|
||||
return ts.toInstant().toString();
|
||||
}
|
||||
|
||||
if (value instanceof ZonedDateTime) {
|
||||
return ((ZonedDateTime) value).format(ISO_WITH_MILLIS);
|
||||
}
|
||||
|
||||
// handle intervals
|
||||
// YEAR/MONTH/YEAR TO MONTH -> YEAR TO MONTH
|
||||
if (value instanceof Period) {
|
||||
@ -112,4 +126,4 @@ public final class StringUtils {
|
||||
private static String indent(long timeUnit) {
|
||||
return timeUnit < 10 ? "0" + timeUnit : Long.toString(timeUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -298,7 +298,8 @@ public final class Verifier {
|
||||
return checkGroupByInexactField(p, localFailures)
|
||||
&& checkGroupByAgg(p, localFailures, resolvedFunctions)
|
||||
&& checkGroupByOrder(p, localFailures, groupingFailures)
|
||||
&& checkGroupByHaving(p, localFailures, groupingFailures, resolvedFunctions);
|
||||
&& checkGroupByHaving(p, localFailures, groupingFailures, resolvedFunctions)
|
||||
&& checkGroupByTime(p, localFailures);
|
||||
}
|
||||
|
||||
// check whether an orderBy failed or if it occurs on a non-key
|
||||
@ -473,14 +474,30 @@ public final class Verifier {
|
||||
a.groupings().forEach(e -> e.forEachUp(c -> {
|
||||
EsField.Exact exact = c.getExactInfo();
|
||||
if (exact.hasExact() == false) {
|
||||
localFailures.add(fail(c, "Field of data type [" + c.dataType().typeName + "] cannot be used for grouping; " +
|
||||
exact.errorMsg()));
|
||||
localFailures.add(fail(c, "Field [" + c.sourceText() + "] of data type [" + c.dataType().typeName + "] " +
|
||||
"cannot be used for grouping; " + exact.errorMsg()));
|
||||
}
|
||||
}, FieldAttribute.class));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkGroupByTime(LogicalPlan p, Set<Failure> localFailures) {
|
||||
if (p instanceof Aggregate) {
|
||||
Aggregate a = (Aggregate) p;
|
||||
|
||||
// TIME data type is not allowed for grouping key
|
||||
// https://github.com/elastic/elasticsearch/issues/40639
|
||||
a.groupings().forEach(f -> {
|
||||
if (f.dataType().isTimeBased()) {
|
||||
localFailures.add(fail(f, "Function [" + f.sourceText() + "] with data type [" + f.dataType().typeName +
|
||||
"] " + "cannot be used for grouping"));
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// check whether plain columns specified in an agg are mentioned in the group-by
|
||||
private static boolean checkGroupByAgg(LogicalPlan p, Set<Failure> localFailures, Map<String, Function> functions) {
|
||||
if (p instanceof Aggregate) {
|
||||
|
@ -75,6 +75,8 @@ public class TopHitsAggExtractor implements BucketExtractor {
|
||||
Object value = agg.getHits().getAt(0).getFields().values().iterator().next().getValue();
|
||||
if (fieldDataType.isDateBased()) {
|
||||
return DateUtils.asDateTime(Long.parseLong(value.toString()), zoneId);
|
||||
} else if (fieldDataType.isTimeBased()) {
|
||||
return DateUtils.asTimeOnly(Long.parseLong(value.toString()), zoneId);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
@ -43,8 +43,18 @@ public final class TypeResolutions {
|
||||
return isType(e, DataType::isDateBased, operationName, paramOrd, "date", "datetime");
|
||||
}
|
||||
|
||||
public static TypeResolution isDateOrTime(Expression e, String operationName, ParamOrdinal paramOrd) {
|
||||
return isType(e, DataType::isDateOrTimeBased, operationName, paramOrd, "date", "time", "datetime");
|
||||
}
|
||||
|
||||
public static TypeResolution isNumericOrDate(Expression e, String operationName, ParamOrdinal paramOrd) {
|
||||
return isType(e, dt -> dt.isNumeric() || dt.isDateBased(), operationName, paramOrd, "date", "datetime", "numeric");
|
||||
return isType(e, dt -> dt.isNumeric() || dt.isDateBased(), operationName, paramOrd,
|
||||
"date", "datetime", "numeric");
|
||||
}
|
||||
|
||||
public static TypeResolution isNumericOrDateOrTime(Expression e, String operationName, ParamOrdinal paramOrd) {
|
||||
return isType(e, dt -> dt.isNumeric() || dt.isDateOrTimeBased(), operationName, paramOrd,
|
||||
"date", "time", "datetime", "numeric");
|
||||
}
|
||||
|
||||
public static TypeResolution isExact(Expression e, String message) {
|
||||
|
4
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Max.java
4
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Max.java
@ -14,7 +14,7 @@ import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isExact;
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isNumericOrDate;
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isNumericOrDateOrTime;
|
||||
|
||||
/**
|
||||
* Find the maximum value in matching documents.
|
||||
@ -50,7 +50,7 @@ public class Max extends NumericAggregate implements EnclosedAgg {
|
||||
if (field().dataType().isString()) {
|
||||
return isExact(field(), sourceText(), ParamOrdinal.DEFAULT);
|
||||
} else {
|
||||
return isNumericOrDate(field(), sourceText(), ParamOrdinal.DEFAULT);
|
||||
return isNumericOrDateOrTime(field(), sourceText(), ParamOrdinal.DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Min.java
4
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Min.java
@ -14,7 +14,7 @@ import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isExact;
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isNumericOrDate;
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isNumericOrDateOrTime;
|
||||
|
||||
/**
|
||||
* Find the minimum value in matched documents.
|
||||
@ -53,7 +53,7 @@ public class Min extends NumericAggregate implements EnclosedAgg {
|
||||
if (field().dataType().isString()) {
|
||||
return isExact(field(), sourceText(), ParamOrdinal.DEFAULT);
|
||||
} else {
|
||||
return isNumericOrDate(field(), sourceText(), ParamOrdinal.DEFAULT);
|
||||
return isNumericOrDateOrTime(field(), sourceText(), ParamOrdinal.DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeP
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NamedDateTimeProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NonIsoDateTimeProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.QuarterProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.TimeProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryOptionalMathProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor;
|
||||
@ -78,6 +79,7 @@ public final class Processors {
|
||||
|
||||
// datetime
|
||||
entries.add(new Entry(Processor.class, DateTimeProcessor.NAME, DateTimeProcessor::new));
|
||||
entries.add(new Entry(Processor.class, TimeProcessor.NAME, TimeProcessor::new));
|
||||
entries.add(new Entry(Processor.class, NamedDateTimeProcessor.NAME, NamedDateTimeProcessor::new));
|
||||
entries.add(new Entry(Processor.class, NonIsoDateTimeProcessor.NAME, NonIsoDateTimeProcessor::new));
|
||||
entries.add(new Entry(Processor.class, QuarterProcessor.NAME, QuarterProcessor::new));
|
||||
|
@ -13,13 +13,12 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isDate;
|
||||
|
||||
abstract class BaseDateTimeFunction extends UnaryScalarFunction {
|
||||
|
||||
|
||||
private final ZoneId zoneId;
|
||||
|
||||
BaseDateTimeFunction(Source source, Expression field, ZoneId zoneId) {
|
||||
@ -50,17 +49,9 @@ abstract class BaseDateTimeFunction extends UnaryScalarFunction {
|
||||
|
||||
@Override
|
||||
public Object fold() {
|
||||
ZonedDateTime folded = (ZonedDateTime) field().fold();
|
||||
if (folded == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return doFold(folded.withZoneSameInstant(zoneId));
|
||||
return makeProcessor().process(field().fold());
|
||||
}
|
||||
|
||||
protected abstract Object doFold(ZonedDateTime dateTime);
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || obj.getClass() != getClass()) {
|
||||
@ -68,7 +59,7 @@ abstract class BaseDateTimeFunction extends UnaryScalarFunction {
|
||||
}
|
||||
BaseDateTimeFunction other = (BaseDateTimeFunction) obj;
|
||||
return Objects.equals(other.field(), field())
|
||||
&& Objects.equals(other.zoneId(), zoneId());
|
||||
&& Objects.equals(other.zoneId(), zoneId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -18,11 +18,11 @@ import java.time.ZonedDateTime;
|
||||
public abstract class BaseDateTimeProcessor implements Processor {
|
||||
|
||||
private final ZoneId zoneId;
|
||||
|
||||
|
||||
BaseDateTimeProcessor(ZoneId zoneId) {
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
|
||||
BaseDateTimeProcessor(StreamInput in) throws IOException {
|
||||
zoneId = ZoneId.of(in.readString());
|
||||
}
|
||||
@ -31,7 +31,7 @@ public abstract class BaseDateTimeProcessor implements Processor {
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeString(zoneId.getId());
|
||||
}
|
||||
|
||||
|
||||
ZoneId zoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
@ -43,11 +43,11 @@ public abstract class BaseDateTimeProcessor implements Processor {
|
||||
}
|
||||
|
||||
if (!(input instanceof ZonedDateTime)) {
|
||||
throw new SqlIllegalArgumentException("A date is required; received {}", input);
|
||||
throw new SqlIllegalArgumentException("A [date], a [time] or a [datetime] is required; received {}", input);
|
||||
}
|
||||
|
||||
return doProcess(((ZonedDateTime) input).withZoneSameInstant(zoneId));
|
||||
}
|
||||
|
||||
abstract Object doProcess(ZonedDateTime dateTime);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.Temporal;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
|
||||
|
||||
@ -28,17 +29,12 @@ public abstract class DateTimeFunction extends BaseDateTimeFunction {
|
||||
this.extractor = extractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doFold(ZonedDateTime dateTime) {
|
||||
return dateTimeChrono(dateTime, extractor.chronoField());
|
||||
}
|
||||
|
||||
public static Integer dateTimeChrono(ZonedDateTime dateTime, String tzId, String chronoName) {
|
||||
ZonedDateTime zdt = dateTime.withZoneSameInstant(ZoneId.of(tzId));
|
||||
return dateTimeChrono(zdt, ChronoField.valueOf(chronoName));
|
||||
}
|
||||
|
||||
private static Integer dateTimeChrono(ZonedDateTime dateTime, ChronoField field) {
|
||||
protected static Integer dateTimeChrono(Temporal dateTime, ChronoField field) {
|
||||
return Integer.valueOf(dateTime.get(field));
|
||||
}
|
||||
|
||||
@ -68,4 +64,8 @@ public abstract class DateTimeFunction extends BaseDateTimeFunction {
|
||||
|
||||
// used for applying ranges
|
||||
public abstract String dateTimeFormat();
|
||||
}
|
||||
|
||||
protected DateTimeExtractor extractor() {
|
||||
return extractor;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
@ -38,6 +39,10 @@ public class DateTimeProcessor extends BaseDateTimeProcessor {
|
||||
return dt.get(field);
|
||||
}
|
||||
|
||||
public int extract(OffsetTime time) {
|
||||
return time.get(field);
|
||||
}
|
||||
|
||||
public ChronoField chronoField() {
|
||||
return field;
|
||||
}
|
||||
@ -95,4 +100,4 @@ public class DateTimeProcessor extends BaseDateTimeProcessor {
|
||||
public String toString() {
|
||||
return extractor.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,15 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
|
||||
|
||||
import org.elasticsearch.xpack.sql.expression.Expression;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeProcessor.DateTimeExtractor;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo.NodeCtor2;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.ZoneId;
|
||||
|
||||
/**
|
||||
* Extract the hour of the day from a datetime.
|
||||
*/
|
||||
public class HourOfDay extends DateTimeFunction {
|
||||
public class HourOfDay extends TimeFunction {
|
||||
public HourOfDay(Source source, Expression field, ZoneId zoneId) {
|
||||
super(source, field, zoneId, DateTimeExtractor.HOUR_OF_DAY);
|
||||
}
|
||||
|
@ -7,15 +7,15 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
|
||||
|
||||
import org.elasticsearch.xpack.sql.expression.Expression;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeProcessor.DateTimeExtractor;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo.NodeCtor2;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.ZoneId;
|
||||
|
||||
/**
|
||||
* Extract the minute of the day from a datetime.
|
||||
*/
|
||||
public class MinuteOfDay extends DateTimeFunction {
|
||||
public class MinuteOfDay extends TimeFunction {
|
||||
|
||||
public MinuteOfDay(Source source, Expression field, ZoneId zoneId) {
|
||||
super(source, field, zoneId, DateTimeExtractor.MINUTE_OF_DAY);
|
||||
|
@ -7,15 +7,15 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
|
||||
|
||||
import org.elasticsearch.xpack.sql.expression.Expression;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeProcessor.DateTimeExtractor;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo.NodeCtor2;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.ZoneId;
|
||||
|
||||
/**
|
||||
* Exract the minute of the hour from a datetime.
|
||||
*/
|
||||
public class MinuteOfHour extends DateTimeFunction {
|
||||
public class MinuteOfHour extends TimeFunction {
|
||||
public MinuteOfHour(Source source, Expression field, ZoneId zoneId) {
|
||||
super(source, field, zoneId, DateTimeExtractor.MINUTE_OF_HOUR);
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import org.elasticsearch.xpack.sql.util.StringUtils;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Locale;
|
||||
|
||||
import static java.lang.String.format;
|
||||
@ -33,11 +32,6 @@ abstract class NamedDateTimeFunction extends BaseDateTimeFunction {
|
||||
this.nameExtractor = nameExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doFold(ZonedDateTime dateTime) {
|
||||
return nameExtractor.extract(dateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptTemplate scriptWithField(FieldAttribute field) {
|
||||
return new ScriptTemplate(
|
||||
@ -58,4 +52,4 @@ abstract class NamedDateTimeFunction extends BaseDateTimeFunction {
|
||||
public DataType dataType() {
|
||||
return DataType.KEYWORD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import org.elasticsearch.xpack.sql.util.StringUtils;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Locale;
|
||||
|
||||
import static java.lang.String.format;
|
||||
@ -33,11 +32,6 @@ abstract class NonIsoDateTimeFunction extends BaseDateTimeFunction {
|
||||
this.extractor = extractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doFold(ZonedDateTime dateTime) {
|
||||
return extractor.extract(dateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptTemplate scriptWithField(FieldAttribute field) {
|
||||
return new ScriptTemplate(
|
||||
@ -58,4 +52,4 @@ abstract class NonIsoDateTimeFunction extends BaseDateTimeFunction {
|
||||
public DataType dataType() {
|
||||
return DataType.INTEGER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,14 +10,12 @@ import org.elasticsearch.xpack.sql.expression.Expression;
|
||||
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo.NodeCtor2;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.type.DataType;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.QuarterProcessor.quarter;
|
||||
import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
|
||||
|
||||
public class Quarter extends BaseDateTimeFunction {
|
||||
@ -26,11 +24,6 @@ public class Quarter extends BaseDateTimeFunction {
|
||||
super(source, field, zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doFold(ZonedDateTime dateTime) {
|
||||
return quarter(dateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptTemplate scriptWithField(FieldAttribute field) {
|
||||
return new ScriptTemplate(formatTemplate("{sql}.quarter(doc[{}].value, {})"),
|
||||
@ -60,4 +53,4 @@ public class Quarter extends BaseDateTimeFunction {
|
||||
public DataType dataType() {
|
||||
return DataType.INTEGER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,15 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
|
||||
|
||||
import org.elasticsearch.xpack.sql.expression.Expression;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeProcessor.DateTimeExtractor;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo.NodeCtor2;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.ZoneId;
|
||||
|
||||
/**
|
||||
* Extract the second of the minute from a datetime.
|
||||
*/
|
||||
public class SecondOfMinute extends DateTimeFunction {
|
||||
public class SecondOfMinute extends TimeFunction {
|
||||
public SecondOfMinute(Source source, Expression field, ZoneId zoneId) {
|
||||
super(source, field, zoneId, DateTimeExtractor.SECOND_OF_MINUTE);
|
||||
}
|
||||
|
40
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/TimeFunction.java
Normal file
40
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/TimeFunction.java
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
|
||||
|
||||
import org.elasticsearch.xpack.sql.expression.Expression;
|
||||
import org.elasticsearch.xpack.sql.expression.Expressions;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeProcessor.DateTimeExtractor;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoField;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isDateOrTime;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.asTimeAtZone;
|
||||
|
||||
public abstract class TimeFunction extends DateTimeFunction {
|
||||
|
||||
TimeFunction(Source source, Expression field, ZoneId zoneId, DateTimeExtractor extractor) {
|
||||
super(source, field, zoneId, extractor);
|
||||
}
|
||||
|
||||
public static Integer dateTimeChrono(OffsetTime time, String tzId, String chronoName) {
|
||||
return dateTimeChrono(asTimeAtZone(time, ZoneId.of(tzId)), ChronoField.valueOf(chronoName));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TypeResolution resolveType() {
|
||||
return isDateOrTime(field(), sourceText(), Expressions.ParamOrdinal.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Processor makeProcessor() {
|
||||
return new TimeProcessor(extractor(), zoneId());
|
||||
}
|
||||
}
|
56
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/TimeProcessor.java
Normal file
56
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/TimeProcessor.java
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
|
||||
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.asTimeAtZone;
|
||||
|
||||
public class TimeProcessor extends DateTimeProcessor {
|
||||
|
||||
|
||||
public static final String NAME = "time";
|
||||
|
||||
public TimeProcessor(DateTimeExtractor extractor, ZoneId zoneId) {
|
||||
super(extractor, zoneId);
|
||||
}
|
||||
|
||||
public TimeProcessor(StreamInput in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object process(Object input) {
|
||||
if (input instanceof OffsetTime) {
|
||||
return doProcess(asTimeAtZone((OffsetTime) input, zoneId()));
|
||||
}
|
||||
return super.process(input);
|
||||
}
|
||||
|
||||
private Object doProcess(OffsetTime time) {
|
||||
return extractor().extract(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(extractor(), zoneId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
TimeProcessor other = (TimeProcessor) obj;
|
||||
return Objects.equals(extractor(), other.extractor())
|
||||
&& Objects.equals(zoneId(), other.zoneId());
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeF
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NamedDateTimeProcessor.NameExtractor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NonIsoDateTimeProcessor.NonIsoDateTimeExtractor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.QuarterProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.TimeFunction;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryOptionalMathProcessor.BinaryOptionalMathOperation;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation;
|
||||
@ -41,6 +42,7 @@ import org.elasticsearch.xpack.sql.util.DateUtils;
|
||||
import org.elasticsearch.xpack.sql.util.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
@ -316,6 +318,9 @@ public final class InternalSqlScriptUtils {
|
||||
if (dateTime == null || tzId == null || chronoName == null) {
|
||||
return null;
|
||||
}
|
||||
if (dateTime instanceof OffsetTime) {
|
||||
return TimeFunction.dateTimeChrono((OffsetTime) dateTime, tzId, chronoName);
|
||||
}
|
||||
return DateTimeFunction.dateTimeChrono(asDateTime(dateTime), tzId, chronoName);
|
||||
}
|
||||
|
||||
@ -396,6 +401,10 @@ public final class InternalSqlScriptUtils {
|
||||
return new IntervalYearMonth(Period.parse(text), DataType.fromTypeName(typeName));
|
||||
}
|
||||
|
||||
public static OffsetTime asTime(String time) {
|
||||
return OffsetTime.parse(time);
|
||||
}
|
||||
|
||||
//
|
||||
// String functions
|
||||
//
|
||||
|
10
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/gen/script/ScriptWeaver.java
10
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/gen/script/ScriptWeaver.java
@ -20,6 +20,7 @@ import org.elasticsearch.xpack.sql.expression.literal.IntervalYearMonth;
|
||||
import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import org.elasticsearch.xpack.sql.util.DateUtils;
|
||||
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
|
||||
@ -80,12 +81,19 @@ public interface ScriptWeaver {
|
||||
return new ScriptTemplate(processScript("{sql}.intervalYearMonth({},{})"),
|
||||
paramsBuilder().variable(iym.interval().toString()).variable(iym.dataType().name()).build(),
|
||||
dataType());
|
||||
} else if (fold instanceof IntervalDayTime) {
|
||||
}
|
||||
if (fold instanceof IntervalDayTime) {
|
||||
IntervalDayTime idt = (IntervalDayTime) fold;
|
||||
return new ScriptTemplate(processScript("{sql}.intervalDayTime({},{})"),
|
||||
paramsBuilder().variable(idt.interval().toString()).variable(idt.dataType().name()).build(),
|
||||
dataType());
|
||||
}
|
||||
if (fold instanceof OffsetTime) {
|
||||
OffsetTime ot = (OffsetTime) fold;
|
||||
return new ScriptTemplate(processScript("{sql}.asTime({})"),
|
||||
paramsBuilder().variable(ot.toString()).build(),
|
||||
dataType());
|
||||
}
|
||||
|
||||
return new ScriptTemplate(processScript("{}"),
|
||||
paramsBuilder().variable(fold).build(),
|
||||
|
@ -6,14 +6,24 @@
|
||||
package org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.Temporal;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.DAY_IN_MILLIS;
|
||||
|
||||
/**
|
||||
* Arithmetic operation using the type widening rules of the JLS 5.6.2 namely
|
||||
* widen to double or float or long or int in this order.
|
||||
*/
|
||||
public abstract class Arithmetics {
|
||||
public final class Arithmetics {
|
||||
|
||||
private Arithmetics() {}
|
||||
|
||||
private enum IntervalOperation {
|
||||
ADD,
|
||||
SUB
|
||||
}
|
||||
|
||||
static Number add(Number l, Number r) {
|
||||
if (l == null || r == null) {
|
||||
@ -33,20 +43,12 @@ public abstract class Arithmetics {
|
||||
return Integer.valueOf(Math.addExact(l.intValue(), r.intValue()));
|
||||
}
|
||||
|
||||
static ZonedDateTime add(ZonedDateTime l, Period r) {
|
||||
if (l == null || r == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return l.plus(r);
|
||||
static Temporal add(Temporal l, Period r) {
|
||||
return periodArithmetics(l, r, IntervalOperation.ADD);
|
||||
}
|
||||
|
||||
static ZonedDateTime add(ZonedDateTime l, Duration r) {
|
||||
if (l == null || r == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return l.plus(r);
|
||||
static Temporal add(Temporal l, Duration r) {
|
||||
return durationArithmetics(l, r, IntervalOperation.ADD);
|
||||
}
|
||||
|
||||
static Number sub(Number l, Number r) {
|
||||
@ -67,20 +69,12 @@ public abstract class Arithmetics {
|
||||
return Integer.valueOf(Math.subtractExact(l.intValue(), r.intValue()));
|
||||
}
|
||||
|
||||
static ZonedDateTime sub(ZonedDateTime l, Period r) {
|
||||
if (l == null || r == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return l.minus(r);
|
||||
static Temporal sub(Temporal l, Period r) {
|
||||
return periodArithmetics(l, r, IntervalOperation.SUB);
|
||||
}
|
||||
|
||||
static ZonedDateTime sub(ZonedDateTime l, Duration r) {
|
||||
if (l == null || r == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return l.minus(r);
|
||||
static Temporal sub(Temporal l, Duration r) {
|
||||
return durationArithmetics(l, r, IntervalOperation.SUB);
|
||||
}
|
||||
|
||||
static Number mul(Number l, Number r) {
|
||||
@ -162,4 +156,36 @@ public abstract class Arithmetics {
|
||||
|
||||
return Integer.valueOf(Math.negateExact(n.intValue()));
|
||||
}
|
||||
|
||||
private static Temporal periodArithmetics(Temporal l, Period r, IntervalOperation operation) {
|
||||
if (l == null || r == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (l instanceof OffsetTime) {
|
||||
return l;
|
||||
}
|
||||
|
||||
if (operation == IntervalOperation.ADD) {
|
||||
return l.plus(r);
|
||||
} else {
|
||||
return l.minus(r);
|
||||
}
|
||||
}
|
||||
|
||||
private static Temporal durationArithmetics(Temporal l, Duration r, IntervalOperation operation) {
|
||||
if (l == null || r == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (l instanceof OffsetTime) {
|
||||
r = Duration.ofMillis(r.toMillis() % DAY_IN_MILLIS);
|
||||
}
|
||||
|
||||
if (operation == IntervalOperation.ADD) {
|
||||
return l.plus(r);
|
||||
} else {
|
||||
return l.minus(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,9 @@ import org.elasticsearch.xpack.sql.expression.predicate.PredicateBiFunction;
|
||||
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class BinaryArithmeticProcessor extends FunctionalBinaryProcessor<Object, Object, Object, BinaryArithmeticOperation> {
|
||||
@ -41,17 +43,17 @@ public class BinaryArithmeticProcessor extends FunctionalBinaryProcessor<Object,
|
||||
}
|
||||
l = unwrapJodaTime(l);
|
||||
r = unwrapJodaTime(r);
|
||||
if (l instanceof ZonedDateTime && r instanceof IntervalYearMonth) {
|
||||
return Arithmetics.add((ZonedDateTime) l, ((IntervalYearMonth) r).interval());
|
||||
if ((l instanceof ZonedDateTime || l instanceof OffsetTime) && r instanceof IntervalYearMonth) {
|
||||
return Arithmetics.add((Temporal) l, ((IntervalYearMonth) r).interval());
|
||||
}
|
||||
if (l instanceof ZonedDateTime && r instanceof IntervalDayTime) {
|
||||
return Arithmetics.add((ZonedDateTime) l, ((IntervalDayTime) r).interval());
|
||||
if ((l instanceof ZonedDateTime || l instanceof OffsetTime) && r instanceof IntervalDayTime) {
|
||||
return Arithmetics.add((Temporal) l, ((IntervalDayTime) r).interval());
|
||||
}
|
||||
if (r instanceof ZonedDateTime && l instanceof IntervalYearMonth) {
|
||||
return Arithmetics.add((ZonedDateTime) r, ((IntervalYearMonth) l).interval());
|
||||
if ((r instanceof ZonedDateTime || r instanceof OffsetTime) && l instanceof IntervalYearMonth) {
|
||||
return Arithmetics.add((Temporal) r, ((IntervalYearMonth) l).interval());
|
||||
}
|
||||
if (r instanceof ZonedDateTime && l instanceof IntervalDayTime) {
|
||||
return Arithmetics.add((ZonedDateTime) r, ((IntervalDayTime) l).interval());
|
||||
if ((r instanceof ZonedDateTime || r instanceof OffsetTime) && l instanceof IntervalDayTime) {
|
||||
return Arithmetics.add((Temporal) r, ((IntervalDayTime) l).interval());
|
||||
}
|
||||
|
||||
throw new SqlIllegalArgumentException("Cannot compute [+] between [{}] [{}]", l.getClass().getSimpleName(),
|
||||
@ -69,13 +71,13 @@ public class BinaryArithmeticProcessor extends FunctionalBinaryProcessor<Object,
|
||||
}
|
||||
l = unwrapJodaTime(l);
|
||||
r = unwrapJodaTime(r);
|
||||
if (l instanceof ZonedDateTime && r instanceof IntervalYearMonth) {
|
||||
return Arithmetics.sub((ZonedDateTime) l, ((IntervalYearMonth) r).interval());
|
||||
if ((l instanceof ZonedDateTime || l instanceof OffsetTime) && r instanceof IntervalYearMonth) {
|
||||
return Arithmetics.sub((Temporal) l, ((IntervalYearMonth) r).interval());
|
||||
}
|
||||
if (l instanceof ZonedDateTime && r instanceof IntervalDayTime) {
|
||||
return Arithmetics.sub((ZonedDateTime) l, ((IntervalDayTime) r).interval());
|
||||
if ((l instanceof ZonedDateTime || l instanceof OffsetTime) && r instanceof IntervalDayTime) {
|
||||
return Arithmetics.sub((Temporal) l, ((IntervalDayTime) r).interval());
|
||||
}
|
||||
if (r instanceof ZonedDateTime && l instanceof Interval<?>) {
|
||||
if ((r instanceof ZonedDateTime || r instanceof OffsetTime) && l instanceof Interval<?>) {
|
||||
throw new SqlIllegalArgumentException("Cannot subtract a date from an interval; do you mean the reverse?");
|
||||
}
|
||||
|
||||
@ -181,4 +183,4 @@ public class BinaryArithmeticProcessor extends FunctionalBinaryProcessor<Object,
|
||||
// this should not occur
|
||||
throw new SqlIllegalArgumentException("Cannot perform arithmetic operation due to arguments");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class Sub extends DateTimeArithmeticOperation {
|
||||
|
||||
@Override
|
||||
protected TypeResolution resolveWithIntervals() {
|
||||
if (right().dataType().isDateBased() && DataTypes.isInterval(left().dataType())) {
|
||||
if ((right().dataType().isDateOrTimeBased()) && DataTypes.isInterval(left().dataType())) {
|
||||
return new TypeResolution(format(null, "Cannot subtract a {}[{}] from an interval[{}]; do you mean the reverse?",
|
||||
right().dataType().typeName, right().source().text(), left().source().text()));
|
||||
}
|
||||
|
@ -114,7 +114,6 @@ import org.elasticsearch.xpack.sql.type.DataTypes;
|
||||
import org.elasticsearch.xpack.sql.util.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Period;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
@ -124,11 +123,11 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.elasticsearch.xpack.sql.type.DataTypeConversion.conversionFor;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.asDateOnly;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.asTimeOnly;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.ofEscapedLiteral;
|
||||
|
||||
abstract class ExpressionBuilder extends IdentifierBuilder {
|
||||
@ -768,14 +767,11 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
|
||||
Source source = source(ctx);
|
||||
|
||||
// parse HH:mm:ss
|
||||
LocalTime lt = null;
|
||||
try {
|
||||
lt = LocalTime.parse(string, ISO_LOCAL_TIME);
|
||||
return new Literal(source, asTimeOnly(string), DataType.TIME);
|
||||
} catch (DateTimeParseException ex) {
|
||||
throw new ParsingException(source, "Invalid time received; {}", ex.getMessage());
|
||||
}
|
||||
|
||||
throw new SqlIllegalArgumentException("Time (only) literals are not supported; a date component is required as well");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -291,7 +291,7 @@ final class QueryTranslator {
|
||||
if (h.dataType() == DATE) {
|
||||
intervalAsMillis = DateUtils.minDayInterval(intervalAsMillis);
|
||||
}
|
||||
// TODO: set timezone
|
||||
|
||||
if (field instanceof FieldAttribute) {
|
||||
key = new GroupByDateHistogram(aggId, nameOf(field), intervalAsMillis, h.zoneId());
|
||||
} else if (field instanceof Function) {
|
||||
|
@ -41,6 +41,8 @@ public abstract class GroupByKey extends Agg {
|
||||
builder.valueType(ValueType.STRING);
|
||||
} else if (script.outputType() == DataType.DATE) {
|
||||
builder.valueType(ValueType.LONG);
|
||||
} else if (script.outputType() == DataType.TIME) {
|
||||
builder.valueType(ValueType.LONG);
|
||||
} else if (script.outputType() == DataType.DATETIME) {
|
||||
builder.valueType(ValueType.LONG);
|
||||
} else if (script.outputType() == DataType.BOOLEAN) {
|
||||
|
@ -45,6 +45,7 @@ public enum DataType {
|
||||
NESTED( "nested", JDBCType.STRUCT, -1, 0, 0, false, false, false),
|
||||
BINARY( "binary", JDBCType.VARBINARY, -1, Integer.MAX_VALUE, Integer.MAX_VALUE, false, false, false),
|
||||
DATE( JDBCType.DATE, Long.BYTES, 24, 24, false, false, true),
|
||||
TIME( JDBCType.TIME, Long.BYTES, 3, 18, false, false, true),
|
||||
// since ODBC and JDBC interpret precision for Date as display size
|
||||
// the precision is 23 (number of chars in ISO8601 with millis) + Z (the UTC timezone)
|
||||
// see https://github.com/elastic/elasticsearch/issues/30386#issuecomment-386807288
|
||||
@ -104,7 +105,7 @@ public enum DataType {
|
||||
|
||||
// Date
|
||||
ODBC_TO_ES.put("SQL_DATE", DATE);
|
||||
ODBC_TO_ES.put("SQL_TIME", DATETIME);
|
||||
ODBC_TO_ES.put("SQL_TIME", TIME);
|
||||
ODBC_TO_ES.put("SQL_TIMESTAMP", DATETIME);
|
||||
|
||||
// Intervals
|
||||
@ -253,6 +254,14 @@ public enum DataType {
|
||||
public boolean isDateBased() {
|
||||
return this == DATE || this == DATETIME;
|
||||
}
|
||||
|
||||
public boolean isTimeBased() {
|
||||
return this == TIME;
|
||||
}
|
||||
|
||||
public boolean isDateOrTimeBased() {
|
||||
return isDateBased() || isTimeBased();
|
||||
}
|
||||
|
||||
public static DataType fromOdbcType(String odbcType) {
|
||||
return ODBC_TO_ES.get(odbcType);
|
||||
@ -278,6 +287,6 @@ public enum DataType {
|
||||
}
|
||||
|
||||
public String format() {
|
||||
return isDateBased() ? DateUtils.DATE_PARSE_FORMAT : null;
|
||||
return isDateOrTimeBased() ? DateUtils.DATE_PARSE_FORMAT : null;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import org.elasticsearch.common.network.InetAddresses;
|
||||
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
||||
import org.elasticsearch.xpack.sql.util.DateUtils;
|
||||
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Locale;
|
||||
@ -22,6 +23,7 @@ import static org.elasticsearch.xpack.sql.type.DataType.DATE;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.DATETIME;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.LONG;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.NULL;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.TIME;
|
||||
|
||||
/**
|
||||
* Conversions from one Elasticsearch data type to another Elasticsearch data types.
|
||||
@ -87,8 +89,24 @@ public abstract class DataTypeConversion {
|
||||
return right;
|
||||
}
|
||||
}
|
||||
if (left == DATETIME) {
|
||||
if (left == TIME) {
|
||||
if (right == DATE) {
|
||||
return DATETIME;
|
||||
}
|
||||
if (DataTypes.isInterval(right)) {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
if (right == TIME) {
|
||||
if (left == DATE) {
|
||||
return DATETIME;
|
||||
}
|
||||
if (DataTypes.isInterval(left)) {
|
||||
return right;
|
||||
}
|
||||
}
|
||||
if (left == DATETIME) {
|
||||
if (right == DATE || right == TIME) {
|
||||
return left;
|
||||
}
|
||||
if (DataTypes.isInterval(right)) {
|
||||
@ -96,7 +114,7 @@ public abstract class DataTypeConversion {
|
||||
}
|
||||
}
|
||||
if (right == DATETIME) {
|
||||
if (left == DATE) {
|
||||
if (left == DATE || left == TIME) {
|
||||
return right;
|
||||
}
|
||||
if (DataTypes.isInterval(left)) {
|
||||
@ -144,7 +162,7 @@ public abstract class DataTypeConversion {
|
||||
|
||||
Conversion conversion = conversion(from, to);
|
||||
if (conversion == null) {
|
||||
throw new SqlIllegalArgumentException("cannot convert from [" + from + "] to [" + to + "]");
|
||||
throw new SqlIllegalArgumentException("cannot convert from [" + from.typeName + "] to [" + to.typeName + "]");
|
||||
}
|
||||
return conversion;
|
||||
}
|
||||
@ -170,6 +188,8 @@ public abstract class DataTypeConversion {
|
||||
return conversionToDouble(from);
|
||||
case DATE:
|
||||
return conversionToDate(from);
|
||||
case TIME:
|
||||
return conversionToTime(from);
|
||||
case DATETIME:
|
||||
return conversionToDateTime(from);
|
||||
case BOOLEAN:
|
||||
@ -184,6 +204,9 @@ public abstract class DataTypeConversion {
|
||||
if (from == DATE) {
|
||||
return Conversion.DATE_TO_STRING;
|
||||
}
|
||||
if (from == TIME) {
|
||||
return Conversion.TIME_TO_STRING;
|
||||
}
|
||||
if (from == DATETIME) {
|
||||
return Conversion.DATETIME_TO_STRING;
|
||||
}
|
||||
@ -213,6 +236,9 @@ public abstract class DataTypeConversion {
|
||||
if (from == DATE) {
|
||||
return Conversion.DATE_TO_LONG;
|
||||
}
|
||||
if (from == TIME) {
|
||||
return Conversion.TIME_TO_LONG;
|
||||
}
|
||||
if (from == DATETIME) {
|
||||
return Conversion.DATETIME_TO_LONG;
|
||||
}
|
||||
@ -235,6 +261,9 @@ public abstract class DataTypeConversion {
|
||||
if (from == DATE) {
|
||||
return Conversion.DATE_TO_INT;
|
||||
}
|
||||
if (from == TIME) {
|
||||
return Conversion.TIME_TO_INT;
|
||||
}
|
||||
if (from == DATETIME) {
|
||||
return Conversion.DATETIME_TO_INT;
|
||||
}
|
||||
@ -257,6 +286,9 @@ public abstract class DataTypeConversion {
|
||||
if (from == DATE) {
|
||||
return Conversion.DATE_TO_SHORT;
|
||||
}
|
||||
if (from == TIME) {
|
||||
return Conversion.TIME_TO_SHORT;
|
||||
}
|
||||
if (from == DATETIME) {
|
||||
return Conversion.DATETIME_TO_SHORT;
|
||||
}
|
||||
@ -279,6 +311,9 @@ public abstract class DataTypeConversion {
|
||||
if (from == DATE) {
|
||||
return Conversion.DATE_TO_BYTE;
|
||||
}
|
||||
if (from == TIME) {
|
||||
return Conversion.TIME_TO_BYTE;
|
||||
}
|
||||
if (from == DATETIME) {
|
||||
return Conversion.DATETIME_TO_BYTE;
|
||||
}
|
||||
@ -301,6 +336,9 @@ public abstract class DataTypeConversion {
|
||||
if (from == DATE) {
|
||||
return Conversion.DATE_TO_FLOAT;
|
||||
}
|
||||
if (from == TIME) {
|
||||
return Conversion.TIME_TO_FLOAT;
|
||||
}
|
||||
if (from == DATETIME) {
|
||||
return Conversion.DATETIME_TO_FLOAT;
|
||||
}
|
||||
@ -323,6 +361,9 @@ public abstract class DataTypeConversion {
|
||||
if (from == DATE) {
|
||||
return Conversion.DATE_TO_DOUBLE;
|
||||
}
|
||||
if (from == TIME) {
|
||||
return Conversion.TIME_TO_DOUBLE;
|
||||
}
|
||||
if (from == DATETIME) {
|
||||
return Conversion.DATETIME_TO_DOUBLE;
|
||||
}
|
||||
@ -348,6 +389,28 @@ public abstract class DataTypeConversion {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Conversion conversionToTime(DataType from) {
|
||||
if (from.isRational()) {
|
||||
return Conversion.RATIONAL_TO_TIME;
|
||||
}
|
||||
if (from.isInteger()) {
|
||||
return Conversion.INTEGER_TO_TIME;
|
||||
}
|
||||
if (from == BOOLEAN) {
|
||||
return Conversion.BOOL_TO_TIME; // We emit an int here which is ok because of Java's casting rules
|
||||
}
|
||||
if (from.isString()) {
|
||||
return Conversion.STRING_TO_TIME;
|
||||
}
|
||||
if (from == DATE) {
|
||||
return Conversion.DATE_TO_TIME;
|
||||
}
|
||||
if (from == DATETIME) {
|
||||
return Conversion.DATETIME_TO_TIME;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Conversion conversionToDateTime(DataType from) {
|
||||
if (from.isRational()) {
|
||||
return Conversion.RATIONAL_TO_DATETIME;
|
||||
@ -377,6 +440,9 @@ public abstract class DataTypeConversion {
|
||||
if (from == DATE) {
|
||||
return Conversion.DATE_TO_BOOLEAN;
|
||||
}
|
||||
if (from == TIME) {
|
||||
return Conversion.TIME_TO_BOOLEAN;
|
||||
}
|
||||
if (from == DATETIME) {
|
||||
return Conversion.DATETIME_TO_BOOLEAN;
|
||||
}
|
||||
@ -456,6 +522,7 @@ public abstract class DataTypeConversion {
|
||||
NULL(value -> null),
|
||||
|
||||
DATE_TO_STRING(o -> DateUtils.toDateString((ZonedDateTime) o)),
|
||||
TIME_TO_STRING(o -> DateUtils.toTimeString((OffsetTime) o)),
|
||||
DATETIME_TO_STRING(o -> DateUtils.toString((ZonedDateTime) o)),
|
||||
OTHER_TO_STRING(String::valueOf),
|
||||
|
||||
@ -463,6 +530,7 @@ public abstract class DataTypeConversion {
|
||||
INTEGER_TO_LONG(fromLong(value -> value)),
|
||||
STRING_TO_LONG(fromString(Long::valueOf, "long")),
|
||||
DATE_TO_LONG(fromDateTime(value -> value)),
|
||||
TIME_TO_LONG(fromTime(value -> value)),
|
||||
DATETIME_TO_LONG(fromDateTime(value -> value)),
|
||||
|
||||
RATIONAL_TO_INT(fromDouble(value -> safeToInt(safeToLong(value)))),
|
||||
@ -470,6 +538,7 @@ public abstract class DataTypeConversion {
|
||||
BOOL_TO_INT(fromBool(value -> value ? 1 : 0)),
|
||||
STRING_TO_INT(fromString(Integer::valueOf, "integer")),
|
||||
DATE_TO_INT(fromDateTime(DataTypeConversion::safeToInt)),
|
||||
TIME_TO_INT(fromTime(DataTypeConversion::safeToInt)),
|
||||
DATETIME_TO_INT(fromDateTime(DataTypeConversion::safeToInt)),
|
||||
|
||||
RATIONAL_TO_SHORT(fromDouble(value -> safeToShort(safeToLong(value)))),
|
||||
@ -477,6 +546,7 @@ public abstract class DataTypeConversion {
|
||||
BOOL_TO_SHORT(fromBool(value -> value ? (short) 1 : (short) 0)),
|
||||
STRING_TO_SHORT(fromString(Short::valueOf, "short")),
|
||||
DATE_TO_SHORT(fromDateTime(DataTypeConversion::safeToShort)),
|
||||
TIME_TO_SHORT(fromTime(DataTypeConversion::safeToShort)),
|
||||
DATETIME_TO_SHORT(fromDateTime(DataTypeConversion::safeToShort)),
|
||||
|
||||
RATIONAL_TO_BYTE(fromDouble(value -> safeToByte(safeToLong(value)))),
|
||||
@ -484,6 +554,7 @@ public abstract class DataTypeConversion {
|
||||
BOOL_TO_BYTE(fromBool(value -> value ? (byte) 1 : (byte) 0)),
|
||||
STRING_TO_BYTE(fromString(Byte::valueOf, "byte")),
|
||||
DATE_TO_BYTE(fromDateTime(DataTypeConversion::safeToByte)),
|
||||
TIME_TO_BYTE(fromTime(DataTypeConversion::safeToByte)),
|
||||
DATETIME_TO_BYTE(fromDateTime(DataTypeConversion::safeToByte)),
|
||||
|
||||
// TODO floating point conversions are lossy but conversions to integer conversions are not. Are we ok with that?
|
||||
@ -492,6 +563,7 @@ public abstract class DataTypeConversion {
|
||||
BOOL_TO_FLOAT(fromBool(value -> value ? 1f : 0f)),
|
||||
STRING_TO_FLOAT(fromString(Float::valueOf, "float")),
|
||||
DATE_TO_FLOAT(fromDateTime(value -> (float) value)),
|
||||
TIME_TO_FLOAT(fromTime(value -> (float) value)),
|
||||
DATETIME_TO_FLOAT(fromDateTime(value -> (float) value)),
|
||||
|
||||
RATIONAL_TO_DOUBLE(fromDouble(Double::valueOf)),
|
||||
@ -499,6 +571,7 @@ public abstract class DataTypeConversion {
|
||||
BOOL_TO_DOUBLE(fromBool(value -> value ? 1d : 0d)),
|
||||
STRING_TO_DOUBLE(fromString(Double::valueOf, "double")),
|
||||
DATE_TO_DOUBLE(fromDateTime(Double::valueOf)),
|
||||
TIME_TO_DOUBLE(fromTime(Double::valueOf)),
|
||||
DATETIME_TO_DOUBLE(fromDateTime(Double::valueOf)),
|
||||
|
||||
RATIONAL_TO_DATE(toDate(RATIONAL_TO_LONG)),
|
||||
@ -507,6 +580,13 @@ public abstract class DataTypeConversion {
|
||||
STRING_TO_DATE(fromString(DateUtils::asDateOnly, "date")),
|
||||
DATETIME_TO_DATE(fromDatetimeToDate()),
|
||||
|
||||
RATIONAL_TO_TIME(toTime(RATIONAL_TO_LONG)),
|
||||
INTEGER_TO_TIME(toTime(INTEGER_TO_LONG)),
|
||||
BOOL_TO_TIME(toTime(BOOL_TO_INT)),
|
||||
STRING_TO_TIME(fromString(DateUtils::asTimeOnly, "time")),
|
||||
DATE_TO_TIME(fromDatetimeToTime()),
|
||||
DATETIME_TO_TIME(fromDatetimeToTime()),
|
||||
|
||||
RATIONAL_TO_DATETIME(toDateTime(RATIONAL_TO_LONG)),
|
||||
INTEGER_TO_DATETIME(toDateTime(INTEGER_TO_LONG)),
|
||||
BOOL_TO_DATETIME(toDateTime(BOOL_TO_INT)),
|
||||
@ -516,6 +596,7 @@ public abstract class DataTypeConversion {
|
||||
NUMERIC_TO_BOOLEAN(fromLong(value -> value != 0)),
|
||||
STRING_TO_BOOLEAN(fromString(DataTypeConversion::convertToBoolean, "boolean")),
|
||||
DATE_TO_BOOLEAN(fromDateTime(value -> value != 0)),
|
||||
TIME_TO_BOOLEAN(fromTime(value -> value != 0)),
|
||||
DATETIME_TO_BOOLEAN(fromDateTime(value -> value != 0)),
|
||||
|
||||
BOOL_TO_LONG(fromBool(value -> value ? 1L : 0L)),
|
||||
@ -557,22 +638,34 @@ public abstract class DataTypeConversion {
|
||||
return (Object l) -> converter.apply(((Boolean) l));
|
||||
}
|
||||
|
||||
private static Function<Object, Object> fromDateTime(Function<Long, Object> converter) {
|
||||
return l -> converter.apply(((ZonedDateTime) l).toInstant().toEpochMilli());
|
||||
private static Function<Object, Object> fromTime(Function<Long, Object> converter) {
|
||||
return l -> converter.apply(((OffsetTime) l).atDate(DateUtils.EPOCH).toInstant().toEpochMilli());
|
||||
}
|
||||
|
||||
private static Function<Object, Object> toDateTime(Conversion conversion) {
|
||||
return l -> DateUtils.asDateTime(((Number) conversion.convert(l)).longValue());
|
||||
private static Function<Object, Object> fromDateTime(Function<Long, Object> converter) {
|
||||
return l -> converter.apply(((ZonedDateTime) l).toInstant().toEpochMilli());
|
||||
}
|
||||
|
||||
private static Function<Object, Object> toDate(Conversion conversion) {
|
||||
return l -> DateUtils.asDateOnly(((Number) conversion.convert(l)).longValue());
|
||||
}
|
||||
|
||||
private static Function<Object, Object> toTime(Conversion conversion) {
|
||||
return l -> DateUtils.asTimeOnly(((Number) conversion.convert(l)).longValue());
|
||||
}
|
||||
|
||||
private static Function<Object, Object> toDateTime(Conversion conversion) {
|
||||
return l -> DateUtils.asDateTime(((Number) conversion.convert(l)).longValue());
|
||||
}
|
||||
|
||||
private static Function<Object, Object> fromDatetimeToDate() {
|
||||
return l -> DateUtils.asDateOnly((ZonedDateTime) l);
|
||||
}
|
||||
|
||||
private static Function<Object, Object> fromDatetimeToTime() {
|
||||
return l -> ((ZonedDateTime) l).toOffsetDateTime().toOffsetTime();
|
||||
}
|
||||
|
||||
public Object convert(Object l) {
|
||||
if (l == null) {
|
||||
return null;
|
||||
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.type;
|
||||
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
||||
import org.elasticsearch.xpack.sql.expression.literal.Interval;
|
||||
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.BOOLEAN;
|
||||
@ -27,6 +28,7 @@ import static org.elasticsearch.xpack.sql.type.DataType.KEYWORD;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.LONG;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.NULL;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.SHORT;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.TIME;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.UNSUPPORTED;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.fromTypeName;
|
||||
|
||||
@ -67,6 +69,9 @@ public final class DataTypes {
|
||||
if (value instanceof Short) {
|
||||
return SHORT;
|
||||
}
|
||||
if (value instanceof OffsetTime) {
|
||||
return TIME;
|
||||
}
|
||||
if (value instanceof ZonedDateTime) {
|
||||
return DATETIME;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import org.elasticsearch.xpack.sql.proto.StringUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@ -19,11 +20,15 @@ import java.time.format.DateTimeFormatterBuilder;
|
||||
|
||||
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
|
||||
import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
|
||||
import static java.time.format.DateTimeFormatter.ISO_TIME;
|
||||
|
||||
public final class DateUtils {
|
||||
|
||||
public static final ZoneId UTC = ZoneId.of("Z");
|
||||
public static final String DATE_PARSE_FORMAT = "epoch_millis";
|
||||
// In Java 8 LocalDate.EPOCH is not available, introduced with later Java versions
|
||||
public static final LocalDate EPOCH = LocalDate.of(1970, 1, 1);
|
||||
public static final long DAY_IN_MILLIS = 60 * 60 * 24 * 1000L;
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_ESCAPED_LITERAL_FORMATTER = new DateTimeFormatterBuilder()
|
||||
.append(ISO_LOCAL_DATE)
|
||||
@ -33,8 +38,6 @@ public final class DateUtils {
|
||||
|
||||
private static final DateFormatter UTC_DATE_TIME_FORMATTER = DateFormatter.forPattern("date_optional_time").withZone(UTC);
|
||||
|
||||
private static final long DAY_IN_MILLIS = 60 * 60 * 24 * 1000L;
|
||||
|
||||
private DateUtils() {}
|
||||
|
||||
/**
|
||||
@ -44,6 +47,24 @@ public final class DateUtils {
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), UTC).toLocalDate().atStartOfDay(UTC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an date for SQL TIME type from the millis since epoch.
|
||||
*/
|
||||
public static OffsetTime asTimeOnly(long millis) {
|
||||
return OffsetTime.ofInstant(Instant.ofEpochMilli(millis % DAY_IN_MILLIS), UTC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an date for SQL TIME type from the millis since epoch.
|
||||
*/
|
||||
public static OffsetTime asTimeOnly(long millis, ZoneId zoneId) {
|
||||
return OffsetTime.ofInstant(Instant.ofEpochMilli(millis % DAY_IN_MILLIS), zoneId);
|
||||
}
|
||||
|
||||
public static OffsetTime asTimeAtZone(OffsetTime time, ZoneId zonedId) {
|
||||
return time.atDate(DateUtils.EPOCH).atZoneSameInstant(zonedId).toOffsetDateTime().toOffsetTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a datetime from the millis since epoch (thus the time-zone is UTC).
|
||||
*/
|
||||
@ -69,6 +90,10 @@ public final class DateUtils {
|
||||
return zdt.toLocalDate().atStartOfDay(zdt.getZone());
|
||||
}
|
||||
|
||||
public static OffsetTime asTimeOnly(String timeFormat) {
|
||||
return DateFormatters.from(ISO_TIME.parse(timeFormat)).toOffsetDateTime().toOffsetTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given string into a DateTime using UTC as a default timezone.
|
||||
*/
|
||||
@ -88,6 +113,10 @@ public final class DateUtils {
|
||||
return date.format(ISO_LOCAL_DATE);
|
||||
}
|
||||
|
||||
public static String toTimeString(OffsetTime time) {
|
||||
return time.format(ISO_LOCAL_TIME);
|
||||
}
|
||||
|
||||
public static long minDayInterval(long l) {
|
||||
if (l < DAY_IN_MILLIS ) {
|
||||
return DAY_IN_MILLIS;
|
||||
|
@ -12,6 +12,9 @@ class org.elasticsearch.xpack.sql.expression.literal.IntervalDayTime {
|
||||
class org.elasticsearch.xpack.sql.expression.literal.IntervalYearMonth {
|
||||
}
|
||||
|
||||
class java.time.OffsetTime {
|
||||
}
|
||||
|
||||
class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalSqlScriptUtils {
|
||||
|
||||
#
|
||||
@ -107,6 +110,7 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS
|
||||
IntervalDayTime intervalDayTime(String, String)
|
||||
IntervalYearMonth intervalYearMonth(String, String)
|
||||
ZonedDateTime asDateTime(Object)
|
||||
OffsetTime asTime(String)
|
||||
|
||||
#
|
||||
# ASCII Functions
|
||||
|
@ -203,10 +203,42 @@ public class VerifierErrorMessagesTests extends ESTestCase {
|
||||
assertEquals("1:8: Invalid datetime field [ABS]. Use any datetime function.", error("SELECT EXTRACT(ABS FROM date) FROM test"));
|
||||
}
|
||||
|
||||
public void testValidDateTimeFunctionsOnTime() {
|
||||
accept("SELECT HOUR_OF_DAY(CAST(date AS TIME)) FROM test");
|
||||
accept("SELECT MINUTE_OF_HOUR(CAST(date AS TIME)) FROM test");
|
||||
accept("SELECT MINUTE_OF_DAY(CAST(date AS TIME)) FROM test");
|
||||
accept("SELECT SECOND_OF_MINUTE(CAST(date AS TIME)) FROM test");
|
||||
}
|
||||
|
||||
public void testInvalidDateTimeFunctionsOnTime() {
|
||||
assertEquals("1:8: argument of [DAY_OF_YEAR(CAST(date AS TIME))] must be [date or datetime], " +
|
||||
"found value [CAST(date AS TIME)] type [time]",
|
||||
error("SELECT DAY_OF_YEAR(CAST(date AS TIME)) FROM test"));
|
||||
}
|
||||
|
||||
public void testGroupByOnTimeNotAllowed() {
|
||||
assertEquals("1:36: Function [CAST(date AS TIME)] with data type [time] cannot be used for grouping",
|
||||
error("SELECT count(*) FROM test GROUP BY CAST(date AS TIME)"));
|
||||
}
|
||||
|
||||
public void testGroupByOnTimeWrappedWithScalar() {
|
||||
accept("SELECT count(*) FROM test GROUP BY MINUTE(CAST(date AS TIME))");
|
||||
}
|
||||
|
||||
public void testHistogramOnTimeNotAllowed() {
|
||||
assertEquals("1:8: first argument of [HISTOGRAM] must be [date, datetime or numeric], " +
|
||||
"found value [CAST(date AS TIME)] type [time]",
|
||||
error("SELECT HISTOGRAM(CAST(date AS TIME), INTERVAL 1 MONTH), COUNT(*) FROM test GROUP BY 1"));
|
||||
}
|
||||
|
||||
public void testSubtractFromInterval() {
|
||||
assertEquals("1:8: Cannot subtract a datetime[CAST('2000-01-01' AS DATETIME)] " +
|
||||
"from an interval[INTERVAL 1 MONTH]; do you mean the reverse?",
|
||||
error("SELECT INTERVAL 1 MONTH - CAST('2000-01-01' AS DATETIME)"));
|
||||
|
||||
assertEquals("1:8: Cannot subtract a time[CAST('12:23:56.789' AS TIME)] " +
|
||||
"from an interval[INTERVAL 1 MONTH]; do you mean the reverse?",
|
||||
error("SELECT INTERVAL 1 MONTH - CAST('12:23:56.789' AS TIME)"));
|
||||
}
|
||||
|
||||
public void testMultipleColumns() {
|
||||
@ -293,7 +325,7 @@ public class VerifierErrorMessagesTests extends ESTestCase {
|
||||
}
|
||||
|
||||
public void testGroupByOnInexact() {
|
||||
assertEquals("1:36: Field of data type [text] cannot be used for grouping; " +
|
||||
assertEquals("1:36: Field [text] of data type [text] cannot be used for grouping; " +
|
||||
"No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
|
||||
error("SELECT COUNT(*) FROM test GROUP BY text"));
|
||||
}
|
||||
|
@ -7,17 +7,20 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
|
||||
|
||||
import org.elasticsearch.common.io.stream.Writeable.Reader;
|
||||
import org.elasticsearch.test.AbstractWireSerializingTestCase;
|
||||
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeProcessor.DateTimeExtractor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeTestUtils.dateTime;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.UTC;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
public class DateTimeProcessorTests extends AbstractWireSerializingTestCase<DateTimeProcessor> {
|
||||
|
||||
public static DateTimeProcessor randomDateTimeProcessor() {
|
||||
return new DateTimeProcessor(randomFrom(DateTimeExtractor.values()), UTC);
|
||||
return new DateTimeProcessor(randomFrom(DateTimeExtractor.values()), randomZone());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -31,12 +34,12 @@ public class DateTimeProcessorTests extends AbstractWireSerializingTestCase<Date
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DateTimeProcessor mutateInstance(DateTimeProcessor instance) throws IOException {
|
||||
protected DateTimeProcessor mutateInstance(DateTimeProcessor instance) {
|
||||
DateTimeExtractor replaced = randomValueOtherThan(instance.extractor(), () -> randomFrom(DateTimeExtractor.values()));
|
||||
return new DateTimeProcessor(replaced, UTC);
|
||||
return new DateTimeProcessor(replaced, randomZone());
|
||||
}
|
||||
|
||||
public void testApply() {
|
||||
public void testApply_withTimezoneUTC() {
|
||||
DateTimeProcessor proc = new DateTimeProcessor(DateTimeExtractor.YEAR, UTC);
|
||||
assertEquals(1970, proc.process(dateTime(0L)));
|
||||
assertEquals(2017, proc.process(dateTime(2017, 01, 02, 10, 10)));
|
||||
@ -46,4 +49,21 @@ public class DateTimeProcessorTests extends AbstractWireSerializingTestCase<Date
|
||||
assertEquals(2, proc.process(dateTime(2017, 01, 02, 10, 10)));
|
||||
assertEquals(31, proc.process(dateTime(2017, 01, 31, 10, 10)));
|
||||
}
|
||||
|
||||
public void testApply_withTimezoneOtherThanUTC() {
|
||||
ZoneId zoneId = ZoneId.of("Etc/GMT-10");
|
||||
DateTimeProcessor proc = new DateTimeProcessor(DateTimeExtractor.YEAR, zoneId);
|
||||
assertEquals(2018, proc.process(dateTime(2017, 12, 31, 18, 10)));
|
||||
|
||||
proc = new DateTimeProcessor(DateTimeExtractor.DAY_OF_MONTH, zoneId);
|
||||
assertEquals(1, proc.process(dateTime(2017, 12, 31, 20, 30)));
|
||||
}
|
||||
|
||||
public void testFailOnTime() {
|
||||
DateTimeProcessor proc = new DateTimeProcessor(DateTimeExtractor.YEAR, UTC);
|
||||
SqlIllegalArgumentException e = expectThrows(SqlIllegalArgumentException.class, () -> {
|
||||
proc.process(OffsetTime.now(UTC));
|
||||
});
|
||||
assertThat(e.getMessage(), startsWith("A [date], a [time] or a [datetime] is required; received "));
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
|
||||
|
||||
import org.elasticsearch.xpack.sql.util.DateUtils;
|
||||
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
public class DateTimeTestUtils {
|
||||
@ -25,4 +27,12 @@ public class DateTimeTestUtils {
|
||||
public static ZonedDateTime date(long millisSinceEpoch) {
|
||||
return DateUtils.asDateOnly(millisSinceEpoch);
|
||||
}
|
||||
|
||||
public static OffsetTime time(long millisSinceEpoch) {
|
||||
return DateUtils.asTimeOnly(millisSinceEpoch);
|
||||
}
|
||||
|
||||
public static OffsetTime time(int hour, int minute, int second, int nano) {
|
||||
return OffsetTime.of(hour, minute, second, nano, ZoneOffset.UTC);
|
||||
}
|
||||
}
|
||||
|
77
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/TimeProcessorTests.java
Normal file
77
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/TimeProcessorTests.java
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
|
||||
|
||||
import org.elasticsearch.common.io.stream.Writeable.Reader;
|
||||
import org.elasticsearch.test.AbstractWireSerializingTestCase;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeProcessor.DateTimeExtractor;
|
||||
|
||||
import java.time.ZoneId;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeTestUtils.time;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.UTC;
|
||||
|
||||
public class TimeProcessorTests extends AbstractWireSerializingTestCase<TimeProcessor> {
|
||||
|
||||
public static TimeProcessor randomTimeProcessor() {
|
||||
return new TimeProcessor(randomFrom(DateTimeExtractor.values()), randomZone());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TimeProcessor createTestInstance() {
|
||||
return randomTimeProcessor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Reader<TimeProcessor> instanceReader() {
|
||||
return TimeProcessor::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TimeProcessor mutateInstance(TimeProcessor instance) {
|
||||
DateTimeExtractor replaced = randomValueOtherThan(instance.extractor(), () -> randomFrom(DateTimeExtractor.values()));
|
||||
return new TimeProcessor(replaced, randomZone());
|
||||
}
|
||||
|
||||
public void testApply_withTimeZoneUTC() {
|
||||
TimeProcessor proc = new TimeProcessor(DateTimeExtractor.SECOND_OF_MINUTE, UTC);
|
||||
assertEquals(0, proc.process(time(0L)));
|
||||
assertEquals(2, proc.process(time(2345L)));
|
||||
|
||||
proc = new TimeProcessor(DateTimeExtractor.MINUTE_OF_DAY, UTC);
|
||||
assertEquals(0, proc.process(time(0L)));
|
||||
assertEquals(620, proc.process(time(10, 20, 30, 123456789)));
|
||||
|
||||
proc = new TimeProcessor(DateTimeExtractor.MINUTE_OF_HOUR, UTC);
|
||||
assertEquals(0, proc.process(time(0L)));
|
||||
assertEquals(20, proc.process(time(10, 20, 30, 123456789)));
|
||||
|
||||
proc = new TimeProcessor(DateTimeExtractor.HOUR_OF_DAY, UTC);
|
||||
assertEquals(0, proc.process(time(0L)));
|
||||
assertEquals(10, proc.process(time(10, 20, 30, 123456789)));
|
||||
}
|
||||
|
||||
public void testApply_withTimeZoneOtherThanUTC() {
|
||||
ZoneId zoneId = ZoneId.of("Etc/GMT-10");
|
||||
|
||||
TimeProcessor proc = new TimeProcessor(DateTimeExtractor.SECOND_OF_MINUTE, zoneId);
|
||||
assertEquals(0, proc.process(time(0L)));
|
||||
assertEquals(2, proc.process(time(2345L)));
|
||||
|
||||
proc = new TimeProcessor(DateTimeExtractor.MINUTE_OF_DAY, zoneId);
|
||||
assertEquals(600, proc.process(time(0L)));
|
||||
assertEquals(1220, proc.process(time(10, 20, 30, 123456789)));
|
||||
|
||||
proc = new TimeProcessor(DateTimeExtractor.MINUTE_OF_HOUR, zoneId);
|
||||
assertEquals(0, proc.process(time(0L)));
|
||||
assertEquals(20, proc.process(time(10, 20, 30, 123456789)));
|
||||
|
||||
proc = new TimeProcessor(DateTimeExtractor.HOUR_OF_DAY, zoneId);
|
||||
assertEquals(10, proc.process(time(0L)));
|
||||
assertEquals(20, proc.process(time(10, 20, 30, 123456789)));;
|
||||
assertEquals(4, proc.process(time(18, 20, 30, 123456789)));
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import org.elasticsearch.xpack.sql.util.DateUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
@ -104,6 +105,33 @@ public class BinaryArithmeticTests extends ESTestCase {
|
||||
assertEquals(L(now.plus(t)), L(x));
|
||||
}
|
||||
|
||||
public void testAddYearMonthIntervalToTime() {
|
||||
OffsetTime now = OffsetTime.now(DateUtils.UTC);
|
||||
Literal l = L(now);
|
||||
TemporalAmount t = Period.ofYears(100).plusMonths(50);
|
||||
Literal r = interval(t, INTERVAL_HOUR);
|
||||
OffsetTime x = add(l, r);
|
||||
assertEquals(L(now), L(x));
|
||||
}
|
||||
|
||||
public void testAddDayTimeIntervalToTime() {
|
||||
OffsetTime now = OffsetTime.now(DateUtils.UTC);
|
||||
Literal l = L(now);
|
||||
TemporalAmount t = Duration.ofHours(32);
|
||||
Literal r = interval(Duration.ofHours(32), INTERVAL_HOUR);
|
||||
OffsetTime x = add(l, r);
|
||||
assertEquals(L(now.plus(t)), L(x));
|
||||
}
|
||||
|
||||
public void testAddDayTimeIntervalToTimeReverse() {
|
||||
OffsetTime now = OffsetTime.now(DateUtils.UTC);
|
||||
Literal l = L(now);
|
||||
TemporalAmount t = Duration.ofHours(45);
|
||||
Literal r = interval(Duration.ofHours(45), INTERVAL_HOUR);
|
||||
OffsetTime x = add(r, l);
|
||||
assertEquals(L(now.plus(t)), L(x));
|
||||
}
|
||||
|
||||
public void testAddNumberToIntervalIllegal() {
|
||||
Literal r = interval(Duration.ofHours(2), INTERVAL_HOUR);
|
||||
SqlIllegalArgumentException expect = expectThrows(SqlIllegalArgumentException.class, () -> add(r, L(1)));
|
||||
@ -142,12 +170,6 @@ public class BinaryArithmeticTests extends ESTestCase {
|
||||
assertEquals("Cannot subtract a date from an interval; do you mean the reverse?", ex.getMessage());
|
||||
}
|
||||
|
||||
public void testSubNumberFromIntervalIllegal() {
|
||||
Literal r = interval(Duration.ofHours(2), INTERVAL_HOUR);
|
||||
SqlIllegalArgumentException expect = expectThrows(SqlIllegalArgumentException.class, () -> sub(r, L(1)));
|
||||
assertEquals("Cannot compute [-] between [IntervalDayTime] [Integer]", expect.getMessage());
|
||||
}
|
||||
|
||||
public void testSubDayTimeIntervalToDateTime() {
|
||||
ZonedDateTime now = ZonedDateTime.now(DateUtils.UTC);
|
||||
Literal l = L(now);
|
||||
@ -157,7 +179,40 @@ public class BinaryArithmeticTests extends ESTestCase {
|
||||
assertEquals(L(now.minus(t)), L(x));
|
||||
}
|
||||
|
||||
public void testMulIntervalNumber() throws Exception {
|
||||
public void testSubYearMonthIntervalToTime() {
|
||||
OffsetTime now = OffsetTime.now(DateUtils.UTC);
|
||||
Literal l = L(now);
|
||||
TemporalAmount t = Period.ofYears(100).plusMonths(50);
|
||||
Literal r = interval(t, INTERVAL_HOUR);
|
||||
OffsetTime x = sub(l, r);
|
||||
assertEquals(L(now), L(x));
|
||||
}
|
||||
|
||||
public void testSubYearMonthIntervalToTimeIllegal() {
|
||||
OffsetTime now = OffsetTime.now(DateUtils.UTC);
|
||||
Literal l = L(now);
|
||||
TemporalAmount t = Period.ofYears(100).plusMonths(50);
|
||||
Literal r = interval(t, INTERVAL_HOUR);
|
||||
SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> sub(r, l));
|
||||
assertEquals("Cannot subtract a date from an interval; do you mean the reverse?", ex.getMessage());
|
||||
}
|
||||
|
||||
public void testSubDayTimeIntervalToTime() {
|
||||
OffsetTime now = OffsetTime.now(DateUtils.UTC);
|
||||
Literal l = L(now);
|
||||
TemporalAmount t = Duration.ofHours(36);
|
||||
Literal r = interval(Duration.ofHours(36), INTERVAL_HOUR);
|
||||
OffsetTime x = sub(l, r);
|
||||
assertEquals(L(now.minus(t)), L(x));
|
||||
}
|
||||
|
||||
public void testSubNumberFromIntervalIllegal() {
|
||||
Literal r = interval(Duration.ofHours(2), INTERVAL_HOUR);
|
||||
SqlIllegalArgumentException expect = expectThrows(SqlIllegalArgumentException.class, () -> sub(r, L(1)));
|
||||
assertEquals("Cannot compute [-] between [IntervalDayTime] [Integer]", expect.getMessage());
|
||||
}
|
||||
|
||||
public void testMulIntervalNumber() {
|
||||
Literal l = interval(Duration.ofHours(2), INTERVAL_HOUR);
|
||||
IntervalDayTime interval = mul(l, -1);
|
||||
assertEquals(INTERVAL_HOUR, interval.dataType());
|
||||
@ -165,7 +220,7 @@ public class BinaryArithmeticTests extends ESTestCase {
|
||||
assertEquals(Duration.ofHours(2).negated(), p);
|
||||
}
|
||||
|
||||
public void testMulNumberInterval() throws Exception {
|
||||
public void testMulNumberInterval() {
|
||||
Literal r = interval(Period.ofYears(1), INTERVAL_YEAR);
|
||||
IntervalYearMonth interval = mul(-2, r);
|
||||
assertEquals(INTERVAL_YEAR, interval.dataType());
|
||||
|
@ -6,7 +6,6 @@
|
||||
package org.elasticsearch.xpack.sql.parser;
|
||||
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
||||
import org.elasticsearch.xpack.sql.expression.Expression;
|
||||
import org.elasticsearch.xpack.sql.expression.Literal;
|
||||
import org.elasticsearch.xpack.sql.expression.UnresolvedAttribute;
|
||||
@ -180,9 +179,9 @@ public class EscapedFunctionsTests extends ESTestCase {
|
||||
ex.getMessage());
|
||||
}
|
||||
|
||||
public void testTimeLiteralUnsupported() {
|
||||
SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> timeLiteral("10:10:10"));
|
||||
assertThat(ex.getMessage(), is("Time (only) literals are not supported; a date component is required as well"));
|
||||
public void testTimeLiteral() {
|
||||
Literal l = timeLiteral("12:23:56");
|
||||
assertThat(l.dataType(), is(DataType.TIME));
|
||||
}
|
||||
|
||||
public void testTimeLiteralValidation() {
|
||||
|
@ -57,11 +57,11 @@ public class SysParserTests extends ESTestCase {
|
||||
return new Tuple<>(cmd, session);
|
||||
}
|
||||
|
||||
public void testSysTypes() throws Exception {
|
||||
public void testSysTypes() {
|
||||
Command cmd = sql("SYS TYPES").v1();
|
||||
|
||||
List<String> names = asList("BYTE", "LONG", "BINARY", "NULL", "INTEGER", "SHORT", "HALF_FLOAT", "FLOAT", "DOUBLE", "SCALED_FLOAT",
|
||||
"KEYWORD", "TEXT", "IP", "BOOLEAN", "DATE", "DATETIME",
|
||||
"KEYWORD", "TEXT", "IP", "BOOLEAN", "DATE", "TIME", "DATETIME",
|
||||
"INTERVAL_YEAR", "INTERVAL_MONTH", "INTERVAL_DAY", "INTERVAL_HOUR", "INTERVAL_MINUTE", "INTERVAL_SECOND",
|
||||
"INTERVAL_YEAR_TO_MONTH", "INTERVAL_DAY_TO_HOUR", "INTERVAL_DAY_TO_MINUTE", "INTERVAL_DAY_TO_SECOND",
|
||||
"INTERVAL_HOUR_TO_MINUTE", "INTERVAL_HOUR_TO_SECOND", "INTERVAL_MINUTE_TO_SECOND",
|
||||
@ -86,11 +86,11 @@ public class SysParserTests extends ESTestCase {
|
||||
}, ex -> fail(ex.getMessage())));
|
||||
}
|
||||
|
||||
public void testSysColsNoArgs() throws Exception {
|
||||
public void testSysColsNoArgs() {
|
||||
runSysColumns("SYS COLUMNS");
|
||||
}
|
||||
|
||||
public void testSysColumnEmptyCatalog() throws Exception {
|
||||
public void testSysColumnEmptyCatalog() {
|
||||
Tuple<Command, SqlSession> sql = sql("SYS COLUMNS CATALOG '' TABLE LIKE '%' LIKE '%'");
|
||||
|
||||
sql.v1().execute(sql.v2(), ActionListener.wrap(r -> {
|
||||
@ -99,7 +99,7 @@ public class SysParserTests extends ESTestCase {
|
||||
}, ex -> fail(ex.getMessage())));
|
||||
}
|
||||
|
||||
public void testSysColsTableOnlyCatalog() throws Exception {
|
||||
public void testSysColsTableOnlyCatalog() {
|
||||
Tuple<Command, SqlSession> sql = sql("SYS COLUMNS CATALOG 'catalog'");
|
||||
|
||||
sql.v1().execute(sql.v2(), ActionListener.wrap(r -> {
|
||||
@ -108,20 +108,20 @@ public class SysParserTests extends ESTestCase {
|
||||
}, ex -> fail(ex.getMessage())));
|
||||
}
|
||||
|
||||
public void testSysColsTableOnlyPattern() throws Exception {
|
||||
public void testSysColsTableOnlyPattern() {
|
||||
runSysColumns("SYS COLUMNS TABLE LIKE 'test'");
|
||||
}
|
||||
|
||||
public void testSysColsColOnlyPattern() throws Exception {
|
||||
public void testSysColsColOnlyPattern() {
|
||||
runSysColumns("SYS COLUMNS LIKE '%'");
|
||||
}
|
||||
|
||||
public void testSysColsTableAndColsPattern() throws Exception {
|
||||
public void testSysColsTableAndColsPattern() {
|
||||
runSysColumns("SYS COLUMNS TABLE LIKE 'test' LIKE '%'");
|
||||
}
|
||||
|
||||
|
||||
private void runSysColumns(String commandVariation) throws Exception {
|
||||
private void runSysColumns(String commandVariation) {
|
||||
Tuple<Command, SqlSession> sql = sql(commandVariation);
|
||||
List<String> names = asList("bool",
|
||||
"int",
|
||||
|
@ -44,7 +44,7 @@ public class SysTypesTests extends ESTestCase {
|
||||
Command cmd = sql("SYS TYPES").v1();
|
||||
|
||||
List<String> names = asList("BYTE", "LONG", "BINARY", "NULL", "INTEGER", "SHORT", "HALF_FLOAT", "FLOAT", "DOUBLE", "SCALED_FLOAT",
|
||||
"KEYWORD", "TEXT", "IP", "BOOLEAN", "DATE", "DATETIME",
|
||||
"KEYWORD", "TEXT", "IP", "BOOLEAN", "DATE", "TIME", "DATETIME",
|
||||
"INTERVAL_YEAR", "INTERVAL_MONTH", "INTERVAL_DAY", "INTERVAL_HOUR", "INTERVAL_MINUTE", "INTERVAL_SECOND",
|
||||
"INTERVAL_YEAR_TO_MONTH", "INTERVAL_DAY_TO_HOUR", "INTERVAL_DAY_TO_MINUTE", "INTERVAL_DAY_TO_SECOND",
|
||||
"INTERVAL_HOUR_TO_MINUTE", "INTERVAL_HOUR_TO_SECOND", "INTERVAL_MINUTE_TO_SECOND",
|
||||
|
@ -662,7 +662,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
||||
assertEquals(259200000L, ((GroupByDateHistogram) eqe.queryContainer().aggs().groups().get(0)).interval());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testCountAndCountDistinctFolding() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT COUNT(DISTINCT keyword) dkey, COUNT(keyword) key FROM test");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
@ -807,57 +807,56 @@ public class QueryTranslatorTests extends ESTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testGlobalCountInImplicitGroupByForcesTrackHits() throws Exception {
|
||||
public void testGlobalCountInImplicitGroupByForcesTrackHits() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT COUNT(*) FROM test");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
EsQueryExec eqe = (EsQueryExec) p;
|
||||
assertTrue("Should be tracking hits", eqe.queryContainer().shouldTrackHits());
|
||||
}
|
||||
|
||||
public void testGlobalCountAllInImplicitGroupByForcesTrackHits() throws Exception {
|
||||
public void testGlobalCountAllInImplicitGroupByForcesTrackHits() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT COUNT(ALL *) FROM test");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
EsQueryExec eqe = (EsQueryExec) p;
|
||||
assertTrue("Should be tracking hits", eqe.queryContainer().shouldTrackHits());
|
||||
}
|
||||
|
||||
public void testGlobalCountInSpecificGroupByDoesNotForceTrackHits() throws Exception {
|
||||
public void testGlobalCountInSpecificGroupByDoesNotForceTrackHits() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT COUNT(*) FROM test GROUP BY int");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
EsQueryExec eqe = (EsQueryExec) p;
|
||||
assertFalse("Should NOT be tracking hits", eqe.queryContainer().shouldTrackHits());
|
||||
}
|
||||
|
||||
public void testFieldAllCountDoesNotTrackHits() throws Exception {
|
||||
public void testFieldAllCountDoesNotTrackHits() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT COUNT(ALL int) FROM test");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
EsQueryExec eqe = (EsQueryExec) p;
|
||||
assertFalse("Should NOT be tracking hits", eqe.queryContainer().shouldTrackHits());
|
||||
}
|
||||
|
||||
public void testFieldCountDoesNotTrackHits() throws Exception {
|
||||
public void testFieldCountDoesNotTrackHits() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT COUNT(int) FROM test");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
EsQueryExec eqe = (EsQueryExec) p;
|
||||
assertFalse("Should NOT be tracking hits", eqe.queryContainer().shouldTrackHits());
|
||||
}
|
||||
|
||||
public void testDistinctCountDoesNotTrackHits() throws Exception {
|
||||
public void testDistinctCountDoesNotTrackHits() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT COUNT(DISTINCT int) FROM test");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
EsQueryExec eqe = (EsQueryExec) p;
|
||||
assertFalse("Should NOT be tracking hits", eqe.queryContainer().shouldTrackHits());
|
||||
}
|
||||
|
||||
public void testNoCountDoesNotTrackHits() throws Exception {
|
||||
public void testNoCountDoesNotTrackHits() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT int FROM test");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
EsQueryExec eqe = (EsQueryExec) p;
|
||||
assertFalse("Should NOT be tracking hits", eqe.queryContainer().shouldTrackHits());
|
||||
}
|
||||
|
||||
public void testZonedDateTimeInScripts() throws Exception {
|
||||
public void testZonedDateTimeInScripts() {
|
||||
PhysicalPlan p = optimizeAndPlan(
|
||||
"SELECT date FROM test WHERE date + INTERVAL 1 YEAR > CAST('2019-03-11T12:34:56.000Z' AS DATETIME)");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
|
@ -12,14 +12,16 @@ import org.elasticsearch.xpack.sql.expression.Literal;
|
||||
import org.elasticsearch.xpack.sql.tree.Location;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.type.DataTypeConversion.Conversion;
|
||||
import org.elasticsearch.xpack.sql.util.DateUtils;
|
||||
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeTestUtils.date;
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeTestUtils.dateTime;
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeTestUtils.time;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.BOOLEAN;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.BYTE;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.DATE;
|
||||
@ -39,12 +41,15 @@ import static org.elasticsearch.xpack.sql.type.DataType.LONG;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.NULL;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.SHORT;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.TEXT;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.TIME;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.UNSUPPORTED;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.fromTypeName;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.values;
|
||||
import static org.elasticsearch.xpack.sql.type.DataTypeConversion.commonType;
|
||||
import static org.elasticsearch.xpack.sql.type.DataTypeConversion.conversionFor;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.asDateOnly;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.asDateTime;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.asTimeOnly;
|
||||
|
||||
|
||||
public class DataTypeConversionTests extends ESTestCase {
|
||||
@ -59,8 +64,16 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals("1973-11-29", conversion.convert(DateUtils.asDateOnly(123456789101L)));
|
||||
assertEquals("1966-02-02", conversion.convert(DateUtils.asDateOnly(-123456789101L)));
|
||||
assertEquals("1973-11-29", conversion.convert(asDateOnly(123456789101L)));
|
||||
assertEquals("1966-02-02", conversion.convert(asDateOnly(-123456789101L)));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(TIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals("00:02:03.456", conversion.convert(asTimeOnly(123456L)));
|
||||
assertEquals("21:33:09.101", conversion.convert(asTimeOnly(123456789101L)));
|
||||
assertEquals("23:57:56.544", conversion.convert(asTimeOnly(-123456L)));
|
||||
assertEquals("02:26:50.899", conversion.convert(asTimeOnly(-123456789101L)));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
@ -99,8 +112,16 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(123379200000L, conversion.convert(DateUtils.asDateOnly(123456789101L)));
|
||||
assertEquals(-123465600000L, conversion.convert(DateUtils.asDateOnly(-123456789101L)));
|
||||
assertEquals(123379200000L, conversion.convert(asDateOnly(123456789101L)));
|
||||
assertEquals(-123465600000L, conversion.convert(asDateOnly(-123456789101L)));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(TIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(123456L, conversion.convert(asTimeOnly(123456L)));
|
||||
assertEquals(77589101L, conversion.convert(asTimeOnly(123456789101L)));
|
||||
assertEquals(86276544L, conversion.convert(asTimeOnly(-123456L)));
|
||||
assertEquals(8810899L, conversion.convert(asTimeOnly(-123456789101L)));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
@ -141,6 +162,10 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
assertEquals(date(1), conversion.convert(true));
|
||||
assertEquals(date(0), conversion.convert(false));
|
||||
}
|
||||
{
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversionFor(TIME, to));
|
||||
assertEquals("cannot convert from [time] to [date]", e.getMessage());
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
@ -160,12 +185,67 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
ZonedDateTime zdt = TestUtils.now();
|
||||
Conversion forward = conversionFor(DATE, KEYWORD);
|
||||
Conversion back = conversionFor(KEYWORD, DATE);
|
||||
assertEquals(DateUtils.asDateOnly(zdt), back.convert(forward.convert(zdt)));
|
||||
assertEquals(asDateOnly(zdt), back.convert(forward.convert(zdt)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert("0xff"));
|
||||
assertEquals("cannot cast [0xff] to [date]: Text '0xff' could not be parsed at index 0", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testConversionToTime() {
|
||||
DataType to = TIME;
|
||||
{
|
||||
Conversion conversion = conversionFor(DOUBLE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(time(10L), conversion.convert(10.0));
|
||||
assertEquals(time(10L), conversion.convert(10.1));
|
||||
assertEquals(time(11L), conversion.convert(10.6));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(Double.MAX_VALUE));
|
||||
assertEquals("[" + Double.MAX_VALUE + "] out of [long] range", e.getMessage());
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(INTEGER, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(time(10L), conversion.convert(10));
|
||||
assertEquals(time(-134L), conversion.convert(-134));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(BOOLEAN, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(time(1), conversion.convert(true));
|
||||
assertEquals(time(0), conversion.convert(false));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(time(123379200000L), conversion.convert(asDateOnly(123456789101L)));
|
||||
assertEquals(time(-123465600000L), conversion.convert(asDateOnly(-123456789101L)));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(time(77589101L), conversion.convert(asDateTime(123456789101L)));
|
||||
assertEquals(time(8810899L), conversion.convert(asDateTime(-123456789101L)));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(KEYWORD, to);
|
||||
assertNull(conversion.convert(null));
|
||||
|
||||
assertEquals(time(0L), conversion.convert("00:00:00Z"));
|
||||
assertEquals(time(1000L), conversion.convert("00:00:01Z"));
|
||||
assertEquals(time(1234L), conversion.convert("00:00:01.234Z"));
|
||||
assertEquals(time(63296789L).withOffsetSameInstant(ZoneOffset.ofHours(-5)), conversion.convert("12:34:56.789-05:00"));
|
||||
|
||||
// double check back and forth conversion
|
||||
OffsetTime ot = org.elasticsearch.common.time.DateUtils.nowWithMillisResolution().toOffsetDateTime().toOffsetTime();
|
||||
Conversion forward = conversionFor(TIME, KEYWORD);
|
||||
Conversion back = conversionFor(KEYWORD, TIME);
|
||||
assertEquals(ot, back.convert(forward.convert(ot)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert("0xff"));
|
||||
assertEquals("cannot cast [0xff] to [time]: Text '0xff' could not be parsed at index 0",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testConversionToDateTime() {
|
||||
DataType to = DATETIME;
|
||||
{
|
||||
@ -192,8 +272,12 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(dateTime(123379200000L), conversion.convert(DateUtils.asDateOnly(123456789101L)));
|
||||
assertEquals(dateTime(-123465600000L), conversion.convert(DateUtils.asDateOnly(-123456789101L)));
|
||||
assertEquals(dateTime(123379200000L), conversion.convert(asDateOnly(123456789101L)));
|
||||
assertEquals(dateTime(-123465600000L), conversion.convert(asDateOnly(-123456789101L)));
|
||||
}
|
||||
{
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversionFor(TIME, to));
|
||||
assertEquals("cannot convert from [time] to [datetime]", e.getMessage());
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(KEYWORD, to);
|
||||
@ -216,6 +300,58 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testConversionToFloat() {
|
||||
DataType to = FLOAT;
|
||||
{
|
||||
Conversion conversion = conversionFor(DOUBLE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(10.0f, (float) conversion.convert(10.0d), 0.00001);
|
||||
assertEquals(10.1f, (float) conversion.convert(10.1d), 0.00001);
|
||||
assertEquals(10.6f, (float) conversion.convert(10.6d), 0.00001);
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(INTEGER, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(10.0f, (float) conversion.convert(10), 0.00001);
|
||||
assertEquals(-134.0f, (float) conversion.convert(-134), 0.00001);
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(BOOLEAN, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(1.0f, (float) conversion.convert(true), 0);
|
||||
assertEquals(0.0f, (float) conversion.convert(false), 0);
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(1.233792E11f, (float) conversion.convert(asDateOnly(123456789101L)), 0);
|
||||
assertEquals(-1.234656E11f, (float) conversion.convert(asDateOnly(-123456789101L)), 0);
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(TIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(123456.0f, (float) conversion.convert(asTimeOnly(123456L)), 0);
|
||||
assertEquals(7.7589104E7f, (float) conversion.convert(asTimeOnly(123456789101L)), 0);
|
||||
assertEquals(8.6276544E7f, (float) conversion.convert(asTimeOnly(-123456L)), 0);
|
||||
assertEquals(8810899.0f, (float) conversion.convert(asTimeOnly(-123456789101L)), 0);
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(1.23456789101E11f, (float) conversion.convert(asDateTime(123456789101L)), 0);
|
||||
assertEquals(-1.23456789101E11f, (float) conversion.convert(asDateTime(-123456789101L)), 0);
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(KEYWORD, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(1.0f, (float) conversion.convert("1"), 0);
|
||||
assertEquals(0.0f, (float) conversion.convert("-0"), 0);
|
||||
assertEquals(12.776f, (float) conversion.convert("12.776"), 0.00001);
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert("0xff"));
|
||||
assertEquals("cannot cast [0xff] to [float]", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testConversionToDouble() {
|
||||
DataType to = DOUBLE;
|
||||
{
|
||||
@ -240,8 +376,16 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(1.233792E11, (double) conversion.convert(DateUtils.asDateOnly(123456789101L)), 0);
|
||||
assertEquals(-1.234656E11, (double) conversion.convert(DateUtils.asDateOnly(-123456789101L)), 0);
|
||||
assertEquals(1.233792E11, (double) conversion.convert(asDateOnly(123456789101L)), 0);
|
||||
assertEquals(-1.234656E11, (double) conversion.convert(asDateOnly(-123456789101L)), 0);
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(TIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(123456.0, (double) conversion.convert(asTimeOnly(123456L)), 0);
|
||||
assertEquals(7.7589101E7, (double) conversion.convert(asTimeOnly(123456789101L)), 0);
|
||||
assertEquals(8.6276544E7, (double) conversion.convert(asTimeOnly(-123456L)), 0);
|
||||
assertEquals(8810899.0, (double) conversion.convert(asTimeOnly(-123456789101L)), 0);
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
@ -293,9 +437,16 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(true, conversion.convert(DateUtils.asDateOnly(123456789101L)));
|
||||
assertEquals(true, conversion.convert(DateUtils.asDateOnly(-123456789101L)));
|
||||
assertEquals(false, conversion.convert(DateUtils.asDateOnly(0L)));
|
||||
assertEquals(true, conversion.convert(asDateOnly(123456789101L)));
|
||||
assertEquals(true, conversion.convert(asDateOnly(-123456789101L)));
|
||||
assertEquals(false, conversion.convert(asDateOnly(0L)));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(TIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(true, conversion.convert(asTimeOnly(123456789101L)));
|
||||
assertEquals(true, conversion.convert(asTimeOnly(-123456789101L)));
|
||||
assertEquals(false, conversion.convert(asTimeOnly(0L)));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
@ -342,20 +493,29 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(0, conversion.convert(DateUtils.asDateOnly(12345678L)));
|
||||
assertEquals(86400000, conversion.convert(DateUtils.asDateOnly(123456789L)));
|
||||
assertEquals(172800000, conversion.convert(DateUtils.asDateOnly(223456789L)));
|
||||
assertEquals(-172800000, conversion.convert(DateUtils.asDateOnly(-123456789L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(DateUtils.asDateOnly(Long.MAX_VALUE)));
|
||||
assertEquals(0, conversion.convert(asDateOnly(12345678L)));
|
||||
assertEquals(86400000, conversion.convert(asDateOnly(123456789L)));
|
||||
assertEquals(172800000, conversion.convert(asDateOnly(223456789L)));
|
||||
assertEquals(-172800000, conversion.convert(asDateOnly(-123456789L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(asDateOnly(Long.MAX_VALUE)));
|
||||
assertEquals("[9223372036828800000] out of [integer] range", e.getMessage());
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(TIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(123456, conversion.convert(asTimeOnly(123456L)));
|
||||
assertEquals(77589101, conversion.convert(asTimeOnly(123456789101L)));
|
||||
assertEquals(86276544, conversion.convert(asTimeOnly(-123456L)));
|
||||
assertEquals(8810899, conversion.convert(asTimeOnly(-123456789101L)));
|
||||
assertEquals(25975807, conversion.convert(asTimeOnly(Long.MAX_VALUE)));
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals(12345678, conversion.convert(DateUtils.asDateTime(12345678L)));
|
||||
assertEquals(223456789, conversion.convert(DateUtils.asDateTime(223456789L)));
|
||||
assertEquals(-123456789, conversion.convert(DateUtils.asDateTime(-123456789L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(DateUtils.asDateTime(Long.MAX_VALUE)));
|
||||
assertEquals(12345678, conversion.convert(asDateTime(12345678L)));
|
||||
assertEquals(223456789, conversion.convert(asDateTime(223456789L)));
|
||||
assertEquals(-123456789, conversion.convert(asDateTime(-123456789L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(asDateTime(Long.MAX_VALUE)));
|
||||
assertEquals("[" + Long.MAX_VALUE + "] out of [integer] range", e.getMessage());
|
||||
}
|
||||
}
|
||||
@ -374,17 +534,26 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals((short) 0, conversion.convert(DateUtils.asDateOnly(12345678L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(DateUtils.asDateOnly(123456789L)));
|
||||
assertEquals((short) 0, conversion.convert(asDateOnly(12345678L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(asDateOnly(123456789L)));
|
||||
assertEquals("[86400000] out of [short] range", e.getMessage());
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(TIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals((short) 12345, conversion.convert(asTimeOnly(12345L)));
|
||||
Exception e1 = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(asTimeOnly(-123456789L)));
|
||||
assertEquals("[49343211] out of [short] range", e1.getMessage());
|
||||
Exception e2 = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(asTimeOnly(123456789L)));
|
||||
assertEquals("[37056789] out of [short] range", e2.getMessage());
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals((short) 12345, conversion.convert(DateUtils.asDateTime(12345L)));
|
||||
assertEquals((short) -12345, conversion.convert(DateUtils.asDateTime(-12345L)));
|
||||
assertEquals((short) 12345, conversion.convert(asDateTime(12345L)));
|
||||
assertEquals((short) -12345, conversion.convert(asDateTime(-12345L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class,
|
||||
() -> conversion.convert(DateUtils.asDateTime(Integer.MAX_VALUE)));
|
||||
() -> conversion.convert(asDateTime(Integer.MAX_VALUE)));
|
||||
assertEquals("[" + Integer.MAX_VALUE + "] out of [short] range", e.getMessage());
|
||||
}
|
||||
}
|
||||
@ -403,17 +572,26 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
{
|
||||
Conversion conversion = conversionFor(DATE, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals((byte) 0, conversion.convert(DateUtils.asDateOnly(12345678L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(DateUtils.asDateOnly(123456789L)));
|
||||
assertEquals((byte) 0, conversion.convert(asDateOnly(12345678L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(asDateOnly(123456789L)));
|
||||
assertEquals("[86400000] out of [byte] range", e.getMessage());
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(TIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals((byte) 123, conversion.convert(asTimeOnly(123L)));
|
||||
Exception e1 = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(asTimeOnly(-123L)));
|
||||
assertEquals("[86399877] out of [byte] range", e1.getMessage());
|
||||
Exception e2 = expectThrows(SqlIllegalArgumentException.class, () -> conversion.convert(asTimeOnly(123456789L)));
|
||||
assertEquals("[37056789] out of [byte] range", e2.getMessage());
|
||||
}
|
||||
{
|
||||
Conversion conversion = conversionFor(DATETIME, to);
|
||||
assertNull(conversion.convert(null));
|
||||
assertEquals((byte) 123, conversion.convert(DateUtils.asDateTime(123L)));
|
||||
assertEquals((byte) -123, conversion.convert(DateUtils.asDateTime(-123L)));
|
||||
assertEquals((byte) 123, conversion.convert(asDateTime(123L)));
|
||||
assertEquals((byte) -123, conversion.convert(asDateTime(-123L)));
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class,
|
||||
() -> conversion.convert(DateUtils.asDateTime(Integer.MAX_VALUE)));
|
||||
() -> conversion.convert(asDateTime(Integer.MAX_VALUE)));
|
||||
assertEquals("[" + Integer.MAX_VALUE + "] out of [byte] range", e.getMessage());
|
||||
}
|
||||
}
|
||||
@ -452,10 +630,16 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
// dates/datetimes and intervals
|
||||
assertEquals(DATETIME, commonType(DATE, DATETIME));
|
||||
assertEquals(DATETIME, commonType(DATETIME, DATE));
|
||||
assertEquals(DATETIME, commonType(TIME, DATETIME));
|
||||
assertEquals(DATETIME, commonType(DATETIME, TIME));
|
||||
assertEquals(DATETIME, commonType(DATETIME, randomInterval()));
|
||||
assertEquals(DATETIME, commonType(randomInterval(), DATETIME));
|
||||
assertEquals(DATETIME, commonType(DATE, TIME));
|
||||
assertEquals(DATETIME, commonType(TIME, DATE));
|
||||
assertEquals(DATE, commonType(DATE, randomInterval()));
|
||||
assertEquals(DATE, commonType(randomInterval(), DATE));
|
||||
assertEquals(TIME, commonType(TIME, randomInterval()));
|
||||
assertEquals(TIME, commonType(randomInterval(), TIME));
|
||||
|
||||
assertEquals(INTERVAL_YEAR_TO_MONTH, commonType(INTERVAL_YEAR_TO_MONTH, INTERVAL_MONTH));
|
||||
assertEquals(INTERVAL_HOUR_TO_SECOND, commonType(INTERVAL_HOUR_TO_MINUTE, INTERVAL_HOUR_TO_SECOND));
|
||||
@ -473,7 +657,7 @@ public class DataTypeConversionTests extends ESTestCase {
|
||||
public void testConversionToUnsupported() {
|
||||
Exception e = expectThrows(SqlIllegalArgumentException.class,
|
||||
() -> conversionFor(INTEGER, UNSUPPORTED));
|
||||
assertEquals("cannot convert from [INTEGER] to [UNSUPPORTED]", e.getMessage());
|
||||
assertEquals("cannot convert from [integer] to [unsupported]", e.getMessage());
|
||||
}
|
||||
|
||||
public void testStringToIp() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user