SQL: Implement DATEADD function (#47747)
Implement DATEADD/TIMESTAMPADD function as per the MS-SQL spec: https://docs.microsoft.com/en-us/sql/t-sql/functions/dateadd-transact-sql?view=sql-server-2017 which allows a user to add/subtract specified number of specified units to/from a date/datetime field/expression. Closes: #47746 (cherry picked from commit e624bc281bebb4bbe0b0c2e0a8cbc712e50097a8)
This commit is contained in:
parent
b5afa95fd8
commit
59b3294bc9
|
@ -248,6 +248,79 @@ include-tagged::{sql-specs}/docs/docs.csv-spec[filterNow]
|
|||
Currently, using a _precision_ greater than 3 doesn't make any difference to the output of the
|
||||
function as the maximum number of second fractional digits returned is 3 (milliseconds).
|
||||
|
||||
[[sql-functions-datetime-add]]
|
||||
==== `DATE_ADD/DATEADD/TIMESTAMPADD/TIMESTAMP_ADD`
|
||||
|
||||
.Synopsis:
|
||||
[source, sql]
|
||||
--------------------------------------------------
|
||||
DATE_ADD(
|
||||
string_exp, <1>
|
||||
integer_exp, <2>
|
||||
datetime_exp) <3>
|
||||
--------------------------------------------------
|
||||
|
||||
*Input*:
|
||||
|
||||
<1> string expression denoting the date/time unit to add to the date/datetime
|
||||
<2> integer expression denoting how many times the above unit should be added to/from the date/datetime,
|
||||
if a negative value is used it results to a subtraction from the date/datetime
|
||||
<3> date/datetime expression
|
||||
|
||||
*Output*: datetime
|
||||
|
||||
.Description:
|
||||
|
||||
Add the given number of date/time units to a date/datetime. If the number of units is negative then it's subtracted from
|
||||
the date/datetime. If any of the three arguments is `null` a `null` is returned.
|
||||
|
||||
[cols="^,^"]
|
||||
|===
|
||||
2+h|Datetime units to add/subtract
|
||||
|
||||
s|unit
|
||||
s|abbreviations
|
||||
|
||||
| year | years, yy, yyyy
|
||||
| quarter | quarters, qq, q
|
||||
| month | months, mm, m
|
||||
| dayofyear | dy, y
|
||||
| day | days, dd, d
|
||||
| week | weeks, wk, ww
|
||||
| weekday | weekdays, dw
|
||||
| hour | hours, hh
|
||||
| minute | minutes, mi, n
|
||||
| second | seconds, ss, s
|
||||
| millisecond | milliseconds, ms
|
||||
| microsecond | microseconds, mcs
|
||||
| nanosecond | nanoseconds, ns
|
||||
|===
|
||||
|
||||
|
||||
[source, sql]
|
||||
--------------------------------------------------
|
||||
include-tagged::{sql-specs}/docs/docs.csv-spec[dateAddDateTimeYears]
|
||||
--------------------------------------------------
|
||||
|
||||
[source, sql]
|
||||
--------------------------------------------------
|
||||
include-tagged::{sql-specs}/docs/docs.csv-spec[dateAddDateTimeWeeks]
|
||||
--------------------------------------------------
|
||||
|
||||
[source, sql]
|
||||
--------------------------------------------------
|
||||
include-tagged::{sql-specs}/docs/docs.csv-spec[dateAddDateTimeSeconds]
|
||||
--------------------------------------------------
|
||||
|
||||
[source, sql]
|
||||
--------------------------------------------------
|
||||
include-tagged::{sql-specs}/docs/docs.csv-spec[dateAddDateQuarters]
|
||||
--------------------------------------------------
|
||||
|
||||
[source, sql]
|
||||
--------------------------------------------------
|
||||
include-tagged::{sql-specs}/docs/docs.csv-spec[dateAddDateMinutes]
|
||||
--------------------------------------------------
|
||||
|
||||
[[sql-functions-datetime-part]]
|
||||
==== `DATE_PART/DATEPART`
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
** <<sql-functions-current-date>>
|
||||
** <<sql-functions-current-time>>
|
||||
** <<sql-functions-current-timestamp>>
|
||||
** <<sql-functions-datetime-add>>
|
||||
** <<sql-functions-datetime-part>>
|
||||
** <<sql-functions-datetime-trunc>>
|
||||
** <<sql-functions-datetime-day>>
|
||||
|
|
|
@ -41,8 +41,10 @@ CURRENT_DATE |SCALAR
|
|||
CURRENT_TIME |SCALAR
|
||||
CURRENT_TIMESTAMP|SCALAR
|
||||
CURTIME |SCALAR
|
||||
DATEADD |SCALAR
|
||||
DATEPART |SCALAR
|
||||
DATETRUNC |SCALAR
|
||||
DATE_ADD |SCALAR
|
||||
DATE_PART |SCALAR
|
||||
DATE_TRUNC |SCALAR
|
||||
DAY |SCALAR
|
||||
|
@ -79,8 +81,10 @@ NOW |SCALAR
|
|||
QUARTER |SCALAR
|
||||
SECOND |SCALAR
|
||||
SECOND_OF_MINUTE |SCALAR
|
||||
TIMESTAMPADD |SCALAR
|
||||
TIMESTAMP_ADD |SCALAR
|
||||
TODAY |SCALAR
|
||||
WEEK |SCALAR
|
||||
WEEK |SCALAR
|
||||
WEEK_OF_YEAR |SCALAR
|
||||
YEAR |SCALAR
|
||||
ABS |SCALAR
|
||||
|
|
|
@ -123,6 +123,160 @@ SELECT WEEK(birth_date) week, birth_date FROM test_emp WHERE WEEK(birth_date) >
|
|||
2 |1953-01-07T00:00:00.000Z
|
||||
;
|
||||
|
||||
selectAddWithDateTime
|
||||
schema::dt_year:s|dt_quarter:s|dt_month:s|dt_week:s|dt_day:s|dt_hours:s|dt_min:s|dt_sec:s|dt_millis:s|dt_mcsec:s|dt_nsec:s
|
||||
SELECT DATE_ADD('year', 10, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_year, DATE_ADD('quarter', -10, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_quarter, DATE_ADD('month', 20, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_month,
|
||||
DATE_ADD('week', -50, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_week, DATE_ADD('day', 200, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_day,
|
||||
DATE_ADD('hours', -3000, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_hours, DATE_ADD('minutes', 5678, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_min,
|
||||
DATE_ADD('second', 987654, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_sec, DATE_ADD('milliseconds', -12345678, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_millis,
|
||||
DATE_ADD('mcs', -12345678, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_mcsec, DATE_ADD('nanosecond', 12345600, '2019-09-04T11:22:33.123Z'::datetime)::string as dt_nsec;
|
||||
|
||||
dt_year | dt_quarter | dt_month | dt_week | dt_day | dt_hours | dt_min | dt_sec | dt_millis | dt_mcsec | dt_nsec
|
||||
-------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+-------------------------
|
||||
2029-09-04T11:22:33.123Z | 2017-03-04T11:22:33.123Z | 2021-05-04T11:22:33.123Z | 2018-09-19T11:22:33.123Z | 2020-03-22T11:22:33.123Z | 2019-05-02T11:22:33.123Z | 2019-09-08T10:00:33.123Z | 2019-09-15T21:43:27.123Z | 2019-09-04T07:56:47.445Z | 2019-09-04T11:22:20.777Z | 2019-09-04T11:22:33.135Z
|
||||
;
|
||||
|
||||
selectAddWithDate
|
||||
schema::dt_year:ts|dt_quarter:ts|dt_month:ts|dt_week:ts|dt_day:ts|dt_hours:ts|dt_min:ts|dt_sec:ts|dt_millis:s|dt_mcsec:s|dt_nsec:s
|
||||
SELECT DATEADD('year', 10, '2019-09-04'::date) as dt_year, DATEADD('quarter', -10, '2019-09-04'::date) as dt_quarter, DATEADD('month', 20, '2019-09-04'::date) as dt_month,
|
||||
DATEADD('week', -50, '2019-09-04'::date) as dt_week, DATEADD('day', 200, '2019-09-04'::date) as dt_day,
|
||||
DATEADD('hours', -3000, '2019-09-04'::date) as dt_hours, DATEADD('minutes', 5678, '2019-09-04'::date) as dt_min, DATEADD('second', 987654, '2019-09-04'::date) as dt_sec,
|
||||
DATEADD('milliseconds', 4314144, '2019-09-04'::date)::string as dt_millis, DATEADD('mcs', -12345678, '2019-09-04'::date)::string as dt_mcsec, DATEADD('nanosecond', 12345600, '2019-09-04'::date)::string as dt_nsec;
|
||||
|
||||
dt_year | dt_quarter | dt_month | dt_week | dt_day | dt_hours | dt_min | dt_sec | dt_millis | dt_mcsec | dt_nsec
|
||||
-------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+-------------------------
|
||||
2029-09-04 00:00:00.000Z | 2017-03-04 00:00:00.000Z | 2021-05-04 00:00:00.000Z | 2018-09-19 00:00:00.000Z | 2020-03-22 00:00:00.000Z | 2019-05-02 00:00:00.000Z | 2019-09-07 22:38:00.000Z | 2019-09-15 10:20:54.000Z | 2019-09-04T01:11:54.144Z | 2019-09-03T23:59:47.654Z | 2019-09-04T00:00:00.012Z
|
||||
;
|
||||
|
||||
selectDateAddWithField
|
||||
schema::emp_no:i|birth_date:ts|dt_year:ts|dt_quarter:ts|dt_month:ts|dt_week:ts|dt_day:ts|dt_min:ts|dt_sec:ts
|
||||
SELECT emp_no, birth_date, TIMESTAMP_ADD('year', 10, birth_date) as dt_year, TIMESTAMP_ADD('quarter', -10, birth_date) as dt_quarter,
|
||||
TIMESTAMP_ADD('month', 20, birth_date) as dt_month, TIMESTAMP_ADD('week', -20, birth_date) as dt_week, TIMESTAMP_ADD('day', 300, birth_date) as dt_day,
|
||||
TIMESTAMP_ADD('minutes', 10000, birth_date) as dt_min, TIMESTAMP_ADD('second', 100000, birth_date) as dt_sec
|
||||
FROM test_emp WHERE emp_no >= 10032 AND emp_no <= 10042 ORDER BY 1;
|
||||
|
||||
emp_no | birth_date | dt_year | dt_quarter | dt_month | dt_week | dt_day | dt_min | dt_sec
|
||||
----------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+-------------------------
|
||||
10032 | 1960-08-09 00:00:00.000Z | 1970-08-09 00:00:00.000Z | 1958-02-09 00:00:00.000Z | 1962-04-09 00:00:00.000Z | 1960-03-22 00:00:00.000Z | 1961-06-05 00:00:00.000Z | 1960-08-15 22:40:00.000Z | 1960-08-10 03:46:40.000Z
|
||||
10033 | 1956-11-14 00:00:00.000Z | 1966-11-14 00:00:00.000Z | 1954-05-14 00:00:00.000Z | 1958-07-14 00:00:00.000Z | 1956-06-27 00:00:00.000Z | 1957-09-10 00:00:00.000Z | 1956-11-20 22:40:00.000Z | 1956-11-15 03:46:40.000Z
|
||||
10034 | 1962-12-29 00:00:00.000Z | 1972-12-29 00:00:00.000Z | 1960-06-29 00:00:00.000Z | 1964-08-29 00:00:00.000Z | 1962-08-11 00:00:00.000Z | 1963-10-25 00:00:00.000Z | 1963-01-04 22:40:00.000Z | 1962-12-30 03:46:40.000Z
|
||||
10035 | 1953-02-08 00:00:00.000Z | 1963-02-08 00:00:00.000Z | 1950-08-08 00:00:00.000Z | 1954-10-08 00:00:00.000Z | 1952-09-21 00:00:00.000Z | 1953-12-05 00:00:00.000Z | 1953-02-14 22:40:00.000Z | 1953-02-09 03:46:40.000Z
|
||||
10036 | 1959-08-10 00:00:00.000Z | 1969-08-10 00:00:00.000Z | 1957-02-10 00:00:00.000Z | 1961-04-10 00:00:00.000Z | 1959-03-23 00:00:00.000Z | 1960-06-05 00:00:00.000Z | 1959-08-16 22:40:00.000Z | 1959-08-11 03:46:40.000Z
|
||||
10037 | 1963-07-22 00:00:00.000Z | 1973-07-22 00:00:00.000Z | 1961-01-22 00:00:00.000Z | 1965-03-22 00:00:00.000Z | 1963-03-04 00:00:00.000Z | 1964-05-17 00:00:00.000Z | 1963-07-28 22:40:00.000Z | 1963-07-23 03:46:40.000Z
|
||||
10038 | 1960-07-20 00:00:00.000Z | 1970-07-20 00:00:00.000Z | 1958-01-20 00:00:00.000Z | 1962-03-20 00:00:00.000Z | 1960-03-02 00:00:00.000Z | 1961-05-16 00:00:00.000Z | 1960-07-26 22:40:00.000Z | 1960-07-21 03:46:40.000Z
|
||||
10039 | 1959-10-01 00:00:00.000Z | 1969-10-01 00:00:00.000Z | 1957-04-01 00:00:00.000Z | 1961-06-01 00:00:00.000Z | 1959-05-14 00:00:00.000Z | 1960-07-27 00:00:00.000Z | 1959-10-07 22:40:00.000Z | 1959-10-02 03:46:40.000Z
|
||||
10040 | null | null | null | null | null | null | null | null
|
||||
10041 | null | null | null | null | null | null | null | null
|
||||
10042 | null | null | null | null | null | null | null | null
|
||||
;
|
||||
|
||||
selectAddWithComplexExpressions1
|
||||
SELECT gender, birth_date, TIMESTAMPADD('months', CASE WHEN gender = 'M' THEN 10 WHEN gender = 'F' THEN -10 ELSE 100 END,
|
||||
birth_date + INTERVAL 10 month) AS dt FROM test_emp WHERE dt > '1954-07-01'::date ORDER BY emp_no LIMIT 10;
|
||||
|
||||
gender:s | birth_date:ts | dt:ts
|
||||
------------+--------------------------+-------------------------
|
||||
M | 1953-09-02 00:00:00.000Z | 1955-05-02 00:00:00.000Z
|
||||
F | 1964-06-02 00:00:00.000Z | 1964-06-02 00:00:00.000Z
|
||||
M | 1959-12-03 00:00:00.000Z | 1961-08-03 00:00:00.000Z
|
||||
M | 1954-05-01 00:00:00.000Z | 1956-01-01 00:00:00.000Z
|
||||
M | 1955-01-21 00:00:00.000Z | 1956-09-21 00:00:00.000Z
|
||||
F | 1957-05-23 00:00:00.000Z | 1957-05-23 00:00:00.000Z
|
||||
M | 1958-02-19 00:00:00.000Z | 1959-10-19 00:00:00.000Z
|
||||
null | 1963-06-01 00:00:00.000Z | 1972-08-01 00:00:00.000Z
|
||||
null | 1953-11-07 00:00:00.000Z | 1963-01-07 00:00:00.000Z
|
||||
null | 1960-10-04 00:00:00.000Z | 1969-12-04 00:00:00.000Z
|
||||
;
|
||||
|
||||
selectAddWithComplexExpressions2
|
||||
schema::languages:byte|first_name:s|gender:s|hire_date:ts|date_add:date
|
||||
SELECT languages, first_name, gender, hire_date,
|
||||
CAST(DATE_ADD(CASE WHEN gender = 'M' THEN CONCAT(gender, 'onths') WHEN gender = 'F' THEN NULL ELSE 'quarter' END,
|
||||
5, hire_date + INTERVAL 10 month) AS DATE) AS date_add
|
||||
FROM test_emp WHERE languages >= 3 AND first_name LIKE '%y%' ORDER BY date_add ASC, languages DESC;
|
||||
|
||||
languages | first_name | gender | hire_date | date_add
|
||||
---------------+---------------+---------------+-------------------------+---------------
|
||||
5 | Hironoby | F | 1988-07-21 00:00:00.00Z | null
|
||||
4 | Weiyi | F | 1993-02-14 00:00:00.00Z | null
|
||||
3 | Magy | F | 1993-03-21 00:00:00.00Z | null
|
||||
4 | Jayson | M | 1990-01-14 00:00:00.00Z | 1991-04-14
|
||||
5 | Mary | null | 1990-01-22 00:00:00.00Z | 1992-02-22
|
||||
5 | Georgy | M | 1992-04-27 00:00:00.00Z | 1993-07-27
|
||||
4 | Mayumi | M | 1995-03-13 00:00:00.00Z | 1996-06-13
|
||||
;
|
||||
|
||||
selectAddWithComplexExpressions3
|
||||
schema::first_name:s|gender:s|hire_date:ts|date_add1:ts|date_add2:i
|
||||
SELECT first_name, gender, hire_date, DATE_ADD(CASE WHEN gender = 'M' THEN CONCAT(gender, 'onths') WHEN gender = 'F' THEN 'year' ELSE 'quarter' END,
|
||||
5, hire_date + INTERVAL 10 month) AS date_add1,
|
||||
YEAR(DATE_ADD(CASE WHEN gender = 'M' THEN CONCAT(gender, 'onths') WHEN gender = 'F' THEN 'year' ELSE 'quarter' END,
|
||||
5, hire_date + INTERVAL 10 month)) AS date_add2
|
||||
FROM test_emp WHERE YEAR(date_add1) > 1990 AND first_name LIKE '%y%' ORDER BY date_add1 DESC LIMIT 15;
|
||||
|
||||
first_name | gender | hire_date | date_add1 | date_add2
|
||||
---------------+---------------+-------------------------+-------------------------+------------
|
||||
Magy | F | 1993-03-21 00:00:00.00Z | 1999-01-21 00:00:00.00Z | 1999
|
||||
Weiyi | F | 1993-02-14 00:00:00.00Z | 1998-12-14 00:00:00.00Z | 1998
|
||||
Mayumi | M | 1995-03-13 00:00:00.00Z | 1996-06-13 00:00:00.00Z | 1996
|
||||
Saniya | M | 1994-09-15 00:00:00.00Z | 1995-12-15 00:00:00.00Z | 1995
|
||||
Hironoby | F | 1988-07-21 00:00:00.00Z | 1994-05-21 00:00:00.00Z | 1994
|
||||
Georgy | M | 1992-04-27 00:00:00.00Z | 1993-07-27 00:00:00.00Z | 1993
|
||||
Mayuko | M | 1991-01-26 00:00:00.00Z | 1992-04-26 00:00:00.00Z | 1992
|
||||
Mary | null | 1990-01-22 00:00:00.00Z | 1992-02-22 00:00:00.00Z | 1992
|
||||
Yishay | M | 1990-10-20 00:00:00.00Z | 1992-01-20 00:00:00.00Z | 1992
|
||||
Jayson | M | 1990-01-14 00:00:00.00Z | 1991-04-14 00:00:00.00Z | 1991
|
||||
;
|
||||
|
||||
dateAddOrderBy
|
||||
schema::emp_no:i|hire_date:ts|dt:ts
|
||||
SELECT emp_no, hire_date, DATE_ADD('hours', -4000, hire_date) as dt FROM test_emp ORDER BY dt NULLS LAST, emp_no LIMIT 5;
|
||||
|
||||
emp_no | hire_date | dt
|
||||
--------+--------------------------+-------------------------
|
||||
10009 | 1985-02-18 00:00:00.000Z | 1984-09-04 08:00:00.000Z
|
||||
10048 | 1985-02-24 00:00:00.000Z | 1984-09-10 08:00:00.000Z
|
||||
10098 | 1985-05-13 00:00:00.000Z | 1984-11-27 08:00:00.000Z
|
||||
10076 | 1985-07-09 00:00:00.000Z | 1985-01-23 08:00:00.000Z
|
||||
10061 | 1985-09-17 00:00:00.000Z | 1985-04-03 08:00:00.000Z
|
||||
;
|
||||
|
||||
dateAddFilter
|
||||
schema::emp_no:i|hire_date:ts|dt:ts
|
||||
SELECT emp_no, hire_date, DATE_ADD('quarter', 42, hire_date) as dt FROM test_emp WHERE DATE_ADD('quarter', 42, hire_date) > '2000-01-01'::date ORDER BY emp_no LIMIT 5;
|
||||
|
||||
emp_no | hire_date | dt
|
||||
--------+--------------------------+-------------------------
|
||||
10005 | 1989-09-12 00:00:00.000Z | 2000-03-12 00:00:00.000Z
|
||||
10008 | 1994-09-15 00:00:00.000Z | 2005-03-15 00:00:00.000Z
|
||||
10010 | 1989-08-24 00:00:00.000Z | 2000-02-24 00:00:00.000Z
|
||||
10011 | 1990-01-22 00:00:00.000Z | 2000-07-22 00:00:00.000Z
|
||||
10012 | 1992-12-18 00:00:00.000Z | 2003-06-18 00:00:00.000Z
|
||||
;
|
||||
|
||||
dateAddGroupBy
|
||||
schema::count:l|dt:ts
|
||||
SELECT count(*) as count, DATE_ADD('weeks', -120, hire_date) dt FROM test_emp GROUP BY dt ORDER BY 2 LIMIT 5;
|
||||
|
||||
count | dt
|
||||
--------+-------------------------
|
||||
1 | 1982-11-01 00:00:00.000Z
|
||||
1 | 1982-11-07 00:00:00.000Z
|
||||
1 | 1983-01-24 00:00:00.000Z
|
||||
1 | 1983-03-22 00:00:00.000Z
|
||||
1 | 1983-05-31 00:00:00.000Z
|
||||
;
|
||||
|
||||
dateAddHaving
|
||||
schema::gender:s|dt:ts
|
||||
SELECT gender, DATE_ADD('months', 60, max(hire_date)) AS dt FROM test_emp GROUP BY gender HAVING DATE_ADD('months', 60, max(hire_date)) >= '2002-01-01T00:00:00.000Z'::timestamp ORDER BY 1;
|
||||
|
||||
gender | dt
|
||||
--------+-------------------------
|
||||
null | 2004-04-30 00:00:00.000Z
|
||||
F | 2002-05-19 00:00:00.000Z
|
||||
;
|
||||
|
||||
selectDateTruncWithDateTime
|
||||
schema::dt_hour:ts|dt_min:ts|dt_sec:ts|dt_millis:s|dt_micro:s|dt_nano:s
|
||||
SELECT DATE_TRUNC('hour', '2019-09-04T11:22:33.123Z'::datetime) as dt_hour, DATE_TRUNC('minute', '2019-09-04T11:22:33.123Z'::datetime) as dt_min,
|
||||
|
|
|
@ -237,8 +237,10 @@ CURRENT_DATE |SCALAR
|
|||
CURRENT_TIME |SCALAR
|
||||
CURRENT_TIMESTAMP|SCALAR
|
||||
CURTIME |SCALAR
|
||||
DATEADD |SCALAR
|
||||
DATEPART |SCALAR
|
||||
DATETRUNC |SCALAR
|
||||
DATE_ADD |SCALAR
|
||||
DATE_PART |SCALAR
|
||||
DATE_TRUNC |SCALAR
|
||||
DAY |SCALAR
|
||||
|
@ -275,8 +277,10 @@ NOW |SCALAR
|
|||
QUARTER |SCALAR
|
||||
SECOND |SCALAR
|
||||
SECOND_OF_MINUTE |SCALAR
|
||||
TIMESTAMPADD |SCALAR
|
||||
TIMESTAMP_ADD |SCALAR
|
||||
TODAY |SCALAR
|
||||
WEEK |SCALAR
|
||||
WEEK |SCALAR
|
||||
WEEK_OF_YEAR |SCALAR
|
||||
YEAR |SCALAR
|
||||
ABS |SCALAR
|
||||
|
@ -2417,6 +2421,56 @@ SELECT DAY_OF_MONTH(CAST('2018-02-19T10:23:27Z' AS TIMESTAMP)) AS day;
|
|||
// end::dayOfMonth
|
||||
;
|
||||
|
||||
dateAddDateTimeYears
|
||||
// tag::dateAddDateTimeYears
|
||||
SELECT DATE_ADD('years', 10, '2019-09-04T11:22:33.000Z'::datetime) AS "+10 years";
|
||||
|
||||
+10 years
|
||||
------------------------
|
||||
2029-09-04T11:22:33.000Z
|
||||
// end::dateAddDateTimeYears
|
||||
;
|
||||
|
||||
dateAddDateTimeWeeks
|
||||
// tag::dateAddDateTimeWeeks
|
||||
SELECT DATE_ADD('week', 10, '2019-09-04T11:22:33.000Z'::datetime) AS "+10 weeks";
|
||||
|
||||
+10 weeks
|
||||
------------------------
|
||||
2019-11-13T11:22:33.000Z
|
||||
// end::dateAddDateTimeWeeks
|
||||
;
|
||||
|
||||
dateAddDateTimeSeconds
|
||||
// tag::dateAddDateTimeSeconds
|
||||
SELECT DATE_ADD('seconds', -1234, '2019-09-04T11:22:33.000Z'::datetime) AS "-1234 seconds";
|
||||
|
||||
-1234 seconds
|
||||
------------------------
|
||||
2019-09-04T11:01:59.000Z
|
||||
// end::dateAddDateTimeSeconds
|
||||
;
|
||||
|
||||
dateAddDateQuarters
|
||||
// tag::dateAddDateQuarters
|
||||
SELECT DATE_ADD('qq', -417, '2019-09-04'::date) AS "-417 quarters";
|
||||
|
||||
-417 quarters
|
||||
------------------------
|
||||
1915-06-04T00:00:00.000Z
|
||||
// end::dateAddDateQuarters
|
||||
;
|
||||
|
||||
dateAddDateMinutes
|
||||
// tag::dateAddDateMinutes
|
||||
SELECT DATE_ADD('minutes', 9235, '2019-09-04'::date) AS "+9235 minutes";
|
||||
|
||||
+9235 minutes
|
||||
------------------------
|
||||
2019-09-10T09:55:00.000Z
|
||||
// end::dateAddDateMinutes
|
||||
;
|
||||
|
||||
datePartDateTimeYears
|
||||
// tag::datePartDateTimeYears
|
||||
SELECT DATE_PART('year', '2019-09-22T11:22:33.123Z'::datetime) AS "years";
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.User;
|
|||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.CurrentDate;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.CurrentDateTime;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.CurrentTime;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateAdd;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DatePart;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTrunc;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayName;
|
||||
|
@ -195,6 +196,7 @@ public class FunctionRegistry {
|
|||
def(DayOfMonth.class, DayOfMonth::new, "DAY_OF_MONTH", "DAYOFMONTH", "DAY", "DOM"),
|
||||
def(DayOfWeek.class, DayOfWeek::new, "DAY_OF_WEEK", "DAYOFWEEK", "DOW"),
|
||||
def(DayOfYear.class, DayOfYear::new, "DAY_OF_YEAR", "DAYOFYEAR", "DOY"),
|
||||
def(DateAdd.class, DateAdd::new, "DATEADD", "DATE_ADD", "TIMESTAMPADD", "TIMESTAMP_ADD"),
|
||||
def(DatePart.class, DatePart::new, "DATEPART", "DATE_PART"),
|
||||
def(DateTrunc.class, DateTrunc::new, "DATETRUNC", "DATE_TRUNC"),
|
||||
def(HourOfDay.class, HourOfDay::new, "HOUR_OF_DAY", "HOUR"),
|
||||
|
@ -377,7 +379,7 @@ public class FunctionRegistry {
|
|||
};
|
||||
return def(function, builder, false, names);
|
||||
}
|
||||
|
||||
|
||||
interface ConfigurationAwareFunctionBuilder<T> {
|
||||
T build(Source source, Configuration configuration);
|
||||
}
|
||||
|
@ -446,7 +448,7 @@ public class FunctionRegistry {
|
|||
interface MultiFunctionBuilder<T> {
|
||||
T build(Source source, List<Expression> children);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a {@linkplain FunctionDefinition} for a unary function that is not
|
||||
* aware of time zone but does support {@code DISTINCT}.
|
||||
|
@ -512,6 +514,28 @@ public class FunctionRegistry {
|
|||
T build(Source source, Expression lhs, Expression rhs, ZoneId zi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@linkplain FunctionDefinition} for a three-args function that
|
||||
* requires a timezone.
|
||||
*/
|
||||
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
|
||||
static <T extends Function> FunctionDefinition def(Class<T> function, DatetimeThreeArgsFunctionBuilder<T> ctorRef, String... names) {
|
||||
FunctionBuilder builder = (source, children, distinct, cfg) -> {
|
||||
if (children.size() != 3) {
|
||||
throw new SqlIllegalArgumentException("expects three arguments");
|
||||
}
|
||||
if (distinct) {
|
||||
throw new SqlIllegalArgumentException("does not support DISTINCT yet it was specified");
|
||||
}
|
||||
return ctorRef.build(source, children.get(0), children.get(1), children.get(2), cfg.zoneId());
|
||||
};
|
||||
return def(function, builder, false, names);
|
||||
}
|
||||
|
||||
interface DatetimeThreeArgsFunctionBuilder<T> {
|
||||
T build(Source source, Expression first, Expression second, Expression third, ZoneId zi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@linkplain FunctionDefinition} for a binary function that is
|
||||
* not aware of time zone and does not support {@code DISTINCT}.
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.expression.function.scalar;
|
|||
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateAddProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DatePartProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTruncProcessor;
|
||||
|
@ -90,6 +91,7 @@ public final class Processors {
|
|||
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));
|
||||
entries.add(new Entry(Processor.class, DateAddProcessor.NAME, DateAddProcessor::new));
|
||||
entries.add(new Entry(Processor.class, DatePartProcessor.NAME, DatePartProcessor::new));
|
||||
entries.add(new Entry(Processor.class, DateTruncProcessor.NAME, DateTruncProcessor::new));
|
||||
// math
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* 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.Nullability;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.type.DataType;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isDate;
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isInteger;
|
||||
import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isString;
|
||||
|
||||
public class DateAdd extends ThreeArgsDateTimeFunction {
|
||||
|
||||
public enum Part implements DateTimeField {
|
||||
YEAR((dt, i) -> dt.plus(i, ChronoUnit.YEARS), "years", "yyyy", "yy"),
|
||||
QUARTER((dt, i) -> dt.plus(i * 3, ChronoUnit.MONTHS), "quarters", "qq", "q"),
|
||||
MONTH((dt, i) -> dt.plus(i, ChronoUnit.MONTHS), "months", "mm", "m"),
|
||||
DAYOFYEAR((dt, i) -> dt.plus(i, ChronoUnit.DAYS), "dy", "y"),
|
||||
DAY((dt, i) -> dt.plus(i, ChronoUnit.DAYS), "days", "dd", "d"),
|
||||
WEEK((dt, i) -> dt.plus(i, ChronoUnit.WEEKS), "weeks", "wk", "ww"),
|
||||
WEEKDAY((dt, i) -> dt.plus(i, ChronoUnit.DAYS), "weekdays", "dw"),
|
||||
HOUR((dt, i) -> dt.plus(i, ChronoUnit.HOURS), "hours", "hh"),
|
||||
MINUTE((dt, i) -> dt.plus(i, ChronoUnit.MINUTES), "minutes", "mi", "n"),
|
||||
SECOND((dt, i) -> dt.plus(i, ChronoUnit.SECONDS), "seconds", "ss", "s"),
|
||||
MILLISECOND((dt, i) -> dt.plus(i, ChronoUnit.MILLIS), "milliseconds", "ms"),
|
||||
MICROSECOND((dt, i) -> dt.plus(i, ChronoUnit.MICROS), "microseconds", "mcs"),
|
||||
NANOSECOND((dt, i) -> dt.plus(i, ChronoUnit.NANOS), "nanoseconds", "ns");
|
||||
|
||||
private static final Map<String, Part> NAME_TO_PART;
|
||||
private static final List<String> VALID_VALUES;
|
||||
|
||||
static {
|
||||
NAME_TO_PART = DateTimeField.initializeResolutionMap(values());
|
||||
VALID_VALUES = DateTimeField.initializeValidValues(values());
|
||||
}
|
||||
|
||||
private BiFunction<ZonedDateTime, Integer, ZonedDateTime> addFunction;
|
||||
private Set<String> aliases;
|
||||
|
||||
Part(BiFunction<ZonedDateTime, Integer, ZonedDateTime> addFunction, String... aliases) {
|
||||
this.addFunction = addFunction;
|
||||
this.aliases = new HashSet<>(Arrays.asList(aliases));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> aliases() {
|
||||
return aliases;
|
||||
}
|
||||
|
||||
public static List<String> findSimilar(String match) {
|
||||
return DateTimeField.findSimilar(NAME_TO_PART.keySet(), match);
|
||||
}
|
||||
|
||||
public static Part resolve(String truncateTo) {
|
||||
return DateTimeField.resolveMatch(NAME_TO_PART, truncateTo);
|
||||
}
|
||||
|
||||
public ZonedDateTime add(ZonedDateTime dateTime, Integer numberOfUnits) {
|
||||
return addFunction.apply(dateTime, numberOfUnits);
|
||||
}
|
||||
}
|
||||
|
||||
public DateAdd(Source source, Expression unit, Expression numberOfUnits, Expression timestamp, ZoneId zoneId) {
|
||||
super(source, unit, numberOfUnits, timestamp, zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TypeResolution resolveType() {
|
||||
TypeResolution resolution = isString(first(), sourceText(), Expressions.ParamOrdinal.FIRST);
|
||||
if (resolution.unresolved()) {
|
||||
return resolution;
|
||||
}
|
||||
|
||||
if (first().foldable()) {
|
||||
String datePartValue = (String) first().fold();
|
||||
if (datePartValue != null && resolveDateTimeField(datePartValue) == false) {
|
||||
List<String> similar = findSimilarDateTimeFields(datePartValue);
|
||||
if (similar.isEmpty()) {
|
||||
return new TypeResolution(format(null, "first argument of [{}] must be one of {} or their aliases; found value [{}]",
|
||||
sourceText(),
|
||||
validDateTimeFieldValues(),
|
||||
Expressions.name(first())));
|
||||
} else {
|
||||
return new TypeResolution(format(null, "Unknown value [{}] for first argument of [{}]; did you mean {}?",
|
||||
Expressions.name(first()),
|
||||
sourceText(),
|
||||
similar));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolution = isInteger(second(), sourceText(), Expressions.ParamOrdinal.SECOND);
|
||||
if (resolution.unresolved()) {
|
||||
return resolution;
|
||||
}
|
||||
|
||||
resolution = isDate(third(), sourceText(), Expressions.ParamOrdinal.THIRD);
|
||||
if (resolution.unresolved()) {
|
||||
return resolution;
|
||||
}
|
||||
|
||||
return TypeResolution.TYPE_RESOLVED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType dataType() {
|
||||
return DataType.DATETIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreeArgsDateTimeFunction replaceChildren(Expression newFirst, Expression newSecond, Expression newThird) {
|
||||
return new DateAdd(source(), newFirst, newSecond, newThird, zoneId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NodeInfo<? extends Expression> info() {
|
||||
return NodeInfo.create(this, DateAdd::new, first(), second(), third(), zoneId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Nullability nullable() {
|
||||
return Nullability.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pipe createPipe(Pipe first, Pipe second, Pipe third, ZoneId zoneId) {
|
||||
return new DateAddPipe(source(), this, first, second, third, zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String scriptMethodName() {
|
||||
return "dateAdd";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fold() {
|
||||
return DateAddProcessor.process(first().fold(), second().fold(), third().fold(), zoneId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean resolveDateTimeField(String dateTimeField) {
|
||||
return Part.resolve(dateTimeField) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> findSimilarDateTimeFields(String dateTimeField) {
|
||||
return Part.findSimilar(dateTimeField);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> validDateTimeFieldValues() {
|
||||
return Part.VALID_VALUES;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.gen.pipeline.Pipe;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.ZoneId;
|
||||
|
||||
public class DateAddPipe extends ThreeArgsDateTimePipe {
|
||||
|
||||
public DateAddPipe(Source source, Expression expression, Pipe first, Pipe second, Pipe third, ZoneId zoneId) {
|
||||
super(source, expression, first, second, third, zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NodeInfo<DateAddPipe> info() {
|
||||
return NodeInfo.create(this, DateAddPipe::new, expression(), first(), second(), third(), zoneId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreeArgsDateTimePipe replaceChildren(Pipe newFirst, Pipe newSecond, Pipe newThird) {
|
||||
return new DateAddPipe(source(), expression(), newFirst, newSecond, newThird, zoneId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Processor makeProcessor(Processor first, Processor second, Processor third, ZoneId zoneId) {
|
||||
return new DateAddProcessor(first, second, third, zoneId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateAdd.Part;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public class DateAddProcessor extends ThreeArgsDateTimeProcessor {
|
||||
|
||||
public static final String NAME = "dtadd";
|
||||
|
||||
public DateAddProcessor(Processor unit, Processor numberOfUnits, Processor timestamp, ZoneId zoneId) {
|
||||
super(unit, numberOfUnits, timestamp, zoneId);
|
||||
}
|
||||
|
||||
public DateAddProcessor(StreamInput in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWriteableName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doProcess(Object unit, Object numberOfUnits, Object timestamp, ZoneId zoneId) {
|
||||
return process(unit, numberOfUnits, timestamp, zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in Painless scripting
|
||||
*/
|
||||
public static Object process(Object unit, Object numberOfUnits, Object timestamp, ZoneId zoneId) {
|
||||
if (unit == null || numberOfUnits == null || timestamp == null) {
|
||||
return null;
|
||||
}
|
||||
if (unit instanceof String == false) {
|
||||
throw new SqlIllegalArgumentException("A string is required; received [{}]", unit);
|
||||
}
|
||||
Part datePartField = Part.resolve((String) unit);
|
||||
if (datePartField == null) {
|
||||
List<String> similar = Part.findSimilar((String) unit);
|
||||
if (similar.isEmpty()) {
|
||||
throw new SqlIllegalArgumentException("A value of {} or their aliases is required; received [{}]",
|
||||
Part.values(), unit);
|
||||
} else {
|
||||
throw new SqlIllegalArgumentException("Received value [{}] is not valid date part to add; " +
|
||||
"did you mean {}?", unit, similar);
|
||||
}
|
||||
}
|
||||
|
||||
if (numberOfUnits instanceof Integer == false) {
|
||||
throw new SqlIllegalArgumentException("An integer is required; received [{}]", numberOfUnits);
|
||||
}
|
||||
|
||||
if (timestamp instanceof ZonedDateTime == false) {
|
||||
throw new SqlIllegalArgumentException("A date/datetime is required; received [{}]", timestamp);
|
||||
}
|
||||
|
||||
return datePartField.add(((ZonedDateTime) timestamp).withZoneSameInstant(zoneId), (Integer) numberOfUnits);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.Nullability;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
|
||||
|
||||
public abstract class ThreeArgsDateTimeFunction extends ScalarFunction {
|
||||
|
||||
private final ZoneId zoneId;
|
||||
|
||||
public ThreeArgsDateTimeFunction(Source source, Expression first, Expression second, Expression third, ZoneId zoneId) {
|
||||
super(source, Arrays.asList(first, second, third));
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public Expression first() {
|
||||
return arguments().get(0);
|
||||
}
|
||||
|
||||
public Expression second() {
|
||||
return arguments().get(1);
|
||||
}
|
||||
|
||||
public Expression third() {
|
||||
return arguments().get(2);
|
||||
}
|
||||
|
||||
public ZoneId zoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
protected abstract boolean resolveDateTimeField(String dateTimeField);
|
||||
|
||||
protected abstract List<String> findSimilarDateTimeFields(String dateTimeField);
|
||||
|
||||
protected abstract List<String> validDateTimeFieldValues();
|
||||
|
||||
@Override
|
||||
public final ThreeArgsDateTimeFunction replaceChildren(List<Expression> newChildren) {
|
||||
if (newChildren.size() != 3) {
|
||||
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
|
||||
}
|
||||
return replaceChildren(newChildren.get(0), newChildren.get(1), newChildren.get(2));
|
||||
}
|
||||
|
||||
protected abstract ThreeArgsDateTimeFunction replaceChildren(Expression newFirst, Expression newSecond, Expression newThird);
|
||||
|
||||
@Override
|
||||
protected Pipe makePipe() {
|
||||
return createPipe(Expressions.pipe(first()), Expressions.pipe(second()), Expressions.pipe(third()), zoneId);
|
||||
}
|
||||
|
||||
protected abstract Pipe createPipe(Pipe first, Pipe second, Pipe third, ZoneId zoneId);
|
||||
|
||||
@Override
|
||||
public Nullability nullable() {
|
||||
return Nullability.TRUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean foldable() {
|
||||
return first().foldable() && second().foldable() && third().foldable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptTemplate asScript() {
|
||||
ScriptTemplate firstScript = asScript(first());
|
||||
ScriptTemplate secondScript = asScript(second());
|
||||
ScriptTemplate thirdScript = asScript(third());
|
||||
|
||||
return asScriptFrom(firstScript, secondScript, thirdScript);
|
||||
}
|
||||
|
||||
protected ScriptTemplate asScriptFrom(ScriptTemplate firstScript, ScriptTemplate secondScript, ScriptTemplate thirdScript) {
|
||||
return new ScriptTemplate(
|
||||
formatTemplate("{sql}." + scriptMethodName() +
|
||||
"(" + firstScript.template() + "," + secondScript.template() + "," + thirdScript.template() + ",{})"),
|
||||
paramsBuilder()
|
||||
.script(firstScript.params())
|
||||
.script(secondScript.params())
|
||||
.script(thirdScript.params())
|
||||
.variable(zoneId.getId())
|
||||
.build(),
|
||||
dataType());
|
||||
}
|
||||
|
||||
protected String scriptMethodName() {
|
||||
return getClass().getSimpleName().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
ThreeArgsDateTimeFunction that = (ThreeArgsDateTimeFunction) o;
|
||||
return zoneId.equals(that.zoneId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.gen.pipeline.Pipe;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class ThreeArgsDateTimePipe extends Pipe {
|
||||
|
||||
private final Pipe first, second, third;
|
||||
private final ZoneId zoneId;
|
||||
|
||||
public ThreeArgsDateTimePipe(Source source, Expression expression, Pipe first, Pipe second, Pipe third, ZoneId zoneId) {
|
||||
super(source, expression, Arrays.asList(first, second, third));
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
this.third = third;
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public Pipe first() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public Pipe second() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public Pipe third() {
|
||||
return third;
|
||||
}
|
||||
|
||||
ZoneId zoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Pipe replaceChildren(List<Pipe> newChildren) {
|
||||
if (newChildren.size() != 3) {
|
||||
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
|
||||
}
|
||||
return replaceChildren(newChildren.get(0), newChildren.get(1), newChildren.get(2));
|
||||
}
|
||||
|
||||
public abstract Pipe replaceChildren(Pipe newFirst, Pipe newSecond, Pipe newThird);
|
||||
|
||||
@Override
|
||||
public Processor asProcessor() {
|
||||
return makeProcessor(first.asProcessor(), second.asProcessor(), third.asProcessor(), zoneId);
|
||||
}
|
||||
|
||||
protected abstract Processor makeProcessor(Processor first, Processor second, Processor third, ZoneId zoneId);
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), first, second, third, zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
ThreeArgsDateTimePipe that = (ThreeArgsDateTimePipe) o;
|
||||
return first.equals(that.first) &&
|
||||
second.equals(that.second) &&
|
||||
third.equals(that.third) &&
|
||||
zoneId.equals(that.zoneId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.xpack.sql.common.io.SqlStreamInput;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class ThreeArgsDateTimeProcessor implements Processor {
|
||||
|
||||
private final Processor first, second, third;
|
||||
private final ZoneId zoneId;
|
||||
|
||||
public ThreeArgsDateTimeProcessor(Processor first, Processor second, Processor third, ZoneId zoneId) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
this.third = third;
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
protected ThreeArgsDateTimeProcessor(StreamInput in) throws IOException {
|
||||
this.first = in.readNamedWriteable(Processor.class);
|
||||
this.second = in.readNamedWriteable(Processor.class);
|
||||
this.third = in.readNamedWriteable(Processor.class);
|
||||
zoneId = SqlStreamInput.asSqlStream(in).zoneId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeNamedWriteable(first);
|
||||
out.writeNamedWriteable(second);
|
||||
out.writeNamedWriteable(third);
|
||||
}
|
||||
|
||||
public Processor first() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public Processor second() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public Processor third() {
|
||||
return third;
|
||||
}
|
||||
|
||||
ZoneId zoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object process(Object input) {
|
||||
Object o1 = first().process(input);
|
||||
if (o1 == null) {
|
||||
return null;
|
||||
}
|
||||
Object o2 = second().process(input);
|
||||
if (o2 == null) {
|
||||
return null;
|
||||
}
|
||||
Object o3 = third().process(input);
|
||||
if (o3 == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return doProcess(o1, o2, o3, zoneId());
|
||||
}
|
||||
|
||||
public abstract Object doProcess(Object o1, Object o2, Object o3, ZoneId zoneId);
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(first, second, third, zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ThreeArgsDateTimeProcessor that = (ThreeArgsDateTimeProcessor) o;
|
||||
return Objects.equals(first, that.first) &&
|
||||
Objects.equals(second, that.second) &&
|
||||
Objects.equals(third, that.third) &&
|
||||
Objects.equals(zoneId, that.zoneId);
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import org.elasticsearch.common.geo.GeoPoint;
|
|||
import org.elasticsearch.index.fielddata.ScriptDocValues;
|
||||
import org.elasticsearch.script.JodaCompatibleZonedDateTime;
|
||||
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateAddProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DatePartProcessor;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTruncProcessor;
|
||||
|
@ -235,7 +236,7 @@ public final class InternalSqlScriptUtils {
|
|||
public static Double atan(Number value) {
|
||||
return MathOperation.ATAN.apply(value);
|
||||
}
|
||||
|
||||
|
||||
public static Number atan2(Number left, Number right) {
|
||||
return BinaryMathOperation.ATAN2.apply(left, right);
|
||||
}
|
||||
|
@ -291,7 +292,7 @@ public final class InternalSqlScriptUtils {
|
|||
public static Double pi(Number value) {
|
||||
return MathOperation.PI.apply(value);
|
||||
}
|
||||
|
||||
|
||||
public static Number power(Number left, Number right) {
|
||||
return BinaryMathOperation.POWER.apply(left, right);
|
||||
}
|
||||
|
@ -350,7 +351,7 @@ public final class InternalSqlScriptUtils {
|
|||
}
|
||||
return NonIsoDateTimeExtractor.DAY_OF_WEEK.extract(asDateTime(dateTime), tzId);
|
||||
}
|
||||
|
||||
|
||||
public static String monthName(Object dateTime, String tzId) {
|
||||
if (dateTime == null || tzId == null) {
|
||||
return null;
|
||||
|
@ -364,7 +365,7 @@ public final class InternalSqlScriptUtils {
|
|||
}
|
||||
return QuarterProcessor.quarter(asDateTime(dateTime), tzId);
|
||||
}
|
||||
|
||||
|
||||
public static Integer weekOfYear(Object dateTime, String tzId) {
|
||||
if (dateTime == null || tzId == null) {
|
||||
return null;
|
||||
|
@ -372,6 +373,10 @@ public final class InternalSqlScriptUtils {
|
|||
return NonIsoDateTimeExtractor.WEEK_OF_YEAR.extract(asDateTime(dateTime), tzId);
|
||||
}
|
||||
|
||||
public static ZonedDateTime dateAdd(String dateField, Integer numberOfUnits, Object dateTime, String tzId) {
|
||||
return (ZonedDateTime) DateAddProcessor.process(dateField, numberOfUnits, asDateTime(dateTime) , ZoneId.of(tzId));
|
||||
}
|
||||
|
||||
public static ZonedDateTime dateTrunc(String truncateTo, Object dateTime, String tzId) {
|
||||
return (ZonedDateTime) DateTruncProcessor.process(truncateTo, asDateTime(dateTime) , ZoneId.of(tzId));
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS
|
|||
String monthName(Object, String)
|
||||
Integer quarter(Object, String)
|
||||
Integer weekOfYear(Object, String)
|
||||
ZonedDateTime dateAdd(String, Integer, Object, String)
|
||||
ZonedDateTime dateTrunc(String, Object, String)
|
||||
Integer datePart(String, Object, String)
|
||||
IntervalDayTime intervalDayTime(String, String)
|
||||
|
|
|
@ -256,6 +256,34 @@ public class VerifierErrorMessagesTests extends ESTestCase {
|
|||
accept("SELECT DATE_PART('ms', date) FROM test");
|
||||
}
|
||||
|
||||
public void testDateAddInvalidArgs() {
|
||||
assertEquals("1:8: first argument of [DATE_ADD(int, int, date)] must be [string], found value [int] type [integer]",
|
||||
error("SELECT DATE_ADD(int, int, date) FROM test"));
|
||||
assertEquals("1:8: second argument of [DATE_ADD(keyword, keyword, date)] must be [integer], found value [keyword] " +
|
||||
"type [keyword]", error("SELECT DATE_ADD(keyword, keyword, date) FROM test"));
|
||||
assertEquals("1:8: third argument of [DATE_ADD(keyword, int, keyword)] must be [date or datetime], found value [keyword] " +
|
||||
"type [keyword]", error("SELECT DATE_ADD(keyword, int, keyword) FROM test"));
|
||||
assertEquals("1:8: first argument of [DATE_ADD('invalid', int, date)] must be one of [YEAR, QUARTER, MONTH, DAYOFYEAR, " +
|
||||
"DAY, WEEK, WEEKDAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND] " +
|
||||
"or their aliases; found value ['invalid']",
|
||||
error("SELECT DATE_ADD('invalid', int, date) FROM test"));
|
||||
assertEquals("1:8: Unknown value ['sacinds'] for first argument of [DATE_ADD('sacinds', int, date)]; " +
|
||||
"did you mean [seconds, second]?",
|
||||
error("SELECT DATE_ADD('sacinds', int, date) FROM test"));
|
||||
assertEquals("1:8: Unknown value ['dz'] for first argument of [DATE_ADD('dz', int, date)]; " +
|
||||
"did you mean [dd, dw, dy, d]?",
|
||||
error("SELECT DATE_ADD('dz', int, date) FROM test"));
|
||||
}
|
||||
|
||||
public void testDateAddValidArgs() {
|
||||
accept("SELECT DATE_ADD('weekday', 0, date) FROM test");
|
||||
accept("SELECT DATE_ADD('dw', 20, date) FROM test");
|
||||
accept("SELECT DATE_ADD('years', -10, date) FROM test");
|
||||
accept("SELECT DATE_ADD('dayofyear', 123, date) FROM test");
|
||||
accept("SELECT DATE_ADD('dy', 30, date) FROM test");
|
||||
accept("SELECT DATE_ADD('ms', 1, date::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");
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* 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.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.sql.expression.Expression;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
|
||||
import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.tree.SourceTests;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.Expressions.pipe;
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomDatetimeLiteral;
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomIntLiteral;
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
|
||||
import static org.elasticsearch.xpack.sql.tree.SourceTests.randomSource;
|
||||
|
||||
public class DateAddPipeTests extends AbstractNodeTestCase<DateAddPipe, Pipe> {
|
||||
|
||||
@Override
|
||||
protected DateAddPipe randomInstance() {
|
||||
return randomDateAddPipe();
|
||||
}
|
||||
|
||||
private Expression randomDateAddPipeExpression() {
|
||||
return randomDateAddPipe().expression();
|
||||
}
|
||||
|
||||
public static DateAddPipe randomDateAddPipe() {
|
||||
return (DateAddPipe) new DateAdd(
|
||||
randomSource(),
|
||||
randomStringLiteral(),
|
||||
randomIntLiteral(),
|
||||
randomDatetimeLiteral(),
|
||||
randomZone())
|
||||
.makePipe();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testTransform() {
|
||||
// test transforming only the properties (source, expression),
|
||||
// skipping the children (the three parameters of the function) which are tested separately
|
||||
DateAddPipe b1 = randomInstance();
|
||||
|
||||
Expression newExpression = randomValueOtherThan(b1.expression(), this::randomDateAddPipeExpression);
|
||||
DateAddPipe newB = new DateAddPipe(
|
||||
b1.source(),
|
||||
newExpression,
|
||||
b1.first(),
|
||||
b1.second(),
|
||||
b1.third(),
|
||||
b1.zoneId());
|
||||
assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
|
||||
|
||||
DateAddPipe b2 = randomInstance();
|
||||
Source newLoc = randomValueOtherThan(b2.source(), SourceTests::randomSource);
|
||||
newB = new DateAddPipe(
|
||||
newLoc,
|
||||
b2.expression(),
|
||||
b2.first(),
|
||||
b2.second(),
|
||||
b2.third(),
|
||||
b2.zoneId());
|
||||
assertEquals(newB,
|
||||
b2.transformPropertiesOnly(v -> Objects.equals(v, b2.source()) ? newLoc : v, Source.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testReplaceChildren() {
|
||||
DateAddPipe b = randomInstance();
|
||||
Pipe newFirst = pipe(((Expression) randomValueOtherThan(b.first(), FunctionTestUtils::randomStringLiteral)));
|
||||
Pipe newSecond = pipe(((Expression) randomValueOtherThan(b.second(), FunctionTestUtils::randomIntLiteral)));
|
||||
Pipe newThird = pipe(((Expression) randomValueOtherThan(b.third(), FunctionTestUtils::randomDatetimeLiteral)));
|
||||
ZoneId newZoneId = randomValueOtherThan(b.zoneId(), ESTestCase::randomZone);
|
||||
DateAddPipe newB = new DateAddPipe( b.source(), b.expression(), b.first(), b.second(), b.third(), newZoneId);
|
||||
|
||||
ThreeArgsDateTimePipe transformed = newB.replaceChildren(newFirst, b.second(), b.third());
|
||||
assertEquals(transformed.source(), b.source());
|
||||
assertEquals(transformed.expression(), b.expression());
|
||||
assertEquals(transformed.first(), newFirst);
|
||||
assertEquals(transformed.second(), b.second());
|
||||
|
||||
transformed = newB.replaceChildren(b.first(), newSecond, b.third());
|
||||
assertEquals(transformed.source(), b.source());
|
||||
assertEquals(transformed.expression(), b.expression());
|
||||
assertEquals(transformed.first(), b.first());
|
||||
assertEquals(transformed.second(), newSecond);
|
||||
|
||||
transformed = newB.replaceChildren(b.first(), b.second(), newThird);
|
||||
assertEquals(transformed.expression(), b.expression());
|
||||
assertEquals(transformed.source(), b.source());
|
||||
assertEquals(transformed.first(), b.first());
|
||||
assertEquals(transformed.second(), b.second());
|
||||
assertEquals(transformed.third(), newThird);
|
||||
|
||||
transformed = newB.replaceChildren(newFirst, newSecond, newThird);
|
||||
assertEquals(transformed.source(), b.source());
|
||||
assertEquals(transformed.expression(), b.expression());
|
||||
assertEquals(transformed.first(), newFirst);
|
||||
assertEquals(transformed.second(), newSecond);
|
||||
assertEquals(transformed.third(), newThird);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DateAddPipe mutate(DateAddPipe instance) {
|
||||
List<Function<DateAddPipe, DateAddPipe>> randoms = new ArrayList<>();
|
||||
randoms.add(f -> new DateAddPipe(f.source(), f.expression(),
|
||||
pipe(((Expression) randomValueOtherThan(f.first(), FunctionTestUtils::randomStringLiteral))),
|
||||
f.second(),
|
||||
f.third(),
|
||||
randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
|
||||
randoms.add(f -> new DateAddPipe(f.source(), f.expression(),
|
||||
f.first(),
|
||||
pipe(((Expression) randomValueOtherThan(f.second(), FunctionTestUtils::randomIntLiteral))),
|
||||
f.third(),
|
||||
randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
|
||||
randoms.add(f -> new DateAddPipe(f.source(), f.expression(),
|
||||
f.first(),
|
||||
f.second(),
|
||||
pipe(((Expression) randomValueOtherThan(f.third(), FunctionTestUtils::randomDatetimeLiteral))),
|
||||
randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
|
||||
randoms.add(f -> new DateAddPipe(f.source(), f.expression(),
|
||||
pipe(((Expression) randomValueOtherThan(f.first(), FunctionTestUtils::randomStringLiteral))),
|
||||
pipe(((Expression) randomValueOtherThan(f.second(), FunctionTestUtils::randomIntLiteral))),
|
||||
pipe(((Expression) randomValueOtherThan(f.third(), FunctionTestUtils::randomDatetimeLiteral))),
|
||||
randomValueOtherThan(f.zoneId(), ESTestCase::randomZone)));
|
||||
|
||||
return randomFrom(randoms).apply(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DateAddPipe copy(DateAddPipe instance) {
|
||||
return new DateAddPipe(
|
||||
instance.source(),
|
||||
instance.expression(),
|
||||
instance.first(),
|
||||
instance.second(),
|
||||
instance.third(),
|
||||
instance.zoneId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* 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.ESTestCase;
|
||||
import org.elasticsearch.xpack.sql.AbstractSqlWireSerializingTestCase;
|
||||
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
||||
import org.elasticsearch.xpack.sql.expression.Literal;
|
||||
import org.elasticsearch.xpack.sql.expression.gen.processor.ConstantProcessor;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.expression.Literal.NULL;
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.l;
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomDatetimeLiteral;
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomIntLiteral;
|
||||
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeTestUtils.dateTime;
|
||||
import static org.elasticsearch.xpack.sql.proto.StringUtils.ISO_DATE_WITH_NANOS;
|
||||
|
||||
public class DateAddProcessorTests extends AbstractSqlWireSerializingTestCase<DateAddProcessor> {
|
||||
|
||||
public static DateAddProcessor randomDateAddProcessor() {
|
||||
return new DateAddProcessor(
|
||||
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)),
|
||||
new ConstantProcessor(randomInt()),
|
||||
new ConstantProcessor(DateTimeTestUtils.nowWithMillisResolution()),
|
||||
randomZone());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DateAddProcessor createTestInstance() {
|
||||
return randomDateAddProcessor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Reader<DateAddProcessor> instanceReader() {
|
||||
return DateAddProcessor::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ZoneId instanceZoneId(DateAddProcessor instance) {
|
||||
return instance.zoneId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DateAddProcessor mutateInstance(DateAddProcessor instance) {
|
||||
return new DateAddProcessor(
|
||||
new ConstantProcessor(ESTestCase.randomRealisticUnicodeOfLength(128)),
|
||||
new ConstantProcessor(randomValueOtherThan((Integer) instance.second().process(null), ESTestCase::randomInt)),
|
||||
new ConstantProcessor(DateTimeTestUtils.nowWithMillisResolution()),
|
||||
randomValueOtherThan(instance.zoneId(), ESTestCase::randomZone));
|
||||
}
|
||||
|
||||
public void testInvalidInputs() {
|
||||
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
|
||||
() -> new DateAdd(Source.EMPTY,
|
||||
l(5), l(10), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
|
||||
assertEquals("A string is required; received [5]", siae.getMessage());
|
||||
|
||||
siae = expectThrows(SqlIllegalArgumentException.class,
|
||||
() -> new DateAdd(Source.EMPTY,
|
||||
l("days"), l("foo"), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
|
||||
assertEquals("An integer is required; received [foo]", siae.getMessage());
|
||||
|
||||
siae = expectThrows(SqlIllegalArgumentException.class,
|
||||
() -> new DateAdd(Source.EMPTY,
|
||||
l("days"), l(10), l("foo"), randomZone()).makePipe().asProcessor().process(null));
|
||||
assertEquals("A date/datetime is required; received [foo]", siae.getMessage());
|
||||
|
||||
siae = expectThrows(SqlIllegalArgumentException.class,
|
||||
() -> new DateAdd(Source.EMPTY,
|
||||
l("invalid"), l(10), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
|
||||
assertEquals("A value of [YEAR, QUARTER, MONTH, DAYOFYEAR, DAY, WEEK, WEEKDAY, HOUR, MINUTE, " +
|
||||
"SECOND, MILLISECOND, MICROSECOND, NANOSECOND] or their aliases is required; received [invalid]",
|
||||
siae.getMessage());
|
||||
|
||||
siae = expectThrows(SqlIllegalArgumentException.class,
|
||||
() -> new DateAdd(Source.EMPTY,
|
||||
l("quertar"), l(10), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
|
||||
assertEquals("Received value [quertar] is not valid date part to add; did you mean [quarter, quarters]?",
|
||||
siae.getMessage());
|
||||
}
|
||||
|
||||
public void testWithNulls() {
|
||||
assertNull(new DateAdd(Source.EMPTY,
|
||||
NULL, randomIntLiteral(), randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
|
||||
assertNull(new DateAdd(Source.EMPTY,
|
||||
l("days"), NULL, randomDatetimeLiteral(), randomZone()).makePipe().asProcessor().process(null));
|
||||
assertNull(new DateAdd(Source.EMPTY,
|
||||
l("days"), randomIntLiteral(), NULL, randomZone()).makePipe().asProcessor().process(null));
|
||||
assertNull(new DateAdd(Source.EMPTY,
|
||||
NULL, NULL, NULL, randomZone()).makePipe().asProcessor().process(null));
|
||||
}
|
||||
|
||||
public void testAddition() {
|
||||
ZoneId zoneId = ZoneId.of("Etc/GMT-10");
|
||||
Literal dateTime = l(dateTime(2019, 9, 3, 18, 10, 37, 123456789));
|
||||
|
||||
assertEquals("2029-09-04T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("years"), l(10), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2009-09-04T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("years"), l(-10), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2022-03-04T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("quarters"), l(10), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2017-03-04T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("quarters"), l(-10), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2021-05-04T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("month"), l(20), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2018-01-04T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("month"), l(-20), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2020-05-01T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("day"), l(240), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2019-05-07T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("day"), l(-120), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2020-12-25T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("dayofyear"), l(478), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2018-05-14T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("dayofyear"), l(-478), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2021-12-22T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("weeks"), l(120), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2017-05-17T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("weeks"), l(-120), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2053-06-22T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("weekday"), l(12345), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("1985-11-16T04:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("weekday"), l(-12345), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2020-07-05T05:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("hours"), l(7321), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2018-11-03T03:10:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("hours"), l(-7321), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2021-07-21T01:04:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("minute"), l(987654), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2017-10-18T07:16:37.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("minute"), l(-987654), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2020-02-01T11:51:31.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("seconds"), l(12987654), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2019-04-06T20:29:43.123456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("seconds"), l(-12987654), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2019-09-19T04:56:42.555456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("ms"), l(1298765432), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2019-08-20T03:24:31.691456789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("ms"), l(-1298765432), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2019-09-04T04:12:41.111110789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("mcs"), l(123987654), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2019-09-04T04:08:33.135802789+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("mcs"), l(-123987654), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
|
||||
assertEquals("2019-09-04T04:10:37.935855554+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("nanoseconds"), l(812398765), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
assertEquals("2019-09-04T04:10:36.311058024+10:00",
|
||||
toString((ZonedDateTime) new DateAdd(Source.EMPTY, l("nanoseconds"), l(-812398765), dateTime, zoneId)
|
||||
.makePipe().asProcessor().process(null)));
|
||||
}
|
||||
|
||||
private String toString(ZonedDateTime dateTime) {
|
||||
return ISO_DATE_WITH_NANOS.format(dateTime);
|
||||
}
|
||||
}
|
|
@ -102,7 +102,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
private LogicalPlan plan(String sql) {
|
||||
return analyzer.analyze(parser.createStatement(sql), true);
|
||||
}
|
||||
|
||||
|
||||
private PhysicalPlan optimizeAndPlan(String sql) {
|
||||
return planner.plan(optimizer.optimize(plan(sql)), true);
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertEquals("int", tq.term());
|
||||
assertEquals(5, tq.value());
|
||||
}
|
||||
|
||||
|
||||
public void testTermEqualityForDate() {
|
||||
LogicalPlan p = plan("SELECT some.string FROM test WHERE date = 5");
|
||||
assertTrue(p instanceof Project);
|
||||
|
@ -148,7 +148,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertEquals("date", tq.term());
|
||||
assertEquals(5, tq.value());
|
||||
}
|
||||
|
||||
|
||||
public void testTermEqualityForDateWithLiteralDate() {
|
||||
LogicalPlan p = plan("SELECT some.string FROM test WHERE date = CAST('2019-08-08T12:34:56' AS DATETIME)");
|
||||
assertTrue(p instanceof Project);
|
||||
|
@ -166,7 +166,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertTrue(rq.includeUpper());
|
||||
assertEquals(DATE_FORMAT, rq.format());
|
||||
}
|
||||
|
||||
|
||||
public void testTermEqualityForDateWithLiteralTime() {
|
||||
LogicalPlan p = plan("SELECT some.string FROM test WHERE date = CAST('12:34:56' AS TIME)");
|
||||
assertTrue(p instanceof Project);
|
||||
|
@ -236,27 +236,27 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertEquals("date", rq.field());
|
||||
assertEquals("1969-05-13T12:34:56.000Z", rq.lower());
|
||||
}
|
||||
|
||||
|
||||
public void testDateRangeWithCurrentTimestamp() {
|
||||
testDateRangeWithCurrentFunctions("CURRENT_TIMESTAMP()", DATE_FORMAT, TestUtils.TEST_CFG.now());
|
||||
}
|
||||
|
||||
|
||||
public void testDateRangeWithCurrentDate() {
|
||||
testDateRangeWithCurrentFunctions("CURRENT_DATE()", DATE_FORMAT, DateUtils.asDateOnly(TestUtils.TEST_CFG.now()));
|
||||
}
|
||||
|
||||
|
||||
public void testDateRangeWithToday() {
|
||||
testDateRangeWithCurrentFunctions("TODAY()", DATE_FORMAT, DateUtils.asDateOnly(TestUtils.TEST_CFG.now()));
|
||||
}
|
||||
|
||||
|
||||
public void testDateRangeWithNow() {
|
||||
testDateRangeWithCurrentFunctions("NOW()", DATE_FORMAT, TestUtils.TEST_CFG.now());
|
||||
}
|
||||
|
||||
|
||||
public void testDateRangeWithCurrentTime() {
|
||||
testDateRangeWithCurrentFunctions("CURRENT_TIME()", TIME_FORMAT, TestUtils.TEST_CFG.now());
|
||||
}
|
||||
|
||||
|
||||
private void testDateRangeWithCurrentFunctions(String function, String pattern, ZonedDateTime now) {
|
||||
String operator = randomFrom(new String[] {">", ">=", "<", "<=", "=", "!="});
|
||||
LogicalPlan p = plan("SELECT some.string FROM test WHERE date" + operator + function);
|
||||
|
@ -267,7 +267,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
QueryTranslation translation = QueryTranslator.toQuery(condition, false);
|
||||
Query query = translation.query;
|
||||
RangeQuery rq;
|
||||
|
||||
|
||||
if (operator.equals("!=")) {
|
||||
assertTrue(query instanceof NotQuery);
|
||||
NotQuery nq = (NotQuery) query;
|
||||
|
@ -278,7 +278,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
rq = (RangeQuery) query;
|
||||
}
|
||||
assertEquals("date", rq.field());
|
||||
|
||||
|
||||
if (operator.contains("<") || operator.equals("=") || operator.equals("!=")) {
|
||||
assertEquals(DateFormatter.forPattern(pattern).format(now.withNano(DateUtils.getNanoPrecision(null, now.getNano()))),
|
||||
rq.upper());
|
||||
|
@ -293,6 +293,23 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertEquals(pattern, rq.format());
|
||||
}
|
||||
|
||||
public void testTranslateDateAdd_WhereClause_Painless() {
|
||||
LogicalPlan p = plan("SELECT int FROM test WHERE DATE_ADD('quarter',int, date) > '2018-09-04'::date");
|
||||
assertTrue(p instanceof Project);
|
||||
assertTrue(p.children().get(0) instanceof Filter);
|
||||
Expression condition = ((Filter) p.children().get(0)).condition();
|
||||
assertFalse(condition.foldable());
|
||||
QueryTranslation translation = QueryTranslator.toQuery(condition, false);
|
||||
assertNull(translation.aggFilter);
|
||||
assertTrue(translation.query instanceof ScriptQuery);
|
||||
ScriptQuery sc = (ScriptQuery) translation.query;
|
||||
assertEquals("InternalSqlScriptUtils.nullSafeFilter(InternalSqlScriptUtils.gt(InternalSqlScriptUtils.dateAdd(" +
|
||||
"params.v0,InternalSqlScriptUtils.docValue(doc,params.v1),InternalSqlScriptUtils.docValue(doc,params.v2)," +
|
||||
"params.v3),InternalSqlScriptUtils.asDateTime(params.v4)))",
|
||||
sc.script().toString());
|
||||
assertEquals("[{v=quarter}, {v=int}, {v=date}, {v=Z}, {v=2018-09-04T00:00:00.000Z}]", sc.script().params().toString());
|
||||
}
|
||||
|
||||
public void testTranslateDateTrunc_WhereClause_Painless() {
|
||||
LogicalPlan p = plan("SELECT int FROM test WHERE DATE_TRUNC('month', date) > '2018-09-04'::date");
|
||||
assertTrue(p instanceof Project);
|
||||
|
@ -336,7 +353,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
WildcardQuery qsq = ((WildcardQuery) qt.query);
|
||||
assertEquals("some.string.typical", qsq.field());
|
||||
}
|
||||
|
||||
|
||||
public void testRLikeOnInexact() {
|
||||
LogicalPlan p = plan("SELECT * FROM test WHERE some.string RLIKE '.*a.*'");
|
||||
assertTrue(p instanceof Project);
|
||||
|
@ -348,7 +365,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
RegexQuery qsq = ((RegexQuery) qt.query);
|
||||
assertEquals("some.string.typical", qsq.field());
|
||||
}
|
||||
|
||||
|
||||
public void testLikeConstructsNotSupported() {
|
||||
LogicalPlan p = plan("SELECT LTRIM(keyword) lt FROM test WHERE LTRIM(keyword) like '%a%'");
|
||||
assertTrue(p instanceof Project);
|
||||
|
@ -358,7 +375,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> QueryTranslator.toQuery(condition, false));
|
||||
assertEquals("Scalar function [LTRIM(keyword)] not allowed (yet) as argument for LIKE", ex.getMessage());
|
||||
}
|
||||
|
||||
|
||||
public void testRLikeConstructsNotSupported() {
|
||||
LogicalPlan p = plan("SELECT LTRIM(keyword) lt FROM test WHERE LTRIM(keyword) RLIKE '.*a.*'");
|
||||
assertTrue(p instanceof Project);
|
||||
|
@ -368,13 +385,13 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
SqlIllegalArgumentException ex = expectThrows(SqlIllegalArgumentException.class, () -> QueryTranslator.toQuery(condition, false));
|
||||
assertEquals("Scalar function [LTRIM(keyword)] not allowed (yet) as argument for RLIKE", ex.getMessage());
|
||||
}
|
||||
|
||||
|
||||
public void testDifferentLikeAndNotLikePatterns() {
|
||||
LogicalPlan p = plan("SELECT keyword k FROM test WHERE k LIKE 'X%' AND k NOT LIKE 'Y%'");
|
||||
assertTrue(p instanceof Project);
|
||||
p = ((Project) p).child();
|
||||
assertTrue(p instanceof Filter);
|
||||
|
||||
|
||||
Expression condition = ((Filter) p).condition();
|
||||
QueryTranslation qt = QueryTranslator.toQuery(condition, false);
|
||||
assertEquals(BoolQuery.class, qt.query.getClass());
|
||||
|
@ -382,18 +399,18 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertTrue(bq.isAnd());
|
||||
assertTrue(bq.left() instanceof WildcardQuery);
|
||||
assertTrue(bq.right() instanceof NotQuery);
|
||||
|
||||
|
||||
NotQuery nq = (NotQuery) bq.right();
|
||||
assertTrue(nq.child() instanceof WildcardQuery);
|
||||
WildcardQuery lqsq = (WildcardQuery) bq.left();
|
||||
WildcardQuery rqsq = (WildcardQuery) nq.child();
|
||||
|
||||
|
||||
assertEquals("X*", lqsq.query());
|
||||
assertEquals("keyword", lqsq.field());
|
||||
assertEquals("Y*", rqsq.query());
|
||||
assertEquals("keyword", rqsq.field());
|
||||
}
|
||||
|
||||
|
||||
public void testRLikePatterns() {
|
||||
String[] patterns = new String[] {"(...)+", "abab(ab)?", "(ab){1,2}", "(ab){3}", "aabb|bbaa", "a+b+|b+a+", "aa(cc|bb)",
|
||||
"a{4,6}b{4,6}", ".{3}.{3}", "aaa*bbb*", "a+.+", "a.c.e", "[^abc\\-]"};
|
||||
|
@ -401,13 +418,13 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertDifferentRLikeAndNotRLikePatterns(randomFrom(patterns), randomFrom(patterns));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void assertDifferentRLikeAndNotRLikePatterns(String firstPattern, String secondPattern) {
|
||||
LogicalPlan p = plan("SELECT keyword k FROM test WHERE k RLIKE '" + firstPattern + "' AND k NOT RLIKE '" + secondPattern + "'");
|
||||
assertTrue(p instanceof Project);
|
||||
p = ((Project) p).child();
|
||||
assertTrue(p instanceof Filter);
|
||||
|
||||
|
||||
Expression condition = ((Filter) p).condition();
|
||||
QueryTranslation qt = QueryTranslator.toQuery(condition, false);
|
||||
assertEquals(BoolQuery.class, qt.query.getClass());
|
||||
|
@ -415,12 +432,12 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertTrue(bq.isAnd());
|
||||
assertTrue(bq.left() instanceof RegexQuery);
|
||||
assertTrue(bq.right() instanceof NotQuery);
|
||||
|
||||
|
||||
NotQuery nq = (NotQuery) bq.right();
|
||||
assertTrue(nq.child() instanceof RegexQuery);
|
||||
RegexQuery lqsq = (RegexQuery) bq.left();
|
||||
RegexQuery rqsq = (RegexQuery) nq.child();
|
||||
|
||||
|
||||
assertEquals(firstPattern, lqsq.regex());
|
||||
assertEquals("keyword", lqsq.field());
|
||||
assertEquals(secondPattern, rqsq.regex());
|
||||
|
@ -646,7 +663,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertThat(aggFilter.scriptTemplate().params().toString(), startsWith("[{a=max(int){a->"));
|
||||
assertThat(aggFilter.scriptTemplate().params().toString(), endsWith(", {v=10}]"));
|
||||
}
|
||||
|
||||
|
||||
public void testTranslateRoundWithOneParameter() {
|
||||
LogicalPlan p = plan("SELECT ROUND(YEAR(date)) FROM test GROUP BY ROUND(YEAR(date))");
|
||||
|
||||
|
@ -667,16 +684,16 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
scriptTemplate.toString());
|
||||
assertEquals("[{v=date}, {v=Z}, {v=YEAR}, {v=null}]", scriptTemplate.params().toString());
|
||||
}
|
||||
|
||||
|
||||
public void testTranslateRoundWithTwoParameters() {
|
||||
LogicalPlan p = plan("SELECT ROUND(YEAR(date), -2) FROM test GROUP BY ROUND(YEAR(date), -2)");
|
||||
|
||||
|
||||
assertTrue(p instanceof Aggregate);
|
||||
assertEquals(1, ((Aggregate) p).groupings().size());
|
||||
assertEquals(1, ((Aggregate) p).aggregates().size());
|
||||
assertTrue(((Aggregate) p).groupings().get(0) instanceof Round);
|
||||
assertTrue(((Aggregate) p).aggregates().get(0) instanceof Round);
|
||||
|
||||
|
||||
Round groupingRound = (Round) ((Aggregate) p).groupings().get(0);
|
||||
assertEquals(2, groupingRound.children().size());
|
||||
assertTrue(groupingRound.children().get(1) instanceof Literal);
|
||||
|
@ -905,7 +922,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
containsString("\"date_histogram\":{\"field\":\"date\",\"missing_bucket\":true,\"value_type\":\"date\",\"order\":\"asc\","
|
||||
+ "\"fixed_interval\":\"62208000000ms\",\"time_zone\":\"Z\"}}}]}"));
|
||||
}
|
||||
|
||||
|
||||
public void testGroupByYearQueryTranslator() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT YEAR(date) FROM test GROUP BY YEAR(date)");
|
||||
assertEquals(EsQueryExec.class, p.getClass());
|
||||
|
@ -971,7 +988,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertEquals(2, ee.output().size());
|
||||
assertThat(ee.output().get(0).toString(), startsWith("dkey{a->"));
|
||||
assertThat(ee.output().get(1).toString(), startsWith("key{a->"));
|
||||
|
||||
|
||||
Collection<AggregationBuilder> subAggs = ee.queryContainer().aggs().asAggBuilder().getSubAggregations();
|
||||
assertEquals(2, subAggs.size());
|
||||
assertTrue(subAggs.toArray()[0] instanceof CardinalityAggregationBuilder);
|
||||
|
@ -979,15 +996,15 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
|
||||
CardinalityAggregationBuilder cardinalityKeyword = (CardinalityAggregationBuilder) subAggs.toArray()[0];
|
||||
assertEquals("keyword", cardinalityKeyword.field());
|
||||
|
||||
|
||||
FilterAggregationBuilder existsKeyword = (FilterAggregationBuilder) subAggs.toArray()[1];
|
||||
assertTrue(existsKeyword.getFilter() instanceof ExistsQueryBuilder);
|
||||
assertEquals("keyword", ((ExistsQueryBuilder) existsKeyword.getFilter()).fieldName());
|
||||
|
||||
|
||||
assertThat(ee.queryContainer().aggs().asAggBuilder().toString().replaceAll("\\s+", ""),
|
||||
endsWith("{\"filter\":{\"exists\":{\"field\":\"keyword\",\"boost\":1.0}}}}}}"));
|
||||
}
|
||||
|
||||
|
||||
public void testAllCountVariantsWithHavingGenerateCorrectAggregations() {
|
||||
PhysicalPlan p = optimizeAndPlan("SELECT AVG(int), COUNT(keyword) ln, COUNT(distinct keyword) dln, COUNT(some.dotted.field) fn,"
|
||||
+ "COUNT(distinct some.dotted.field) dfn, COUNT(*) ccc FROM test GROUP BY bool "
|
||||
|
@ -1001,7 +1018,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertThat(ee.output().get(3).toString(), startsWith("fn{a->"));
|
||||
assertThat(ee.output().get(4).toString(), startsWith("dfn{a->"));
|
||||
assertThat(ee.output().get(5).toString(), startsWith("ccc{a->"));
|
||||
|
||||
|
||||
Collection<AggregationBuilder> subAggs = ee.queryContainer().aggs().asAggBuilder().getSubAggregations();
|
||||
assertEquals(5, subAggs.size());
|
||||
assertTrue(subAggs.toArray()[0] instanceof AvgAggregationBuilder);
|
||||
|
@ -1009,21 +1026,21 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
assertTrue(subAggs.toArray()[2] instanceof CardinalityAggregationBuilder);
|
||||
assertTrue(subAggs.toArray()[3] instanceof FilterAggregationBuilder);
|
||||
assertTrue(subAggs.toArray()[4] instanceof CardinalityAggregationBuilder);
|
||||
|
||||
|
||||
AvgAggregationBuilder avgInt = (AvgAggregationBuilder) subAggs.toArray()[0];
|
||||
assertEquals("int", avgInt.field());
|
||||
|
||||
|
||||
FilterAggregationBuilder existsKeyword = (FilterAggregationBuilder) subAggs.toArray()[1];
|
||||
assertTrue(existsKeyword.getFilter() instanceof ExistsQueryBuilder);
|
||||
assertEquals("keyword", ((ExistsQueryBuilder) existsKeyword.getFilter()).fieldName());
|
||||
|
||||
|
||||
CardinalityAggregationBuilder cardinalityKeyword = (CardinalityAggregationBuilder) subAggs.toArray()[2];
|
||||
assertEquals("keyword", cardinalityKeyword.field());
|
||||
|
||||
|
||||
FilterAggregationBuilder existsDottedField = (FilterAggregationBuilder) subAggs.toArray()[3];
|
||||
assertTrue(existsDottedField.getFilter() instanceof ExistsQueryBuilder);
|
||||
assertEquals("some.dotted.field", ((ExistsQueryBuilder) existsDottedField.getFilter()).fieldName());
|
||||
|
||||
|
||||
CardinalityAggregationBuilder cardinalityDottedField = (CardinalityAggregationBuilder) subAggs.toArray()[4];
|
||||
assertEquals("some.dotted.field", cardinalityDottedField.field());
|
||||
|
||||
|
@ -1193,7 +1210,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
+ "\"lang\":\"painless\","
|
||||
+ "\"params\":{\"v0\":\"date\",\"v1\":\"P1Y\",\"v2\":\"INTERVAL_YEAR\",\"v3\":\"2019-03-11T12:34:56.000Z\"}},"));
|
||||
}
|
||||
|
||||
|
||||
public void testChronoFieldBasedDateTimeFunctionsWithMathIntervalAndGroupBy() {
|
||||
DateTimeExtractor randomFunction = randomValueOtherThan(DateTimeExtractor.YEAR, () -> randomFrom(DateTimeExtractor.values()));
|
||||
PhysicalPlan p = optimizeAndPlan(
|
||||
|
@ -1210,7 +1227,7 @@ public class QueryTranslatorTests extends ESTestCase {
|
|||
+ "\"v3\":\"Z\",\"v4\":\"" + randomFunction.chronoField().name() + "\"}},\"missing_bucket\":true,"
|
||||
+ "\"value_type\":\"long\",\"order\":\"asc\"}}}]}}}}"));
|
||||
}
|
||||
|
||||
|
||||
public void testDateTimeFunctionsWithMathIntervalAndGroupBy() {
|
||||
String[] functions = new String[] {"DAY_NAME", "MONTH_NAME", "DAY_OF_WEEK", "WEEK_OF_YEAR", "QUARTER"};
|
||||
String[] scriptMethods = new String[] {"dayName", "monthName", "dayOfWeek", "weekOfYear", "quarter"};
|
||||
|
|
Loading…
Reference in New Issue