SQL: Introduce HISTOGRAM grouping function (#36510)

Introduce Histogram grouping function for bucketing/grouping data based
 on a given range. Both date and numeric histograms are supported using
 the appropriate range declaration (numbers vs intervals).

SELECT HISTOGRAM(number, 50) AS h FROM index GROUP BY h
SELECT HISTOGRAM(date, INTERVAL 1 YEAR) AS h FROM index GROUP BY h

In addition add multiply operator for Intervals
Add docs for intervals and histogram

Fix #36509
This commit is contained in:
Costin Leau 2018-12-14 18:20:37 +02:00 committed by GitHub
parent 09bf93dc2a
commit 6ee6bb55e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1432 additions and 408 deletions

View File

@ -64,4 +64,4 @@ Multiple clusters, each with its own namespace, connected to each other in a fed
|=== |===
As one can see while the mapping between the concepts are not exactly one to one and the semantics somewhat different, there are more things in common than differences. In fact, thanks to SQL declarative nature, many concepts can move across {es} transparently and the terminology of the two likely to be used interchangeably through-out the rest of the material. As one can see while the mapping between the concepts are not exactly one to one and the semantics somewhat different, there are more things in common than differences. In fact, thanks to SQL declarative nature, many concepts can move across {es} transparently and the terminology of the two likely to be used interchangeably throughout the rest of the material.

View File

@ -1,7 +1,101 @@
[role="xpack"] [role="xpack"]
[testenv="basic"] [testenv="basic"]
[[sql-functions-datetime]] [[sql-functions-datetime]]
=== Date and Time Functions === Date/Time and Interval Functions and Operators
beta[]
{es-sql} offers a wide range of facilities for performing date/time manipulations.
[[sql-functions-datetime-interval]]
==== Intervals
A common requirement when dealing with date/time in general revolves around
the notion of ``interval``s, a topic that is worth exploring in the context of {es} and {es-sql}.
{es} has comprehensive support for <<date-math, date math>> both inside <<date-math-index-names, index names>> and <<mapping-date-format, queries>>.
Inside {es-sql} the former is supported as is by passing the expression in the table name, while the latter is supported through the standard SQL `INTERVAL`.
The table below shows the mapping between {es} and {es-sql}:
[cols="^m,^m",options="header"]
|===
| {es} | {es-sql}
2+h| Index/Table date math
2+|<index-{now/M{YYYY.MM}}>
2+h| Query date math
| 1y | INTERVAL 1 YEAR
| 2M | INTERVAL 2 MONTH
| 3w | INTERVAL 21 DAY
| 4d | INTERVAL 4 DAY
| 5h | INTERVAL 5 HOUR
| 6m | INTERVAL 6 MINUTE
| 7s | INTERVAL 7 SECOND
|===
`INTERVAL` allows either `YEAR` and `MONTH` to be mixed together _or_ `DAY`, `HOUR`, `MINUTE` and `SECOND`.
TIP: {es-sql} accepts also the plural for each time unit (e.g. both `YEAR` and `YEARS` are valid).
Example of the possible combinations below:
[cols="^,^",options="header"]
|===
| Interval | Description
| `INTERVAL '1-2' YEAR TO MONTH` | 1 year and 2 months
| `INTERVAL '3 4' DAYS TO HOURS` | 3 days and 4 hours
| `INTERVAL '5 6:12' DAYS TO MINUTES` | 5 days, 6 hours and 12 minutes
| `INTERVAL '3 4:56:01' DAY TO SECOND` | 3 days, 4 hours, 56 minutes and 1 second
| `INTERVAL '2 3:45:01.23456789' DAY TO SECOND` | 2 days, 3 hours, 45 minutes, 1 second and 234567890 nanoseconds
| `INTERVAL '123:45' HOUR TO MINUTES` | 123 hours and 45 minutes
| `INTERVAL '65:43:21.0123' HOUR TO SECONDS` | 65 hours, 43 minutes, 21 seconds and 12300000 nanoseconds
| `INTERVAL '45:01.23' MINUTES TO SECONDS` | 45 minutes, 1 second and 230000000 nanoseconds
|===
==== Operators
Basic arithmetic operators (`+`, `-`, etc) support date-time parameters as indicated below:
["source","sql",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{sql-specs}/docs.csv-spec[dtIntervalPlusInterval]
--------------------------------------------------
["source","sql",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{sql-specs}/docs.csv-spec[dtDatePlusInterval]
--------------------------------------------------
["source","sql",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{sql-specs}/docs.csv-spec[dtMinusInterval]
--------------------------------------------------
["source","sql",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{sql-specs}/docs.csv-spec[dtIntervalMinusInterval]
--------------------------------------------------
["source","sql",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{sql-specs}/docs.csv-spec[dtDateMinusInterval]
--------------------------------------------------
["source","sql",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{sql-specs}/docs.csv-spec[dtIntervalMul]
--------------------------------------------------
==== Functions
beta[] beta[]

View File

@ -0,0 +1,54 @@
[role="xpack"]
[testenv="basic"]
[[sql-functions-grouping]]
=== Grouping Functions
beta[]
Functions for creating special __grouping__s (also known as _bucketing_); as such these need to be used
as part of the <<sql-syntax-group-by, grouping>>.
[[sql-functions-grouping-histogram]]
==== `HISTOGRAM`
.Synopsis
[source, sql]
----
HISTOGRAM ( numeric_exp<1>, numeric_interval<2>)
HISTOGRAM ( date_exp<3>, date_time_interval<4>)
----
*Input*:
<1> numeric expression (typically a field)
<2> numeric interval
<3> date/time expression (typically a field)
<4> date/time <<sql-functions-datetime-interval, interval>>
*Output*: non-empty buckets or groups of the given expression divided according to the given interval
.Description
The histogram function takes all matching values and divides them into buckets with fixed size matching the given interval, using (roughly) the following formula:
[source, sql]
----
bucket_key = Math.floor(value / interval) * interval
----
`Histogram` can be applied on either numeric fields:
["source","sql",subs="attributes,callouts,macros"]
----
include-tagged::{sql-specs}/docs.csv-spec[histogramNumeric]
----
or date/time fields:
["source","sql",subs="attributes,callouts,macros"]
----
include-tagged::{sql-specs}/docs.csv-spec[histogramDate]
----

View File

@ -9,6 +9,7 @@ beta[]
* <<sql-operators, Operators>> * <<sql-operators, Operators>>
* <<sql-functions-aggs, Aggregate>> * <<sql-functions-aggs, Aggregate>>
* <<sql-functions-grouping, Grouping>>
* <<sql-functions-datetime, Date-Time>> * <<sql-functions-datetime, Date-Time>>
* <<sql-functions-search, Full-Text Search>> * <<sql-functions-search, Full-Text Search>>
* <<sql-functions-math, Mathematical>> * <<sql-functions-math, Mathematical>>
@ -19,6 +20,7 @@ beta[]
include::operators.asciidoc[] include::operators.asciidoc[]
include::aggs.asciidoc[] include::aggs.asciidoc[]
include::grouping.asciidoc[]
include::date-time.asciidoc[] include::date-time.asciidoc[]
include::search.asciidoc[] include::search.asciidoc[]
include::math.asciidoc[] include::math.asciidoc[]

View File

@ -7,42 +7,71 @@ beta[]
Most of {es} <<mapping-types, data types>> are available in {es-sql}, as indicated below: Most of {es} <<mapping-types, data types>> are available in {es-sql}, as indicated below:
[cols="^,^,^",options="header"] [cols="^,^m,^",options="header"]
|=== |===
| {es} type | SQL type | SQL precision | {es} type | SQL type | SQL precision
3+h| Core types 3+h| Core types
| <<null-value, `null`>> | `null` | 0 | <<null-value, `null`>> | null | 0
| <<boolean, `boolean`>> | `boolean` | 1 | <<boolean, `boolean`>> | boolean | 1
| <<number, `byte`>> | `tinyint` | 3 | <<number, `byte`>> | tinyint | 3
| <<number, `short`>> | `smallint` | 5 | <<number, `short`>> | smallint | 5
| <<number, `integer`>> | `integer` | 10 | <<number, `integer`>> | integer | 10
| <<number, `long`>> | `bigint` | 19 | <<number, `long`>> | bigint | 19
| <<number, `double`>> | `double` | 15 | <<number, `double`>> | double | 15
| <<number, `float`>> | `real` | 7 | <<number, `float`>> | real | 7
| <<number, `half_float`>> | `float` | 16 | <<number, `half_float`>> | float | 16
| <<number, `scaled_float`>> | `float` | 19 | <<number, `scaled_float`>> | float | 19
| <<keyword, `keyword`>> | `varchar` | based on <<ignore-above>> | <<keyword, `keyword`>> | varchar | based on <<ignore-above>>
| <<text, `text`>> | `varchar` | 2,147,483,647 | <<text, `text`>> | varchar | 2,147,483,647
| <<binary, `binary`>> | `varbinary` | 2,147,483,647 | <<binary, `binary`>> | varbinary | 2,147,483,647
| <<date, `date`>> | `timestamp` | 24 | <<date, `date`>> | timestamp | 24
| <<ip, `ip`>> | varchar | 39
3+h| Complex types
3+h| Complex types
| <<object, `object`>> | `struct` | 0
| <<nested, `nested`>> | `struct` | 0 | <<object, `object`>> | struct | 0
| <<nested, `nested`>> | struct | 0
3+h| Unsupported types 3+h| Unsupported types
| _types not mentioned above_ | `unsupported`| 0 | _types not mentioned above_ | unsupported | 0
|=== |===
Obviously, not all types in {es} have an equivalent in SQL and vice-versa hence why, {es-sql} 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. uses the data type _particularities_ of the former over the latter as ultimately {es} is the backing store.
In addition to the types above, {es-sql} also supports at _runtime_ SQL-specific types that do not have an equivalent in {es}.
Such types cannot be loaded from {es} (as it does not know about them) however can be used inside {es-sql} in queries or their results.
The table below indicates these types:
[cols="^m,^",options="header"]
|===
| SQL type | SQL precision
| interval_year | 7
| interval_month | 7
| interval_day | 23
| interval_hour | 23
| interval_minute | 23
| interval_second | 23
| interval_year_to_month | 7
| interval_day_to_hour | 23
| interval_day_to_minute | 23
| interval_day_to_second | 23
| interval_hour_to_minute | 23
| interval_hour_to_second | 23
| interval_minute_to_second | 23
|===
[[sql-multi-field]] [[sql-multi-field]]
[float] [float]

View File

@ -122,8 +122,7 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
@Override @Override
public String getString(int columnIndex) throws SQLException { public String getString(int columnIndex) throws SQLException {
Object val = column(columnIndex); return getObject(columnIndex, String.class);
return val != null ? val.toString() : null;
} }
@Override @Override

View File

@ -5,6 +5,8 @@
*/ */
package org.elasticsearch.xpack.sql.jdbc; package org.elasticsearch.xpack.sql.jdbc;
import org.elasticsearch.xpack.sql.proto.StringUtils;
import java.sql.Date; import java.sql.Date;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLFeatureNotSupportedException;
@ -118,10 +120,11 @@ final class TypeConverter {
return (T) convert(val, columnType, typeString); return (T) convert(val, columnType, typeString);
} }
// converting a Long to a Timestamp shouldn't be possible according to the spec, // if the value type is the same as the target, no conversion is needed
// it feels a little brittle to check this scenario here and I don't particularly like it // make sure though to check the internal type against the desired one
// TODO: can we do any better or should we go over the spec and allow getLong(date) to be valid? // since otherwise the internal object format can leak out
if (!(type == Long.class && columnType == EsType.DATE) && type.isInstance(val)) { // (for example dates when longs are requested or intervals for strings)
if (type.isInstance(val) && TypeUtils.classOf(columnType) == type) {
try { try {
return type.cast(val); return type.cast(val);
} catch (ClassCastException cce) { } catch (ClassCastException cce) {
@ -268,7 +271,7 @@ final class TypeConverter {
} }
private static String asString(Object nativeValue) { private static String asString(Object nativeValue) {
return nativeValue == null ? null : String.valueOf(nativeValue); return nativeValue == null ? null : StringUtils.toString(nativeValue);
} }
private static <T> T failConversion(Object value, EsType columnType, String typeString, Class<T> target) throws SQLException { private static <T> T failConversion(Object value, EsType columnType, String typeString, Class<T> target) throws SQLException {

View File

@ -26,8 +26,7 @@ public class CliExplainIT extends CliIntegrationTestCase {
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT * FROM test"), containsString("plan")); assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT * FROM test"), containsString("plan"));
assertThat(readLine(), startsWith("----------")); assertThat(readLine(), startsWith("----------"));
assertThat(readLine(), startsWith("Project[[test_field{f}#")); assertThat(readLine(), startsWith("Project[[test_field{f}#"));
assertThat(readLine(), startsWith("\\_SubQueryAlias[test]")); assertThat(readLine(), startsWith("\\_EsRelation[test][test_field{f}#"));
assertThat(readLine(), startsWith(" \\_EsRelation[test][test_field{f}#"));
assertEquals("", readLine()); assertEquals("", readLine());
assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT * FROM test"), containsString("plan")); assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT * FROM test"), containsString("plan"));
@ -74,8 +73,7 @@ public class CliExplainIT extends CliIntegrationTestCase {
assertThat(readLine(), startsWith("----------")); assertThat(readLine(), startsWith("----------"));
assertThat(readLine(), startsWith("Project[[i{f}#")); assertThat(readLine(), startsWith("Project[[i{f}#"));
assertThat(readLine(), startsWith("\\_Filter[i{f}#")); assertThat(readLine(), startsWith("\\_Filter[i{f}#"));
assertThat(readLine(), startsWith(" \\_SubQueryAlias[test]")); assertThat(readLine(), startsWith(" \\_EsRelation[test][i{f}#"));
assertThat(readLine(), startsWith(" \\_EsRelation[test][i{f}#"));
assertEquals("", readLine()); assertEquals("", readLine());
assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT * FROM test WHERE i = 2"), containsString("plan")); assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT * FROM test WHERE i = 2"), containsString("plan"));
@ -134,8 +132,7 @@ public class CliExplainIT extends CliIntegrationTestCase {
containsString("plan")); containsString("plan"));
assertThat(readLine(), startsWith("----------")); assertThat(readLine(), startsWith("----------"));
assertThat(readLine(), startsWith("Aggregate[[],[COUNT(1)#")); assertThat(readLine(), startsWith("Aggregate[[],[COUNT(1)#"));
assertThat(readLine(), startsWith("\\_SubQueryAlias[test]")); assertThat(readLine(), startsWith("\\_EsRelation[test][i{f}#"));
assertThat(readLine(), startsWith(" \\_EsRelation[test][i{f}#"));
assertEquals("", readLine()); assertEquals("", readLine());
assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT COUNT(*) FROM test"), containsString("plan")); assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT COUNT(*) FROM test"), containsString("plan"));

View File

@ -74,7 +74,7 @@ public class JdbcDocCsvSpecIT extends SpecBaseIntegrationTestCase {
@Override @Override
protected boolean logEsResultSet() { protected boolean logEsResultSet() {
return true; return false;
} }
@Override @Override

View File

@ -38,6 +38,10 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
while (aggregateFunction.matcher(line).matches()) { while (aggregateFunction.matcher(line).matches()) {
line = readLine(); line = readLine();
} }
Pattern groupingFunction = Pattern.compile("\\s*[A-Z0-9_~]+\\s*\\|\\s*GROUPING\\s*");
while (groupingFunction.matcher(line).matches()) {
line = readLine();
}
Pattern conditionalFunction = Pattern.compile("\\s*[A-Z0-9_~]+\\s*\\|\\s*CONDITIONAL\\s*"); Pattern conditionalFunction = Pattern.compile("\\s*[A-Z0-9_~]+\\s*\\|\\s*CONDITIONAL\\s*");
while (conditionalFunction.matcher(line).matches()) { while (conditionalFunction.matcher(line).matches()) {
line = readLine(); line = readLine();

View File

@ -16,7 +16,7 @@ public abstract class DebugSqlSpec extends SqlSpecTestCase {
@ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING) @ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING)
public static List<Object[]> readScriptSpec() throws Exception { public static List<Object[]> readScriptSpec() throws Exception {
Parser parser = specParser(); Parser parser = specParser();
return readScriptSpec("/debug.sql-spec", parser); return readScriptSpec("/datetime.sql-spec", parser);
} }
public DebugSqlSpec(String fileName, String groupName, String testName, Integer lineNumber, String query) { public DebugSqlSpec(String fileName, String groupName, String testName, Integer lineNumber, String query) {

View File

@ -137,3 +137,80 @@ null |null |null |null |null |
3 |3 |51 |3 |3.0 |100.0 |NaN |NaN 3 |3 |51 |3 |3.0 |100.0 |NaN |NaN
4 |4 |72 |4 |4.0 |0.0 |NaN |NaN 4 |4 |72 |4 |4.0 |0.0 |NaN |NaN
; ;
//
// Grouping functions
//
histogramNumeric
SELECT HISTOGRAM(salary, 5000) AS h FROM test_emp GROUP BY h;
h
---------------
25000
30000
35000
40000
45000
50000
55000
60000
65000
70000
;
histogramDate
schema::h:ts|c:l
SELECT HISTOGRAM(birth_date, INTERVAL 1 YEAR) AS h, COUNT(*) as c FROM test_emp GROUP BY h;
h | c
--------------------+---------------
null |10
1951-04-11T00:00:00Z|1
1952-04-05T00:00:00Z|10
1953-03-31T00:00:00Z|10
1954-03-26T00:00:00Z|7
1955-03-21T00:00:00Z|4
1956-03-15T00:00:00Z|4
1957-03-10T00:00:00Z|6
1958-03-05T00:00:00Z|6
1959-02-28T00:00:00Z|9
1960-02-23T00:00:00Z|7
1961-02-17T00:00:00Z|8
1962-02-12T00:00:00Z|6
1963-02-07T00:00:00Z|7
1964-02-02T00:00:00Z|5
;
histogramDateWithCountAndOrder
schema::h:ts|c:l
SELECT HISTOGRAM(birth_date, INTERVAL 1 YEAR) AS h, COUNT(*) as c FROM test_emp GROUP BY h ORDER BY h DESC;
h | c
--------------------+---------------
1964-02-02T00:00:00Z|5
1963-02-07T00:00:00Z|7
1962-02-12T00:00:00Z|6
1961-02-17T00:00:00Z|8
1960-02-23T00:00:00Z|7
1959-02-28T00:00:00Z|9
1958-03-05T00:00:00Z|6
1957-03-10T00:00:00Z|6
1956-03-15T00:00:00Z|4
1955-03-21T00:00:00Z|4
1954-03-26T00:00:00Z|7
1953-03-31T00:00:00Z|10
1952-04-05T00:00:00Z|10
1951-04-11T00:00:00Z|1
null |10
;
histogramDateWithDateFunction-Ignore
SELECT YEAR(HISTOGRAM(birth_date, INTERVAL 1 YEAR)) AS h, COUNT(*) as c FROM test_emp GROUP BY h ORDER BY h DESC;
;

View File

@ -92,7 +92,7 @@ test_emp | BASE TABLE
test_emp_copy | BASE TABLE test_emp_copy | BASE TABLE
; ;
testGroupByOnAlias groupByOnAlias
SELECT gender g, PERCENTILE(emp_no, 97) p1 FROM test_alias GROUP BY g ORDER BY g DESC; SELECT gender g, PERCENTILE(emp_no, 97) p1 FROM test_alias GROUP BY g ORDER BY g DESC;
g:s | p1:d g:s | p1:d
@ -102,7 +102,7 @@ F | 10099.52
null | 10019.0 null | 10019.0
; ;
testGroupByOnPattern groupByOnPattern
SELECT gender, PERCENTILE(emp_no, 97) p1 FROM "test_*" WHERE gender is NOT NULL GROUP BY gender; SELECT gender, PERCENTILE(emp_no, 97) p1 FROM "test_*" WHERE gender is NOT NULL GROUP BY gender;
gender:s | p1:d gender:s | p1:d

View File

@ -19,6 +19,7 @@ SKEWNESS |AGGREGATE
STDDEV_POP |AGGREGATE STDDEV_POP |AGGREGATE
SUM_OF_SQUARES |AGGREGATE SUM_OF_SQUARES |AGGREGATE
VAR_POP |AGGREGATE VAR_POP |AGGREGATE
HISTOGRAM |GROUPING
COALESCE |CONDITIONAL COALESCE |CONDITIONAL
GREATEST |CONDITIONAL GREATEST |CONDITIONAL
IFNULL |CONDITIONAL IFNULL |CONDITIONAL

View File

@ -3,7 +3,7 @@
// hence why all INTERVAL tests need to be done manually // hence why all INTERVAL tests need to be done manually
// //
testExactIntervals exactIntervals
SELECT INTERVAL 1 YEAR AS y, INTERVAL 2 MONTH AS m, INTERVAL 3 DAY AS d, INTERVAL 4 HOUR AS h, INTERVAL 5 MINUTE AS mm, INTERVAL 6 SECOND AS s; SELECT INTERVAL 1 YEAR AS y, INTERVAL 2 MONTH AS m, INTERVAL 3 DAY AS d, INTERVAL 4 HOUR AS h, INTERVAL 5 MINUTE AS mm, INTERVAL 6 SECOND AS s;
y | m | d | h | mm | s y | m | d | h | mm | s
@ -20,7 +20,7 @@ SELECT INTERVAL 1 YEARS AS y, INTERVAL 2 MONTHS AS m, INTERVAL 3 DAYS AS d, INTE
; ;
// take the examples from https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/interval-literals?view=sql-server-2017 // take the examples from https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/interval-literals?view=sql-server-2017
testYear year
SELECT INTERVAL '326' YEAR; SELECT INTERVAL '326' YEAR;
INTERVAL '326' YEAR INTERVAL '326' YEAR
@ -28,7 +28,7 @@ INTERVAL '326' YEAR
+326-0 +326-0
; ;
testMonth month
SELECT INTERVAL '326' MONTH; SELECT INTERVAL '326' MONTH;
INTERVAL '326' MONTH INTERVAL '326' MONTH
@ -36,7 +36,7 @@ INTERVAL '326' MONTH
+0-326 +0-326
; ;
testDay day
SELECT INTERVAL '3261' DAY; SELECT INTERVAL '3261' DAY;
INTERVAL '3261' DAY INTERVAL '3261' DAY
@ -44,7 +44,7 @@ INTERVAL '3261' DAY
+3261 00:00:00.0 +3261 00:00:00.0
; ;
testHour hour
SELECT INTERVAL '163' HOUR; SELECT INTERVAL '163' HOUR;
INTERVAL '163' HOUR INTERVAL '163' HOUR
@ -52,7 +52,7 @@ INTERVAL '163' HOUR
+6 19:00:00.0 +6 19:00:00.0
; ;
testMinute minute
SELECT INTERVAL '163' MINUTE; SELECT INTERVAL '163' MINUTE;
INTERVAL '163' MINUTE INTERVAL '163' MINUTE
@ -60,7 +60,7 @@ INTERVAL '163' MINUTE
+0 02:43:00.0 +0 02:43:00.0
; ;
testSecond second
SELECT INTERVAL '223.16' SECOND; SELECT INTERVAL '223.16' SECOND;
INTERVAL '223.16' SECOND INTERVAL '223.16' SECOND
@ -68,7 +68,7 @@ INTERVAL '223.16' SECOND
+0 00:03:43.16 +0 00:03:43.16
; ;
testYearMonth yearMonth
SELECT INTERVAL '163-11' YEAR TO MONTH; SELECT INTERVAL '163-11' YEAR TO MONTH;
INTERVAL '163-11' YEAR TO MONTH INTERVAL '163-11' YEAR TO MONTH
@ -76,7 +76,7 @@ INTERVAL '163-11' YEAR TO MONTH
+163-11 +163-11
; ;
testDayHour dayHour
SELECT INTERVAL '163 12' DAY TO HOUR; SELECT INTERVAL '163 12' DAY TO HOUR;
INTERVAL '163 12' DAY TO HOUR INTERVAL '163 12' DAY TO HOUR
@ -84,7 +84,7 @@ INTERVAL '163 12' DAY TO HOUR
+163 12:00:00.0 +163 12:00:00.0
; ;
testDayMinute dayMinute
SELECT INTERVAL '163 12:39' DAY TO MINUTE AS interval; SELECT INTERVAL '163 12:39' DAY TO MINUTE AS interval;
interval interval
@ -92,7 +92,7 @@ interval
+163 12:39:00.0 +163 12:39:00.0
; ;
testDaySecond daySecond
SELECT INTERVAL '163 12:39:59.163' DAY TO SECOND AS interval; SELECT INTERVAL '163 12:39:59.163' DAY TO SECOND AS interval;
interval interval
@ -100,7 +100,7 @@ interval
+163 12:39:59.163 +163 12:39:59.163
; ;
testDaySecondNegative daySecondNegative
SELECT INTERVAL -'163 23:39:56.23' DAY TO SECOND AS interval; SELECT INTERVAL -'163 23:39:56.23' DAY TO SECOND AS interval;
interval interval
@ -108,7 +108,7 @@ interval
-163 23:39:56.23 -163 23:39:56.23
; ;
testHourMinute hourMinute
SELECT INTERVAL '163:39' HOUR TO MINUTE AS interval; SELECT INTERVAL '163:39' HOUR TO MINUTE AS interval;
interval interval
@ -116,7 +116,7 @@ interval
+6 19:39:00.0 +6 19:39:00.0
; ;
testHourSecond hourSecond
SELECT INTERVAL '163:39:59.163' HOUR TO SECOND AS interval; SELECT INTERVAL '163:39:59.163' HOUR TO SECOND AS interval;
interval interval
@ -124,7 +124,7 @@ interval
+6 19:39:59.163 +6 19:39:59.163
; ;
testMinuteSecond minuteSecond
SELECT INTERVAL '163:59.163' MINUTE TO SECOND AS interval; SELECT INTERVAL '163:59.163' MINUTE TO SECOND AS interval;
interval interval
@ -132,7 +132,65 @@ interval
+0 02:43:59.163 +0 02:43:59.163
; ;
testDatePlusInterval intervalPlusInterval
SELECT INTERVAL 1 DAY + INTERVAL 53 MINUTES;
INTERVAL 1 DAY + INTERVAL 53 MINUTES
------------------------------------
+1 00:53:00.0
;
datePlusIntervalInline
SELECT CAST('1969-05-13T12:34:56' AS DATE) + INTERVAL 49 YEARS AS result;
result
--------------------
2018-05-13T12:34:56Z
;
minusInterval
SELECT - INTERVAL '49-1' YEAR TO MONTH result;
result
---------------
-49-1
;
intervalMinusInterval
SELECT INTERVAL '1' DAY - INTERVAL '2' HOURS AS result;
result
---------------
+0 22:00:00.0
;
intervalYearMultiply
SELECT -2 * INTERVAL '3' YEARS AS result;
result
---------------
-6-0
;
intervalDayMultiply
SELECT -2 * INTERVAL '1 23:45' DAY TO MINUTES AS result;
result
---------------
-3 23:30:00.0
;
dateMinusInterval
SELECT CAST('2018-05-13T12:34:56' AS DATE) - INTERVAL '2-8' YEAR TO MONTH AS result;
result
--------------------
2015-09-13T12:34:56Z
;
datePlusInterval
SELECT MONTH(birth_date) AS m, MONTH(birth_date + INTERVAL '1-2' YEAR TO MONTH) AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5; SELECT MONTH(birth_date) AS m, MONTH(birth_date + INTERVAL '1-2' YEAR TO MONTH) AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5;
m | f m | f
@ -144,7 +202,7 @@ null |null
6 |8 6 |8
; ;
testDatePlusMixInterval datePlusMixInterval
SELECT birth_date, birth_date + INTERVAL '1-2' YEAR TO MONTH AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5; SELECT birth_date, birth_date + INTERVAL '1-2' YEAR TO MONTH AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5;
birth_date:ts | f:ts birth_date:ts | f:ts
@ -157,7 +215,7 @@ null |null
; ;
testDateMinusInterval dateMinusInterval
SELECT YEAR(birth_date) AS y, YEAR(birth_date - INTERVAL 1 YEAR) AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5; SELECT YEAR(birth_date) AS y, YEAR(birth_date - INTERVAL 1 YEAR) AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5;
y | f y | f
@ -169,7 +227,7 @@ null |null
1952 |1951 1952 |1951
; ;
testDatePlusMixInterval datePlusMixInterval
SELECT birth_date, birth_date + INTERVAL '1-2' YEAR TO MONTH AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5; SELECT birth_date, birth_date + INTERVAL '1-2' YEAR TO MONTH AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5;
birth_date:ts | f:ts birth_date:ts | f:ts
@ -182,7 +240,7 @@ null |null
; ;
testDateAndMultipleIntervals dateAndMultipleIntervals
SELECT birth_date, birth_date - INTERVAL 1 YEAR + INTERVAL '2-3' YEAR TO MONTH AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5; SELECT birth_date, birth_date - INTERVAL 1 YEAR + INTERVAL '2-3' YEAR TO MONTH AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5;
birth_date:ts | f:ts birth_date:ts | f:ts
@ -195,7 +253,7 @@ null |null
; ;
testDatePlusIntervalWhereClause datePlusIntervalWhereClause
SELECT birth_date, YEAR(birth_date + INTERVAL 1 YEAR) AS f FROM test_emp WHERE YEAR(birth_date + INTERVAL 1 YEAR) > 1 GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5; SELECT birth_date, YEAR(birth_date + INTERVAL 1 YEAR) AS f FROM test_emp WHERE YEAR(birth_date + INTERVAL 1 YEAR) > 1 GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5;
birth_date:ts | f:i birth_date:ts | f:i
@ -207,7 +265,7 @@ SELECT birth_date, YEAR(birth_date + INTERVAL 1 YEAR) AS f FROM test_emp WHERE Y
1952-07-08T00:00:00Z|1953 1952-07-08T00:00:00Z|1953
; ;
testDateMinusIntervalOrder dateMinusIntervalOrder
SELECT birth_date, MONTH(birth_date - INTERVAL 1 YEAR) AS f FROM test_emp GROUP BY birth_date ORDER BY MONTH(birth_date - INTERVAL 1 YEAR) ASC LIMIT 5; SELECT birth_date, MONTH(birth_date - INTERVAL 1 YEAR) AS f FROM test_emp GROUP BY birth_date ORDER BY MONTH(birth_date - INTERVAL 1 YEAR) ASC LIMIT 5;
birth_date:ts | f:i birth_date:ts | f:i
@ -220,7 +278,7 @@ null |null
; ;
// see https://github.com/elastic/elasticsearch/issues/35745 // see https://github.com/elastic/elasticsearch/issues/35745
testDatePlusIntervalHavingClause-Ignore datePlusIntervalHavingClause-Ignore
SELECT birth_date, MAX(hire_date) - INTERVAL 1 YEAR AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5; SELECT birth_date, MAX(hire_date) - INTERVAL 1 YEAR AS f FROM test_emp GROUP BY birth_date ORDER BY birth_date ASC LIMIT 5;
birth_date:ts | f:ts birth_date:ts | f:ts

View File

@ -196,6 +196,7 @@ SKEWNESS |AGGREGATE
STDDEV_POP |AGGREGATE STDDEV_POP |AGGREGATE
SUM_OF_SQUARES |AGGREGATE SUM_OF_SQUARES |AGGREGATE
VAR_POP |AGGREGATE VAR_POP |AGGREGATE
HISTOGRAM |GROUPING
COALESCE |CONDITIONAL COALESCE |CONDITIONAL
GREATEST |CONDITIONAL GREATEST |CONDITIONAL
IFNULL |CONDITIONAL IFNULL |CONDITIONAL
@ -299,6 +300,7 @@ CONVERT |SCALAR
DATABASE |SCALAR DATABASE |SCALAR
USER |SCALAR USER |SCALAR
SCORE |SCORE SCORE |SCORE
// end::showFunctions // end::showFunctions
; ;
@ -697,6 +699,131 @@ SELECT MIN(salary) AS min, MAX(salary) AS max FROM emp HAVING min > 25000;
// end::groupByHavingImplicitNoMatch // end::groupByHavingImplicitNoMatch
//; //;
///////////////////////////////
//
// Grouping
//
///////////////////////////////
histogramNumeric
// tag::histogramNumeric
SELECT HISTOGRAM(salary, 5000) AS h FROM emp GROUP BY h;
h
---------------
25000
30000
35000
40000
45000
50000
55000
60000
65000
70000
// end::histogramNumeric
;
histogramDate
schema::h:ts|c:l
// tag::histogramDate
SELECT HISTOGRAM(birth_date, INTERVAL 1 YEAR) AS h, COUNT(*) AS c FROM emp GROUP BY h;
h | c
--------------------+---------------
null |10
1951-04-11T00:00:00Z|1
1952-04-05T00:00:00Z|10
1953-03-31T00:00:00Z|10
1954-03-26T00:00:00Z|7
1955-03-21T00:00:00Z|4
1956-03-15T00:00:00Z|4
1957-03-10T00:00:00Z|6
1958-03-05T00:00:00Z|6
1959-02-28T00:00:00Z|9
1960-02-23T00:00:00Z|7
1961-02-17T00:00:00Z|8
1962-02-12T00:00:00Z|6
1963-02-07T00:00:00Z|7
1964-02-02T00:00:00Z|5
// end::histogramDate
;
///////////////////////////////
//
// Date/Time
//
///////////////////////////////
dtIntervalPlusInterval
// tag::dtIntervalPlusInterval
SELECT INTERVAL 1 DAY + INTERVAL 53 MINUTES AS result;
result
---------------
+1 00:53:00.0
// end::dtIntervalPlusInterval
;
dtDatePlusInterval
// tag::dtDatePlusInterval
SELECT CAST('1969-05-13T12:34:56' AS DATE) + INTERVAL 49 YEARS AS result;
result
--------------------
2018-05-13T12:34:56Z
// end::dtDatePlusInterval
;
dtMinusInterval
// tag::dtMinusInterval
SELECT - INTERVAL '49-1' YEAR TO MONTH result;
result
---------------
-49-1
// end::dtMinusInterval
;
dtIntervalMinusInterval
// tag::dtIntervalMinusInterval
SELECT INTERVAL '1' DAY - INTERVAL '2' HOURS AS result;
result
---------------
+0 22:00:00.0
// end::dtIntervalMinusInterval
;
dtDateMinusInterval
// tag::dtDateMinusInterval
SELECT CAST('2018-05-13T12:34:56' AS DATE) - INTERVAL '2-8' YEAR TO MONTH AS result;
result
--------------------
2015-09-13T12:34:56Z
// end::dtDateMinusInterval
;
dtIntervalMul
// tag::dtIntervalMul
SELECT -2 * INTERVAL '3' YEARS AS result;
result
---------------
-6-0
// end::dtIntervalMul
;
/////////////////////////////// ///////////////////////////////
// //
// Order by // Order by

View File

@ -99,13 +99,13 @@ SELECT MIN(salary) mi, MAX(salary) ma, YEAR(hire_date) year, ROUND(AVG(languages
mi:i | ma:i | year:i |ROUND(AVG(languages),1):d|TRUNCATE(AVG(languages),1):d| COUNT(1):l mi:i | ma:i | year:i |ROUND(AVG(languages),1):d|TRUNCATE(AVG(languages),1):d| COUNT(1):l
---------------+---------------+---------------+-------------------------+----------------------------+--------------- ---------------+---------------+---------------+-------------------------+----------------------------+---------------
25324 |70011 |1987 |3.0 |3.0 |15 25324 |70011 |1986 |3.0 |3.0 |15
25945 |73578 |1988 |2.9 |2.8 |9 25945 |73578 |1987 |2.9 |2.8 |9
25976 |74970 |1989 |3.0 |3.0 |13 25976 |74970 |1988 |3.0 |3.0 |13
31120 |71165 |1990 |3.1 |3.0 |12 31120 |71165 |1989 |3.1 |3.0 |12
30404 |58715 |1993 |3.0 |3.0 |3 30404 |58715 |1992 |3.0 |3.0 |3
35742 |67492 |1994 |2.8 |2.7 |4 35742 |67492 |1993 |2.8 |2.7 |4
45656 |45656 |1996 |3.0 |3.0 |1 45656 |45656 |1995 |3.0 |3.0 |1
; ;
minMaxRoundWithHavingRound minMaxRoundWithHavingRound
@ -113,17 +113,17 @@ SELECT MIN(salary) mi, MAX(salary) ma, YEAR(hire_date) year, ROUND(AVG(languages
mi:i | ma:i | year:i |ROUND(AVG(languages),1):d| COUNT(1):l mi:i | ma:i | year:i |ROUND(AVG(languages),1):d| COUNT(1):l
---------------+---------------+---------------+-------------------------+--------------- ---------------+---------------+---------------+-------------------------+---------------
26436 |74999 |1985 |3.1 |11 26436 |74999 |1984 |3.1 |11
31897 |61805 |1986 |3.5 |11 31897 |61805 |1985 |3.5 |11
25324 |70011 |1987 |3.0 |15 25324 |70011 |1986 |3.0 |15
25945 |73578 |1988 |2.9 |9 25945 |73578 |1987 |2.9 |9
25976 |74970 |1989 |3.0 |13 25976 |74970 |1988 |3.0 |13
31120 |71165 |1990 |3.1 |12 31120 |71165 |1989 |3.1 |12
32568 |65030 |1991 |3.3 |6 32568 |65030 |1990 |3.3 |6
27215 |60781 |1992 |4.1 |8 27215 |60781 |1991 |4.1 |8
30404 |58715 |1993 |3.0 |3 30404 |58715 |1992 |3.0 |3
35742 |67492 |1994 |2.8 |4 35742 |67492 |1993 |2.8 |4
45656 |45656 |1996 |3.0 |1 45656 |45656 |1995 |3.0 |1
; ;
groupByAndOrderByTruncateWithPositiveParameter groupByAndOrderByTruncateWithPositiveParameter

View File

@ -108,7 +108,11 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
new ResolveAggsInHaving() new ResolveAggsInHaving()
//new ImplicitCasting() //new ImplicitCasting()
); );
return Arrays.asList(substitution, resolution); Batch finish = new Batch("Finish Analysis",
new PruneSubqueryAliases(),
CleanAliases.INSTANCE
);
return Arrays.asList(substitution, resolution, finish);
} }
public LogicalPlan analyze(LogicalPlan plan) { public LogicalPlan analyze(LogicalPlan plan) {
@ -1057,6 +1061,69 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
} }
} }
public static class PruneSubqueryAliases extends AnalyzeRule<SubQueryAlias> {
@Override
protected LogicalPlan rule(SubQueryAlias alias) {
return alias.child();
}
@Override
protected boolean skipResolved() {
return false;
}
}
public static class CleanAliases extends AnalyzeRule<LogicalPlan> {
public static final CleanAliases INSTANCE = new CleanAliases();
@Override
protected LogicalPlan rule(LogicalPlan plan) {
if (plan instanceof Project) {
Project p = (Project) plan;
return new Project(p.location(), p.child(), cleanExpressions(p.projections()));
}
if (plan instanceof Aggregate) {
Aggregate a = (Aggregate) plan;
// clean group expressions
List<Expression> cleanedGroups = a.groupings().stream().map(CleanAliases::trimAliases).collect(toList());
return new Aggregate(a.location(), a.child(), cleanedGroups, cleanExpressions(a.aggregates()));
}
return plan.transformExpressionsOnly(e -> {
if (e instanceof Alias) {
return ((Alias) e).child();
}
return e;
});
}
private List<NamedExpression> cleanExpressions(List<? extends NamedExpression> args) {
return args.stream().map(CleanAliases::trimNonTopLevelAliases).map(NamedExpression.class::cast).collect(toList());
}
public static Expression trimNonTopLevelAliases(Expression e) {
if (e instanceof Alias) {
Alias a = (Alias) e;
return new Alias(a.location(), a.name(), a.qualifier(), trimAliases(a.child()), a.id());
}
return trimAliases(e);
}
private static Expression trimAliases(Expression e) {
return e.transformDown(Alias::child, Alias.class);
}
@Override
protected boolean skipResolved() {
return false;
}
}
abstract static class AnalyzeRule<SubPlan extends LogicalPlan> extends Rule<SubPlan, LogicalPlan> { abstract static class AnalyzeRule<SubPlan extends LogicalPlan> extends Rule<SubPlan, LogicalPlan> {
// transformUp (post-order) - that is first children and then the node // transformUp (post-order) - that is first children and then the node
@ -1073,4 +1140,4 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
return true; return true;
} }
} }
} }

View File

@ -462,7 +462,7 @@ public final class Verifier {
Map<Expression, Node<?>> missing = new LinkedHashMap<>(); Map<Expression, Node<?>> missing = new LinkedHashMap<>();
a.aggregates().forEach(ne -> a.aggregates().forEach(ne ->
ne.collectFirstChildren(c -> checkGroupMatch(c, ne, a.groupings(), missing, functions))); ne.collectFirstChildren(c -> checkGroupMatch(c, ne, a.groupings(), missing, functions)));
if (!missing.isEmpty()) { if (!missing.isEmpty()) {
String plural = missing.size() > 1 ? "s" : StringUtils.EMPTY; String plural = missing.size() > 1 ? "s" : StringUtils.EMPTY;
@ -478,6 +478,13 @@ public final class Verifier {
private static boolean checkGroupMatch(Expression e, Node<?> source, List<Expression> groupings, private static boolean checkGroupMatch(Expression e, Node<?> source, List<Expression> groupings,
Map<Expression, Node<?>> missing, Map<String, Function> functions) { Map<Expression, Node<?>> missing, Map<String, Function> functions) {
// 1:1 match
if (Expressions.match(groupings, e::semanticEquals)) {
return true;
}
// resolve FunctionAttribute to backing functions // resolve FunctionAttribute to backing functions
if (e instanceof FunctionAttribute) { if (e instanceof FunctionAttribute) {
FunctionAttribute fa = (FunctionAttribute) e; FunctionAttribute fa = (FunctionAttribute) e;
@ -521,12 +528,14 @@ public final class Verifier {
if (Functions.isAggregate(e)) { if (Functions.isAggregate(e)) {
return true; return true;
} }
// left without leaves which have to match; if not there's a failure // left without leaves which have to match; if not there's a failure
// make sure to match directly on the expression and not on the tree
// (since otherwise exp might match the function argument which would be incorrect)
final Expression exp = e; final Expression exp = e;
if (e.children().isEmpty()) { if (e.children().isEmpty()) {
if (!Expressions.anyMatch(groupings, c -> exp.semanticEquals(exp instanceof Attribute ? Expressions.attribute(c) : c))) { if (Expressions.match(groupings, c -> exp.semanticEquals(exp instanceof Attribute ? Expressions.attribute(c) : c)) == false) {
missing.put(e, source); missing.put(exp, source);
} }
return true; return true;
} }

View File

@ -70,6 +70,15 @@ public final class Expressions {
return false; return false;
} }
public static boolean match(List<? extends Expression> exps, Predicate<? super Expression> predicate) {
for (Expression exp : exps) {
if (predicate.test(exp)) {
return true;
}
}
return false;
}
public static boolean nullable(List<? extends Expression> exps) { public static boolean nullable(List<? extends Expression> exps) {
for (Expression exp : exps) { for (Expression exp : exps) {
if (exp.nullable()) { if (exp.nullable()) {

View File

@ -5,9 +5,6 @@
*/ */
package org.elasticsearch.xpack.sql.expression; package org.elasticsearch.xpack.sql.expression;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
@ -16,12 +13,12 @@ import java.util.Objects;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
public abstract class UnaryExpression extends NamedExpression { public abstract class UnaryExpression extends Expression {
private final Expression child; private final Expression child;
protected UnaryExpression(Location location, Expression child) { protected UnaryExpression(Location location, Expression child) {
super(location, null, singletonList(child), null); super(location, singletonList(child));
this.child = child; this.child = child;
} }
@ -58,21 +55,6 @@ public abstract class UnaryExpression extends NamedExpression {
return child.dataType(); return child.dataType();
} }
@Override
public Attribute toAttribute() {
throw new SqlIllegalArgumentException("Not supported yet");
}
@Override
public ScriptTemplate asScript() {
throw new SqlIllegalArgumentException("Not supported yet");
}
@Override
protected Pipe makePipe() {
throw new SqlIllegalArgumentException("Not supported yet");
}
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(child); return Objects.hash(child);
@ -91,4 +73,4 @@ public abstract class UnaryExpression extends NamedExpression {
UnaryExpression other = (UnaryExpression) obj; UnaryExpression other = (UnaryExpression) obj;
return Objects.equals(child, other.child); return Objects.equals(child, other.child);
} }
} }

View File

@ -20,6 +20,7 @@ import org.elasticsearch.xpack.sql.expression.function.aggregate.StddevPop;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Sum; import org.elasticsearch.xpack.sql.expression.function.aggregate.Sum;
import org.elasticsearch.xpack.sql.expression.function.aggregate.SumOfSquares; import org.elasticsearch.xpack.sql.expression.function.aggregate.SumOfSquares;
import org.elasticsearch.xpack.sql.expression.function.aggregate.VarPop; import org.elasticsearch.xpack.sql.expression.function.aggregate.VarPop;
import org.elasticsearch.xpack.sql.expression.function.grouping.Histogram;
import org.elasticsearch.xpack.sql.expression.function.scalar.Cast; import org.elasticsearch.xpack.sql.expression.function.scalar.Cast;
import org.elasticsearch.xpack.sql.expression.function.scalar.Database; import org.elasticsearch.xpack.sql.expression.function.scalar.Database;
import org.elasticsearch.xpack.sql.expression.function.scalar.User; import org.elasticsearch.xpack.sql.expression.function.scalar.User;
@ -153,6 +154,8 @@ public class FunctionRegistry {
def(SumOfSquares.class, SumOfSquares::new, "SUM_OF_SQUARES"), def(SumOfSquares.class, SumOfSquares::new, "SUM_OF_SQUARES"),
def(Skewness.class, Skewness::new, "SKEWNESS"), def(Skewness.class, Skewness::new, "SKEWNESS"),
def(Kurtosis.class, Kurtosis::new, "KURTOSIS")); def(Kurtosis.class, Kurtosis::new, "KURTOSIS"));
// histogram
addToMap(def(Histogram.class, Histogram::new, "HISTOGRAM"));
// Scalar functions // Scalar functions
// Conditional // Conditional
addToMap(def(Coalesce.class, Coalesce::new, "COALESCE"), addToMap(def(Coalesce.class, Coalesce::new, "COALESCE"),
@ -447,6 +450,28 @@ public class FunctionRegistry {
T build(Location location, Expression target, TimeZone tz); T build(Location location, Expression target, TimeZone tz);
} }
/**
* Build a {@linkplain FunctionDefinition} for a binary 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, DatetimeBinaryFunctionBuilder<T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
if (children.size() != 2) {
throw new IllegalArgumentException("expects exactly two arguments");
}
if (distinct) {
throw new IllegalArgumentException("does not support DISTINCT yet it was specified");
}
return ctorRef.build(location, children.get(0), children.get(1), cfg.timeZone());
};
return def(function, builder, false, names);
}
interface DatetimeBinaryFunctionBuilder<T> {
T build(Location location, Expression lhs, Expression rhs, TimeZone tz);
}
/** /**
* Build a {@linkplain FunctionDefinition} for a binary function that is * Build a {@linkplain FunctionDefinition} for a binary function that is
* not aware of time zone and does not support {@code DISTINCT}. * not aware of time zone and does not support {@code DISTINCT}.
@ -559,4 +584,4 @@ public class FunctionRegistry {
private interface CastFunctionBuilder<T> { private interface CastFunctionBuilder<T> {
T build(Location location, Expression expression, DataType dataType); T build(Location location, Expression expression, DataType dataType);
} }
} }

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.expression.function;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunction; import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunction;
import org.elasticsearch.xpack.sql.expression.function.grouping.GroupingFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction; import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalFunction; import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalFunction;
@ -15,6 +16,7 @@ public enum FunctionType {
AGGREGATE(AggregateFunction.class), AGGREGATE(AggregateFunction.class),
CONDITIONAL(ConditionalFunction.class), CONDITIONAL(ConditionalFunction.class),
GROUPING(GroupingFunction.class),
SCALAR(ScalarFunction.class), SCALAR(ScalarFunction.class),
SCORE(Score.class); SCORE(Score.class);

View File

@ -30,11 +30,11 @@ public abstract class AggregateFunction extends Function {
private AggregateFunctionAttribute lazyAttribute; private AggregateFunctionAttribute lazyAttribute;
AggregateFunction(Location location, Expression field) { protected AggregateFunction(Location location, Expression field) {
this(location, field, emptyList()); this(location, field, emptyList());
} }
AggregateFunction(Location location, Expression field, List<Expression> parameters) { protected AggregateFunction(Location location, Expression field, List<Expression> parameters) {
super(location, CollectionUtils.combine(singletonList(field), parameters)); super(location, CollectionUtils.combine(singletonList(field), parameters));
this.field = field; this.field = field;
this.parameters = parameters; this.parameters = parameters;

View File

@ -0,0 +1,95 @@
/*
* 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.grouping;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.Function;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.AggNameInput;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.util.CollectionUtils;
import java.util.List;
import java.util.Objects;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
/**
* A type of {@code Function} that creates groups or buckets.
*/
public abstract class GroupingFunction extends Function {
private final Expression field;
private final List<Expression> parameters;
private GroupingFunctionAttribute lazyAttribute;
protected GroupingFunction(Location location, Expression field) {
this(location, field, emptyList());
}
protected GroupingFunction(Location location, Expression field, List<Expression> parameters) {
super(location, CollectionUtils.combine(singletonList(field), parameters));
this.field = field;
this.parameters = parameters;
}
public Expression field() {
return field;
}
public List<Expression> parameters() {
return parameters;
}
@Override
public GroupingFunctionAttribute toAttribute() {
if (lazyAttribute == null) {
// this is highly correlated with QueryFolder$FoldAggregate#addFunction (regarding the function name within the querydsl)
lazyAttribute = new GroupingFunctionAttribute(location(), name(), dataType(), id(), functionId());
}
return lazyAttribute;
}
@Override
public final GroupingFunction replaceChildren(List<Expression> newChildren) {
if (newChildren.size() != 1) {
throw new IllegalArgumentException("expected [1] child but received [" + newChildren.size() + "]");
}
return replaceChild(newChildren.get(0));
}
protected abstract GroupingFunction replaceChild(Expression newChild);
@Override
protected Pipe makePipe() {
// unresolved AggNameInput (should always get replaced by the folder)
return new AggNameInput(location(), this, name());
}
@Override
public ScriptTemplate asScript() {
throw new SqlIllegalArgumentException("Grouping functions cannot be scripted");
}
@Override
public boolean equals(Object obj) {
if (false == super.equals(obj)) {
return false;
}
GroupingFunction other = (GroupingFunction) obj;
return Objects.equals(other.field(), field())
&& Objects.equals(other.parameters(), parameters());
}
@Override
public int hashCode() {
return Objects.hash(field(), parameters());
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.grouping;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.function.FunctionAttribute;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
public class GroupingFunctionAttribute extends FunctionAttribute {
GroupingFunctionAttribute(Location location, String name, DataType dataType, ExpressionId id, String functionId) {
this(location, name, dataType, null, false, id, false, functionId);
}
public GroupingFunctionAttribute(Location location, String name, DataType dataType, String qualifier,
boolean nullable, ExpressionId id, boolean synthetic, String functionId) {
super(location, name, dataType, qualifier, nullable, id, synthetic, functionId);
}
@Override
protected NodeInfo<GroupingFunctionAttribute> info() {
return NodeInfo.create(this, GroupingFunctionAttribute::new,
name(), dataType(), qualifier(), nullable(), id(), synthetic(), functionId());
}
@Override
protected Expression canonicalize() {
return new GroupingFunctionAttribute(location(), "<none>", dataType(), null, true, id(), false, "<none>");
}
@Override
protected Attribute clone(Location location, String name, String qualifier, boolean nullable, ExpressionId id, boolean synthetic) {
// this is highly correlated with QueryFolder$FoldAggregate#addFunction (regarding the function name within the querydsl)
// that is the functionId is actually derived from the expression id to easily track it across contexts
return new GroupingFunctionAttribute(location, name, dataType(), qualifier, nullable, id, synthetic, functionId());
}
public GroupingFunctionAttribute withFunctionId(String functionId, String propertyPath) {
return new GroupingFunctionAttribute(location(), name(), dataType(), qualifier(), nullable(),
id(), synthetic(), functionId);
}
@Override
protected String label() {
return "g->" + functionId();
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.grouping;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.DataTypes;
import java.util.Objects;
import java.util.TimeZone;
public class Histogram extends GroupingFunction {
private final Literal interval;
private final TimeZone timeZone;
public Histogram(Location location, Expression field, Expression interval, TimeZone timeZone) {
super(location, field);
this.interval = (Literal) interval;
this.timeZone = timeZone;
}
public Literal interval() {
return interval;
}
public TimeZone timeZone() {
return timeZone;
}
@Override
protected TypeResolution resolveType() {
TypeResolution resolution = Expressions.typeMustBeNumericOrDate(field(), "HISTOGRAM", ParamOrdinal.FIRST);
if (resolution == TypeResolution.TYPE_RESOLVED) {
// interval must be Literal interval
if (field().dataType() == DataType.DATE) {
resolution = Expressions.typeMustBe(interval, DataTypes::isInterval, "(Date) HISTOGRAM", ParamOrdinal.SECOND, "interval");
} else {
resolution = Expressions.typeMustBeNumeric(interval, "(Numeric) HISTOGRAM", ParamOrdinal.SECOND);
}
}
return resolution;
}
@Override
protected GroupingFunction replaceChild(Expression newChild) {
return new Histogram(location(), newChild, interval, timeZone);
}
@Override
public DataType dataType() {
return field().dataType();
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, Histogram::new, field(), interval, timeZone);
}
@Override
public int hashCode() {
return Objects.hash(field(), interval, timeZone);
}
@Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
Histogram other = (Histogram) obj;
return Objects.equals(interval, other.interval)
&& Objects.equals(timeZone, other.timeZone);
}
return false;
}
}

View File

@ -24,5 +24,5 @@ public abstract class DateTimeHistogramFunction extends DateTimeFunction {
/** /**
* used for aggregration (date histogram) * used for aggregration (date histogram)
*/ */
public abstract String interval(); public abstract long interval();
} }

View File

@ -11,11 +11,15 @@ import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo.NodeCtor2; import org.elasticsearch.xpack.sql.tree.NodeInfo.NodeCtor2;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/** /**
* Extract the year from a datetime. * Extract the year from a datetime.
*/ */
public class Year extends DateTimeHistogramFunction { public class Year extends DateTimeHistogramFunction {
private static long YEAR_IN_MILLIS = TimeUnit.DAYS.toMillis(1) * 365L;
public Year(Location location, Expression field, TimeZone timeZone) { public Year(Location location, Expression field, TimeZone timeZone) {
super(location, field, timeZone, DateTimeExtractor.YEAR); super(location, field, timeZone, DateTimeExtractor.YEAR);
} }
@ -41,7 +45,7 @@ public class Year extends DateTimeHistogramFunction {
} }
@Override @Override
public String interval() { public long interval() {
return "year"; return YEAR_IN_MILLIS;
} }
} }

View File

@ -9,6 +9,7 @@ package org.elasticsearch.xpack.sql.expression.literal;
import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.sql.proto.StringUtils;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
import java.io.IOException; import java.io.IOException;
@ -46,6 +47,8 @@ public abstract class Interval<I extends TemporalAmount> implements NamedWriteab
public abstract Interval<I> sub(Interval<I> interval); public abstract Interval<I> sub(Interval<I> interval);
public abstract Interval<I> mul(long mul);
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(interval, intervalType); return Objects.hash(interval, intervalType);
@ -73,6 +76,6 @@ public abstract class Interval<I extends TemporalAmount> implements NamedWriteab
@Override @Override
public String toString() { public String toString() {
return intervalType.name() + "[" + interval + "]"; return StringUtils.toString(interval);
} }
} }

View File

@ -54,4 +54,9 @@ public class IntervalDayTime extends Interval<Duration> {
public IntervalDayTime sub(Interval<Duration> interval) { public IntervalDayTime sub(Interval<Duration> interval) {
return new IntervalDayTime(interval().minus(interval.interval()), DataTypes.compatibleInterval(dataType(), interval.dataType())); return new IntervalDayTime(interval().minus(interval.interval()), DataTypes.compatibleInterval(dataType(), interval.dataType()));
} }
@Override
public Interval<Duration> mul(long mul) {
return new IntervalDayTime(interval().multipliedBy(mul), dataType());
}
} }

View File

@ -9,6 +9,7 @@ package org.elasticsearch.xpack.sql.expression.literal;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.DataTypeConversion;
import org.elasticsearch.xpack.sql.type.DataTypes; import org.elasticsearch.xpack.sql.type.DataTypes;
import java.io.IOException; import java.io.IOException;
@ -58,4 +59,10 @@ public class IntervalYearMonth extends Interval<Period> {
return new IntervalYearMonth(interval().minus(interval.interval()).normalized(), return new IntervalYearMonth(interval().minus(interval.interval()).normalized(),
DataTypes.compatibleInterval(dataType(), interval.dataType())); DataTypes.compatibleInterval(dataType(), interval.dataType()));
} }
@Override
public Interval<Period> mul(long mul) {
int i = DataTypeConversion.safeToInt(mul);
return new IntervalYearMonth(interval().multipliedBy(i), dataType());
}
} }

View File

@ -10,9 +10,12 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry; import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.Foldables;
import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.parser.ParsingException; import org.elasticsearch.xpack.sql.parser.ParsingException;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.util.Check;
import org.elasticsearch.xpack.sql.util.StringUtils; import org.elasticsearch.xpack.sql.util.StringUtils;
import java.time.Duration; import java.time.Duration;
@ -44,6 +47,22 @@ public final class Intervals {
private Intervals() {} private Intervals() {}
public static long inMillis(Literal literal) {
Object fold = Foldables.valueOf(literal);
Check.isTrue(fold instanceof Interval, "Expected interval, received [{}]", fold);
TemporalAmount interval = ((Interval<?>) fold).interval();
long millis = 0;
if (interval instanceof Period) {
Period p = (Period) interval;
millis = p.toTotalMonths() * 30 * 24 * 60 * 60 * 1000;
} else {
Duration d = (Duration) interval;
millis = d.toMillis();
}
return millis;
}
public static TemporalAmount of(Location source, long duration, TimeUnit unit) { public static TemporalAmount of(Location source, long duration, TimeUnit unit) {
// Cannot use Period.of since it accepts int so use plus which accepts long // Cannot use Period.of since it accepts int so use plus which accepts long
// Further more Period and Duration have inconsistent addition methods but plus is there // Further more Period and Duration have inconsistent addition methods but plus is there

View File

@ -82,7 +82,28 @@ public class BinaryArithmeticProcessor extends FunctionalBinaryProcessor<Object,
throw new SqlIllegalArgumentException("Cannot compute [-] between [{}] [{}]", l.getClass().getSimpleName(), throw new SqlIllegalArgumentException("Cannot compute [-] between [{}] [{}]", l.getClass().getSimpleName(),
r.getClass().getSimpleName()); r.getClass().getSimpleName());
}, "-"), }, "-"),
MUL(Arithmetics::mul, "*"), MUL((Object l, Object r) -> {
if (l instanceof Number && r instanceof Number) {
return Arithmetics.mul((Number) l, (Number) r);
}
l = unwrapJodaTime(l);
r = unwrapJodaTime(r);
if (l instanceof Number && r instanceof IntervalYearMonth) {
return ((IntervalYearMonth) r).mul(((Number) l).intValue());
}
if (r instanceof Number && l instanceof IntervalYearMonth) {
return ((IntervalYearMonth) l).mul(((Number) r).intValue());
}
if (l instanceof Number && r instanceof IntervalDayTime) {
return ((IntervalDayTime) r).mul(((Number) l).longValue());
}
if (r instanceof Number && l instanceof IntervalDayTime) {
return ((IntervalDayTime) l).mul(((Number) r).longValue());
}
throw new SqlIllegalArgumentException("Cannot compute [*] between [{}] [{}]", l.getClass().getSimpleName(),
r.getClass().getSimpleName());
}, "*"),
DIV(Arithmetics::div, "/"), DIV(Arithmetics::div, "/"),
MOD(Arithmetics::mod, "%"); MOD(Arithmetics::mod, "%");

View File

@ -9,16 +9,55 @@ import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation; import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo; import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.DataTypes;
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
/** /**
* Multiplication function ({@code a * b}). * Multiplication function ({@code a * b}).
*/ */
public class Mul extends ArithmeticOperation { public class Mul extends ArithmeticOperation {
private DataType dataType;
public Mul(Location location, Expression left, Expression right) { public Mul(Location location, Expression left, Expression right) {
super(location, left, right, BinaryArithmeticOperation.MUL); super(location, left, right, BinaryArithmeticOperation.MUL);
} }
@Override
protected TypeResolution resolveType() {
if (!childrenResolved()) {
return new TypeResolution("Unresolved children");
}
DataType l = left().dataType();
DataType r = right().dataType();
// 1. both are numbers
if (l.isNumeric() && r.isNumeric()) {
return TypeResolution.TYPE_RESOLVED;
}
if (DataTypes.isInterval(l) && r.isInteger()) {
dataType = l;
return TypeResolution.TYPE_RESOLVED;
} else if (DataTypes.isInterval(r) && l.isInteger()) {
dataType = r;
return TypeResolution.TYPE_RESOLVED;
}
return new TypeResolution(format("[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
}
@Override
public DataType dataType() {
if (dataType == null) {
dataType = super.dataType();
}
return dataType;
}
@Override @Override
protected NodeInfo<Mul> info() { protected NodeInfo<Mul> info() {
return NodeInfo.create(this, Mul::new, left(), right()); return NodeInfo.create(this, Mul::new, left(), right());

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.optimizer; package org.elasticsearch.xpack.sql.optimizer;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.analysis.analyzer.Analyzer.CleanAliases;
import org.elasticsearch.xpack.sql.expression.Alias; import org.elasticsearch.xpack.sql.expression.Alias;
import org.elasticsearch.xpack.sql.expression.Attribute; import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.AttributeMap; import org.elasticsearch.xpack.sql.expression.AttributeMap;
@ -110,11 +111,6 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
@Override @Override
protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() { protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
Batch resolution = new Batch("Finish Analysis",
new PruneSubqueryAliases(),
CleanAliases.INSTANCE
);
Batch aggregate = new Batch("Aggregation", Batch aggregate = new Batch("Aggregation",
new PruneDuplicatesInGroupBy(), new PruneDuplicatesInGroupBy(),
new ReplaceDuplicateAggsWithReferences(), new ReplaceDuplicateAggsWithReferences(),
@ -162,70 +158,10 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
Batch label = new Batch("Set as Optimized", Limiter.ONCE, Batch label = new Batch("Set as Optimized", Limiter.ONCE,
new SetAsOptimized()); new SetAsOptimized());
return Arrays.asList(resolution, aggregate, operators, local, label); return Arrays.asList(aggregate, operators, local, label);
} }
static class PruneSubqueryAliases extends OptimizerRule<SubQueryAlias> {
PruneSubqueryAliases() {
super(TransformDirection.UP);
}
@Override
protected LogicalPlan rule(SubQueryAlias alias) {
return alias.child();
}
}
static class CleanAliases extends OptimizerRule<LogicalPlan> {
private static final CleanAliases INSTANCE = new CleanAliases();
CleanAliases() {
super(TransformDirection.UP);
}
@Override
protected LogicalPlan rule(LogicalPlan plan) {
if (plan instanceof Project) {
Project p = (Project) plan;
return new Project(p.location(), p.child(), cleanExpressions(p.projections()));
}
if (plan instanceof Aggregate) {
Aggregate a = (Aggregate) plan;
// clean group expressions
List<Expression> cleanedGroups = a.groupings().stream().map(CleanAliases::trimAliases).collect(toList());
return new Aggregate(a.location(), a.child(), cleanedGroups, cleanExpressions(a.aggregates()));
}
return plan.transformExpressionsOnly(e -> {
if (e instanceof Alias) {
return ((Alias) e).child();
}
return e;
});
}
private List<NamedExpression> cleanExpressions(List<? extends NamedExpression> args) {
return args.stream().map(CleanAliases::trimNonTopLevelAliases).map(NamedExpression.class::cast)
.collect(toList());
}
static Expression trimNonTopLevelAliases(Expression e) {
if (e instanceof Alias) {
Alias a = (Alias) e;
return new Alias(a.location(), a.name(), a.qualifier(), trimAliases(a.child()), a.id());
}
return trimAliases(e);
}
private static Expression trimAliases(Expression e) {
return e.transformDown(Alias::child, Alias.class);
}
}
static class PruneDuplicatesInGroupBy extends OptimizerRule<Aggregate> { static class PruneDuplicatesInGroupBy extends OptimizerRule<Aggregate> {
@Override @Override

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.parser;
import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.v4.runtime.tree.TerminalNode;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
@ -85,6 +86,12 @@ abstract class AbstractBuilder extends SqlBaseBaseVisitor<Object> {
return new Location(token.getLine(), token.getCharPositionInLine()); return new Location(token.getLine(), token.getCharPositionInLine());
} }
static String text(ParserRuleContext parserRuleContext) {
Check.notNull(parserRuleContext, "parserRuleContext is null");
Interval interval = new Interval(parserRuleContext.start.getStartIndex(), parserRuleContext.stop.getStopIndex());
return parserRuleContext.start.getInputStream().getText(interval);
}
/** /**
* Retrieves the raw text of the node (without interpreting it as a string literal). * Retrieves the raw text of the node (without interpreting it as a string literal).
*/ */

View File

@ -63,6 +63,7 @@ import org.elasticsearch.xpack.sql.parser.SqlBaseParser.BuiltinDateTimeFunctionC
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.CastExpressionContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.CastExpressionContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.CastTemplateContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.CastTemplateContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ComparisonContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ComparisonContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ConstantDefaultContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ConvertTemplateContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ConvertTemplateContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.DateEscapedLiteralContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.DateEscapedLiteralContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.DecimalLiteralContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.DecimalLiteralContext;
@ -103,6 +104,7 @@ import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SubqueryExpressionContex
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SysTypesContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SysTypesContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.TimeEscapedLiteralContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.TimeEscapedLiteralContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.TimestampEscapedLiteralContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.TimestampEscapedLiteralContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ValueExpressionDefaultContext;
import org.elasticsearch.xpack.sql.proto.SqlTypedParamValue; import org.elasticsearch.xpack.sql.proto.SqlTypedParamValue;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
@ -546,9 +548,7 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
} }
@Override @Override
public Literal visitIntervalLiteral(IntervalLiteralContext ctx) { public Literal visitInterval(IntervalContext interval) {
IntervalContext interval = ctx.interval();
TimeUnit leading = visitIntervalField(interval.leading); TimeUnit leading = visitIntervalField(interval.leading);
TimeUnit trailing = visitIntervalField(interval.trailing); TimeUnit trailing = visitIntervalField(interval.trailing);
@ -571,10 +571,31 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
DataType intervalType = Intervals.intervalType(source(interval), leading, trailing); DataType intervalType = Intervals.intervalType(source(interval), leading, trailing);
boolean negative = interval.sign != null && interval.sign.getType() == SqlBaseParser.MINUS; // negation outside the interval - use xor
boolean negative = false;
ParserRuleContext parentCtx = interval.getParent();
if (parentCtx != null) {
if (parentCtx instanceof IntervalLiteralContext) {
parentCtx = parentCtx.getParent();
if (parentCtx instanceof ConstantDefaultContext) {
parentCtx = parentCtx.getParent();
if (parentCtx instanceof ValueExpressionDefaultContext) {
parentCtx = parentCtx.getParent();
if (parentCtx instanceof ArithmeticUnaryContext) {
ArithmeticUnaryContext auc = (ArithmeticUnaryContext) parentCtx;
negative = auc.MINUS() != null;
}
}
}
}
}
// negation inside the interval
negative ^= interval.sign != null && interval.sign.getType() == SqlBaseParser.MINUS;
TemporalAmount value = null; TemporalAmount value = null;
String valueAsText = null;
if (interval.valueNumeric != null) { if (interval.valueNumeric != null) {
if (trailing != null) { if (trailing != null) {
@ -583,18 +604,14 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
+ "use the string notation instead", trailing); + "use the string notation instead", trailing);
} }
value = of(interval.valueNumeric, leading); value = of(interval.valueNumeric, leading);
valueAsText = interval.valueNumeric.getText();
} else { } else {
value = of(interval.valuePattern, negative, intervalType); value = of(interval.valuePattern, negative, intervalType);
valueAsText = interval.valuePattern.getText();
} }
String name = "INTERVAL " + valueAsText + " " + leading.name() + (trailing != null ? " TO " + trailing.name() : "");
Interval<?> timeInterval = value instanceof Period ? new IntervalYearMonth((Period) value, Interval<?> timeInterval = value instanceof Period ? new IntervalYearMonth((Period) value,
intervalType) : new IntervalDayTime((Duration) value, intervalType); intervalType) : new IntervalDayTime((Duration) value, intervalType);
return new Literal(source(ctx), name, timeInterval, intervalType); return new Literal(source(interval), text(interval), timeInterval, timeInterval.dataType());
} }
private TemporalAmount of(NumberContext valueNumeric, TimeUnit unit) { private TemporalAmount of(NumberContext valueNumeric, TimeUnit unit) {

View File

@ -22,6 +22,7 @@ import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFuncti
import org.elasticsearch.xpack.sql.expression.function.aggregate.CompoundNumericAggregate; import org.elasticsearch.xpack.sql.expression.function.aggregate.CompoundNumericAggregate;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Count; import org.elasticsearch.xpack.sql.expression.function.aggregate.Count;
import org.elasticsearch.xpack.sql.expression.function.aggregate.InnerAggregate; import org.elasticsearch.xpack.sql.expression.function.aggregate.InnerAggregate;
import org.elasticsearch.xpack.sql.expression.function.grouping.GroupingFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction; import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunctionAttribute; import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunctionAttribute;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction;
@ -336,6 +337,11 @@ class QueryFolder extends RuleExecutor<PhysicalPlan> {
TimeZone dt = DataType.DATE == child.dataType() ? UTC : null; TimeZone dt = DataType.DATE == child.dataType() ? UTC : null;
queryC = queryC.addColumn(new GroupByRef(matchingGroup.id(), null, dt)); queryC = queryC.addColumn(new GroupByRef(matchingGroup.id(), null, dt));
} }
// handle histogram
else if (child instanceof GroupingFunction) {
queryC = queryC.addColumn(new GroupByRef(matchingGroup.id(), null, null));
}
// fallback to regular agg functions
else { else {
// the only thing left is agg function // the only thing left is agg function
Check.isTrue(Functions.isAggregate(child), Check.isTrue(Functions.isAggregate(child),

View File

@ -11,6 +11,7 @@ import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.ExpressionId; import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.Expressions; import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.FieldAttribute; import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.Foldables;
import org.elasticsearch.xpack.sql.expression.Literal; import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.NamedExpression; import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.function.Function; import org.elasticsearch.xpack.sql.expression.function.Function;
@ -27,9 +28,12 @@ import org.elasticsearch.xpack.sql.expression.function.aggregate.PercentileRanks
import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentiles; import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentiles;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Stats; import org.elasticsearch.xpack.sql.expression.function.aggregate.Stats;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Sum; import org.elasticsearch.xpack.sql.expression.function.aggregate.Sum;
import org.elasticsearch.xpack.sql.expression.function.grouping.GroupingFunction;
import org.elasticsearch.xpack.sql.expression.function.grouping.Histogram;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction; import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeHistogramFunction; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeHistogramFunction;
import org.elasticsearch.xpack.sql.expression.literal.Intervals;
import org.elasticsearch.xpack.sql.expression.predicate.Range; import org.elasticsearch.xpack.sql.expression.predicate.Range;
import org.elasticsearch.xpack.sql.expression.predicate.fulltext.MatchQueryPredicate; import org.elasticsearch.xpack.sql.expression.predicate.fulltext.MatchQueryPredicate;
import org.elasticsearch.xpack.sql.expression.predicate.fulltext.MultiMatchQueryPredicate; import org.elasticsearch.xpack.sql.expression.predicate.fulltext.MultiMatchQueryPredicate;
@ -57,10 +61,10 @@ import org.elasticsearch.xpack.sql.querydsl.agg.AndAggFilter;
import org.elasticsearch.xpack.sql.querydsl.agg.AvgAgg; import org.elasticsearch.xpack.sql.querydsl.agg.AvgAgg;
import org.elasticsearch.xpack.sql.querydsl.agg.CardinalityAgg; import org.elasticsearch.xpack.sql.querydsl.agg.CardinalityAgg;
import org.elasticsearch.xpack.sql.querydsl.agg.ExtendedStatsAgg; import org.elasticsearch.xpack.sql.querydsl.agg.ExtendedStatsAgg;
import org.elasticsearch.xpack.sql.querydsl.agg.GroupByColumnKey; import org.elasticsearch.xpack.sql.querydsl.agg.GroupByDateHistogram;
import org.elasticsearch.xpack.sql.querydsl.agg.GroupByDateKey;
import org.elasticsearch.xpack.sql.querydsl.agg.GroupByKey; import org.elasticsearch.xpack.sql.querydsl.agg.GroupByKey;
import org.elasticsearch.xpack.sql.querydsl.agg.GroupByScriptKey; import org.elasticsearch.xpack.sql.querydsl.agg.GroupByNumericHistogram;
import org.elasticsearch.xpack.sql.querydsl.agg.GroupByValue;
import org.elasticsearch.xpack.sql.querydsl.agg.LeafAgg; import org.elasticsearch.xpack.sql.querydsl.agg.LeafAgg;
import org.elasticsearch.xpack.sql.querydsl.agg.MatrixStatsAgg; import org.elasticsearch.xpack.sql.querydsl.agg.MatrixStatsAgg;
import org.elasticsearch.xpack.sql.querydsl.agg.MaxAgg; import org.elasticsearch.xpack.sql.querydsl.agg.MaxAgg;
@ -85,6 +89,7 @@ import org.elasticsearch.xpack.sql.querydsl.query.TermQuery;
import org.elasticsearch.xpack.sql.querydsl.query.TermsQuery; import org.elasticsearch.xpack.sql.querydsl.query.TermsQuery;
import org.elasticsearch.xpack.sql.querydsl.query.WildcardQuery; import org.elasticsearch.xpack.sql.querydsl.query.WildcardQuery;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.util.Check; import org.elasticsearch.xpack.sql.util.Check;
import org.elasticsearch.xpack.sql.util.ReflectionUtils; import org.elasticsearch.xpack.sql.util.ReflectionUtils;
@ -231,10 +236,16 @@ final class QueryTranslator {
Map<ExpressionId, GroupByKey> aggMap = new LinkedHashMap<>(); Map<ExpressionId, GroupByKey> aggMap = new LinkedHashMap<>();
for (Expression exp : groupings) { for (Expression exp : groupings) {
GroupByKey key = null;
ExpressionId id;
String aggId; String aggId;
if (exp instanceof NamedExpression) { if (exp instanceof NamedExpression) {
NamedExpression ne = (NamedExpression) exp; NamedExpression ne = (NamedExpression) exp;
id = ne.id();
aggId = id.toString();
// change analyzed to non non-analyzed attributes // change analyzed to non non-analyzed attributes
if (exp instanceof FieldAttribute) { if (exp instanceof FieldAttribute) {
FieldAttribute fa = (FieldAttribute) exp; FieldAttribute fa = (FieldAttribute) exp;
@ -242,21 +253,51 @@ final class QueryTranslator {
ne = fa.exactAttribute(); ne = fa.exactAttribute();
} }
} }
aggId = ne.id().toString();
GroupByKey key;
// handle functions differently // handle functions differently
if (exp instanceof Function) { if (exp instanceof Function) {
// dates are handled differently because of date histograms // dates are handled differently because of date histograms
if (exp instanceof DateTimeHistogramFunction) { if (exp instanceof DateTimeHistogramFunction) {
DateTimeHistogramFunction dthf = (DateTimeHistogramFunction) exp; DateTimeHistogramFunction dthf = (DateTimeHistogramFunction) exp;
key = new GroupByDateKey(aggId, nameOf(exp), dthf.interval(), dthf.timeZone()); key = new GroupByDateHistogram(aggId, nameOf(exp), dthf.interval(), dthf.timeZone());
} }
// all other scalar functions become a script // all other scalar functions become a script
else if (exp instanceof ScalarFunction) { else if (exp instanceof ScalarFunction) {
ScalarFunction sf = (ScalarFunction) exp; ScalarFunction sf = (ScalarFunction) exp;
key = new GroupByScriptKey(aggId, nameOf(exp), sf.asScript()); key = new GroupByValue(aggId, sf.asScript());
}
// histogram
else if (exp instanceof GroupingFunction) {
if (exp instanceof Histogram) {
Histogram h = (Histogram) exp;
Expression field = h.field();
// date histogram
if (h.dataType() == DataType.DATE) {
long intervalAsMillis = Intervals.inMillis(h.interval());
// TODO: set timezone
if (field instanceof FieldAttribute || field instanceof DateTimeHistogramFunction) {
key = new GroupByDateHistogram(aggId, nameOf(field), intervalAsMillis, h.timeZone());
} else if (field instanceof Function) {
key = new GroupByDateHistogram(aggId, ((Function) field).asScript(), intervalAsMillis, h.timeZone());
}
}
// numeric histogram
else {
if (field instanceof FieldAttribute || field instanceof DateTimeHistogramFunction) {
key = new GroupByNumericHistogram(aggId, nameOf(field), Foldables.doubleValueOf(h.interval()));
} else if (field instanceof Function) {
key = new GroupByNumericHistogram(aggId, ((Function) field).asScript(),
Foldables.doubleValueOf(h.interval()));
}
}
if (key == null) {
throw new SqlIllegalArgumentException("Unsupported histogram field {}", field);
}
}
else {
throw new SqlIllegalArgumentException("Unsupproted grouping function {}", exp);
}
} }
// bumped into into an invalid function (which should be caught by the verifier) // bumped into into an invalid function (which should be caught by the verifier)
else { else {
@ -264,14 +305,14 @@ final class QueryTranslator {
} }
} }
else { else {
key = new GroupByColumnKey(aggId, ne.name()); key = new GroupByValue(aggId, ne.name());
} }
aggMap.put(ne.id(), key);
} }
else { else {
throw new SqlIllegalArgumentException("Don't know how to group on {}", exp.nodeString()); throw new SqlIllegalArgumentException("Don't know how to group on {}", exp.nodeString());
} }
aggMap.put(id, key);
} }
return new GroupingContext(aggMap); return new GroupingContext(aggMap);
} }

View File

@ -27,7 +27,7 @@ public class RestSqlClearCursorAction extends BaseRestHandler {
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestSqlClearCursorAction.class)); private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestSqlClearCursorAction.class));
RestSqlClearCursorAction(Settings settings, RestController controller) { public RestSqlClearCursorAction(Settings settings, RestController controller) {
super(settings); super(settings);
// TODO: remove deprecated endpoint in 8.0.0 // TODO: remove deprecated endpoint in 8.0.0
controller.registerWithDeprecatedHandler( controller.registerWithDeprecatedHandler(

View File

@ -38,7 +38,7 @@ public class RestSqlQueryAction extends BaseRestHandler {
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestSqlQueryAction.class)); private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestSqlQueryAction.class));
RestSqlQueryAction(Settings settings, RestController controller) { public RestSqlQueryAction(Settings settings, RestController controller) {
super(settings); super(settings);
// TODO: remove deprecated endpoint in 8.0.0 // TODO: remove deprecated endpoint in 8.0.0
controller.registerWithDeprecatedHandler( controller.registerWithDeprecatedHandler(
@ -56,8 +56,8 @@ public class RestSqlQueryAction extends BaseRestHandler {
SqlQueryRequest sqlRequest; SqlQueryRequest sqlRequest;
try (XContentParser parser = request.contentOrSourceParamParser()) { try (XContentParser parser = request.contentOrSourceParamParser()) {
sqlRequest = SqlQueryRequest.fromXContent(parser); sqlRequest = SqlQueryRequest.fromXContent(parser);
} }
/* /*
* Since we support {@link TextFormat} <strong>and</strong> * Since we support {@link TextFormat} <strong>and</strong>
* {@link XContent} outputs we can't use {@link RestToXContentListener} * {@link XContent} outputs we can't use {@link RestToXContentListener}

View File

@ -27,7 +27,7 @@ public abstract class Agg {
return id; return id;
} }
public String fieldName() { protected String fieldName() {
return fieldName; return fieldName;
} }

View File

@ -10,6 +10,7 @@ import org.elasticsearch.search.aggregations.bucket.composite.CompositeAggregati
import org.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder; import org.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregationBuilder;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction; import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,15 +40,15 @@ public class Aggs {
public static final String ROOT_GROUP_NAME = "groupby"; public static final String ROOT_GROUP_NAME = "groupby";
public static final GroupByKey IMPLICIT_GROUP_KEY = new GroupByKey(ROOT_GROUP_NAME, EMPTY, null) { public static final GroupByKey IMPLICIT_GROUP_KEY = new GroupByKey(ROOT_GROUP_NAME, EMPTY, null, null) {
@Override @Override
public CompositeValuesSourceBuilder<?> asValueSource() { public CompositeValuesSourceBuilder<?> createSourceBuilder() {
throw new SqlIllegalArgumentException("Default group does not translate to an aggregation"); throw new SqlIllegalArgumentException("Default group does not translate to an aggregation");
} }
@Override @Override
protected GroupByKey copy(String id, String fieldName, Direction direction) { protected GroupByKey copy(String id, String fieldName, ScriptTemplate script, Direction direction) {
return this; return this;
} }
}; };

View File

@ -1,36 +0,0 @@
/*
* 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.querydsl.agg;
import org.elasticsearch.search.aggregations.bucket.composite.TermsValuesSourceBuilder;
import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction;
/**
* GROUP BY key for regular fields.
*/
public class GroupByColumnKey extends GroupByKey {
public GroupByColumnKey(String id, String fieldName) {
this(id, fieldName, null);
}
public GroupByColumnKey(String id, String fieldName, Direction direction) {
super(id, fieldName, direction);
}
@Override
public TermsValuesSourceBuilder asValueSource() {
return new TermsValuesSourceBuilder(id())
.field(fieldName())
.order(direction().asOrder())
.missingBucket(true);
}
@Override
protected GroupByKey copy(String id, String fieldName, Direction direction) {
return new GroupByColumnKey(id, fieldName, direction);
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.querydsl.agg;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
import org.elasticsearch.search.aggregations.bucket.composite.DateHistogramValuesSourceBuilder;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction;
import org.joda.time.DateTimeZone;
import java.util.Objects;
import java.util.TimeZone;
/**
* GROUP BY key based on histograms on date fields.
*/
public class GroupByDateHistogram extends GroupByKey {
private final long interval;
private final TimeZone timeZone;
public GroupByDateHistogram(String id, String fieldName, long interval, TimeZone timeZone) {
this(id, fieldName, null, null, interval, timeZone);
}
public GroupByDateHistogram(String id, ScriptTemplate script, long interval, TimeZone timeZone) {
this(id, null, script, null, interval, timeZone);
}
private GroupByDateHistogram(String id, String fieldName, ScriptTemplate script, Direction direction, long interval,
TimeZone timeZone) {
super(id, fieldName, script, direction);
this.interval = interval;
this.timeZone = timeZone;
}
@Override
protected CompositeValuesSourceBuilder<?> createSourceBuilder() {
return new DateHistogramValuesSourceBuilder(id())
.interval(interval)
.timeZone(DateTimeZone.forTimeZone(timeZone));
}
@Override
protected GroupByKey copy(String id, String fieldName, ScriptTemplate script, Direction direction) {
return new GroupByDateHistogram(id, fieldName, script, direction, interval, timeZone);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), interval, timeZone);
}
@Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
GroupByDateHistogram other = (GroupByDateHistogram) obj;
return Objects.equals(interval, other.interval)
&& Objects.equals(timeZone, other.timeZone);
}
return false;
}
}

View File

@ -1,70 +0,0 @@
/*
* 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.querydsl.agg;
import org.elasticsearch.search.aggregations.bucket.composite.DateHistogramValuesSourceBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction;
import org.joda.time.DateTimeZone;
import java.util.Objects;
import java.util.TimeZone;
/**
* GROUP BY key specific for date fields.
*/
public class GroupByDateKey extends GroupByKey {
private final String interval;
private final TimeZone timeZone;
public GroupByDateKey(String id, String fieldName, String interval, TimeZone timeZone) {
this(id, fieldName, null, interval, timeZone);
}
public GroupByDateKey(String id, String fieldName, Direction direction, String interval, TimeZone timeZone) {
super(id, fieldName, direction);
this.interval = interval;
this.timeZone = timeZone;
}
public String interval() {
return interval;
}
public TimeZone timeZone() {
return timeZone;
}
@Override
public DateHistogramValuesSourceBuilder asValueSource() {
return new DateHistogramValuesSourceBuilder(id())
.field(fieldName())
.dateHistogramInterval(new DateHistogramInterval(interval))
.timeZone(DateTimeZone.forTimeZone(timeZone))
.missingBucket(true);
}
@Override
protected GroupByKey copy(String id, String fieldName, Direction direction) {
return new GroupByDateKey(id, fieldName, direction, interval, timeZone);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), interval, timeZone);
}
@Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
GroupByDateKey other = (GroupByDateKey) obj;
return Objects.equals(interval, other.interval)
&& Objects.equals(timeZone, other.timeZone);
}
return false;
}
}

View File

@ -6,7 +6,10 @@
package org.elasticsearch.xpack.sql.querydsl.agg; package org.elasticsearch.xpack.sql.querydsl.agg;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder; import org.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
import org.elasticsearch.search.aggregations.support.ValueType;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction; import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Objects; import java.util.Objects;
@ -15,33 +18,64 @@ import java.util.Objects;
*/ */
public abstract class GroupByKey extends Agg { public abstract class GroupByKey extends Agg {
private final Direction direction; protected final Direction direction;
private final ScriptTemplate script;
GroupByKey(String id, String fieldName, Direction direction) { protected GroupByKey(String id, String fieldName, ScriptTemplate script, Direction direction) {
super(id, fieldName); super(id, fieldName);
// ASC is the default order of CompositeValueSource // ASC is the default order of CompositeValueSource
this.direction = direction == null ? Direction.ASC : direction; this.direction = direction == null ? Direction.ASC : direction;
this.script = script;
} }
public Direction direction() { public final CompositeValuesSourceBuilder<?> asValueSource() {
return direction; CompositeValuesSourceBuilder<?> builder = createSourceBuilder();
if (script != null) {
builder.script(script.toPainless());
if (script.outputType().isInteger()) {
builder.valueType(ValueType.LONG);
} else if (script.outputType().isRational()) {
builder.valueType(ValueType.DOUBLE);
} else if (script.outputType().isString()) {
builder.valueType(ValueType.STRING);
} else if (script.outputType() == DataType.DATE) {
builder.valueType(ValueType.DATE);
} else if (script.outputType() == DataType.BOOLEAN) {
builder.valueType(ValueType.BOOLEAN);
} else if (script.outputType() == DataType.IP) {
builder.valueType(ValueType.IP);
}
}
// field based
else {
builder.field(fieldName());
}
return builder.order(direction.asOrder())
.missingBucket(true);
} }
public abstract CompositeValuesSourceBuilder<?> asValueSource(); protected abstract CompositeValuesSourceBuilder<?> createSourceBuilder();
protected abstract GroupByKey copy(String id, String fieldName, Direction direction); protected abstract GroupByKey copy(String id, String fieldName, ScriptTemplate script, Direction direction);
public GroupByKey with(Direction direction) { public GroupByKey with(Direction direction) {
return this.direction == direction ? this : copy(id(), fieldName(), direction); return this.direction == direction ? this : copy(id(), fieldName(), script, direction);
}
public ScriptTemplate script() {
return script;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id(), fieldName(), direction); return Objects.hash(id(), fieldName(), script, direction);
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return super.equals(obj) && Objects.equals(direction, ((GroupByKey) obj).direction); return super.equals(obj)
&& Objects.equals(script, ((GroupByKey) obj).script)
&& Objects.equals(direction, ((GroupByKey) obj).direction);
} }
} }

View File

@ -0,0 +1,59 @@
/*
* 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.querydsl.agg;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
import org.elasticsearch.search.aggregations.bucket.composite.HistogramValuesSourceBuilder;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction;
import java.util.Objects;
/**
* GROUP BY key based on histograms on numeric fields.
*/
public class GroupByNumericHistogram extends GroupByKey {
private final double interval;
public GroupByNumericHistogram(String id, String fieldName, double interval) {
this(id, fieldName, null, null, interval);
}
public GroupByNumericHistogram(String id, ScriptTemplate script, double interval) {
this(id, null, script, null, interval);
}
private GroupByNumericHistogram(String id, String fieldName, ScriptTemplate script, Direction direction, double interval) {
super(id, fieldName, script, direction);
this.interval = interval;
}
@Override
protected CompositeValuesSourceBuilder<?> createSourceBuilder() {
return new HistogramValuesSourceBuilder(id())
.interval(interval);
}
@Override
protected GroupByKey copy(String id, String fieldName, ScriptTemplate script, Direction direction) {
return new GroupByNumericHistogram(id, fieldName, script, direction, interval);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), interval);
}
@Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
GroupByNumericHistogram other = (GroupByNumericHistogram) obj;
return interval == other.interval;
}
return false;
}
}

View File

@ -1,74 +0,0 @@
/*
* 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.querydsl.agg;
import org.elasticsearch.search.aggregations.bucket.composite.TermsValuesSourceBuilder;
import org.elasticsearch.search.aggregations.support.ValueType;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Objects;
/**
* GROUP BY key for scripts (typically caused by functions).
*/
public class GroupByScriptKey extends GroupByKey {
private final ScriptTemplate script;
public GroupByScriptKey(String id, String fieldName, ScriptTemplate script) {
this(id, fieldName, null, script);
}
public GroupByScriptKey(String id, String fieldName, Direction direction, ScriptTemplate script) {
super(id, fieldName, direction);
this.script = script;
}
public ScriptTemplate script() {
return script;
}
@Override
public TermsValuesSourceBuilder asValueSource() {
TermsValuesSourceBuilder builder = new TermsValuesSourceBuilder(id())
.script(script.toPainless())
.order(direction().asOrder())
.missingBucket(true);
if (script.outputType().isInteger()) {
builder.valueType(ValueType.LONG);
} else if (script.outputType().isRational()) {
builder.valueType(ValueType.DOUBLE);
} else if (script.outputType().isString()) {
builder.valueType(ValueType.STRING);
} else if (script.outputType() == DataType.DATE) {
builder.valueType(ValueType.DATE);
} else if (script.outputType() == DataType.BOOLEAN) {
builder.valueType(ValueType.BOOLEAN);
} else if (script.outputType() == DataType.IP) {
builder.valueType(ValueType.IP);
}
return builder;
}
@Override
protected GroupByKey copy(String id, String fieldName, Direction direction) {
return new GroupByScriptKey(id, fieldName, direction, script);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), script);
}
@Override
public boolean equals(Object obj) {
return super.equals(obj) && Objects.equals(script, ((GroupByScriptKey) obj).script);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.querydsl.agg;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
import org.elasticsearch.search.aggregations.bucket.composite.TermsValuesSourceBuilder;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.querydsl.container.Sort.Direction;
/**
* GROUP BY key for fields or scripts.
*/
public class GroupByValue extends GroupByKey {
public GroupByValue(String id, String fieldName) {
this(id, fieldName, null, null);
}
public GroupByValue(String id, ScriptTemplate script) {
this(id, null, script, null);
}
private GroupByValue(String id, String fieldName, ScriptTemplate script, Direction direction) {
super(id, fieldName, script, direction);
}
@Override
protected CompositeValuesSourceBuilder<?> createSourceBuilder() {
return new TermsValuesSourceBuilder(id());
}
@Override
protected GroupByKey copy(String id, String fieldName, ScriptTemplate script, Direction direction) {
return new GroupByValue(id, fieldName, script, direction);
}
}

View File

@ -178,11 +178,24 @@ public class VerifierErrorMessagesTests extends ESTestCase {
} }
// GROUP BY // GROUP BY
public void testGroupBySelectWithAlias() {
assertNotNull(accept("SELECT int AS i FROM test GROUP BY i"));
}
public void testGroupBySelectWithAliasOrderOnActualField() {
assertNotNull(accept("SELECT int AS i FROM test GROUP BY i ORDER BY int"));
}
public void testGroupBySelectNonGrouped() { public void testGroupBySelectNonGrouped() {
assertEquals("1:8: Cannot use non-grouped column [text], expected [int]", assertEquals("1:8: Cannot use non-grouped column [text], expected [int]",
error("SELECT text, int FROM test GROUP BY int")); error("SELECT text, int FROM test GROUP BY int"));
} }
public void testGroupByFunctionSelectFieldFromGroupByFunction() {
assertEquals("1:8: Cannot use non-grouped column [int], expected [ABS(int)]",
error("SELECT int FROM test GROUP BY ABS(int)"));
}
public void testGroupByOrderByNonGrouped() { public void testGroupByOrderByNonGrouped() {
assertEquals("1:50: Cannot order by non-grouped column [bool], expected [text]", assertEquals("1:50: Cannot order by non-grouped column [bool], expected [text]",
error("SELECT MAX(int) FROM test GROUP BY text ORDER BY bool")); error("SELECT MAX(int) FROM test GROUP BY text ORDER BY bool"));
@ -203,6 +216,11 @@ public class VerifierErrorMessagesTests extends ESTestCase {
error("SELECT MAX(int) FROM test GROUP BY text ORDER BY YEAR(date)")); error("SELECT MAX(int) FROM test GROUP BY text ORDER BY YEAR(date)"));
} }
public void testGroupByOrderByFieldFromGroupByFunction() {
assertEquals("1:54: Cannot use non-grouped column [int], expected [ABS(int)]",
error("SELECT ABS(int) FROM test GROUP BY ABS(int) ORDER BY int"));
}
public void testGroupByOrderByScalarOverNonGrouped_WithHaving() { public void testGroupByOrderByScalarOverNonGrouped_WithHaving() {
assertEquals("1:71: Cannot order by non-grouped column [YEAR(date [UTC])], expected [text]", assertEquals("1:71: Cannot order by non-grouped column [YEAR(date [UTC])], expected [text]",
error("SELECT MAX(int) FROM test GROUP BY text HAVING MAX(int) > 10 ORDER BY YEAR(date)")); error("SELECT MAX(int) FROM test GROUP BY text HAVING MAX(int) > 10 ORDER BY YEAR(date)"));

View File

@ -16,7 +16,7 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.expression.FieldAttribute; import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.function.Score; import org.elasticsearch.xpack.sql.expression.function.Score;
import org.elasticsearch.xpack.sql.querydsl.agg.AvgAgg; import org.elasticsearch.xpack.sql.querydsl.agg.AvgAgg;
import org.elasticsearch.xpack.sql.querydsl.agg.GroupByColumnKey; import org.elasticsearch.xpack.sql.querydsl.agg.GroupByValue;
import org.elasticsearch.xpack.sql.querydsl.container.AttributeSort; import org.elasticsearch.xpack.sql.querydsl.container.AttributeSort;
import org.elasticsearch.xpack.sql.querydsl.container.QueryContainer; import org.elasticsearch.xpack.sql.querydsl.container.QueryContainer;
import org.elasticsearch.xpack.sql.querydsl.container.ScoreSort; import org.elasticsearch.xpack.sql.querydsl.container.ScoreSort;
@ -62,7 +62,7 @@ public class SourceGeneratorTests extends ESTestCase {
} }
public void testLimit() { public void testLimit() {
QueryContainer container = new QueryContainer().withLimit(10).addGroups(singletonList(new GroupByColumnKey("1", "field"))); QueryContainer container = new QueryContainer().withLimit(10).addGroups(singletonList(new GroupByValue("1", "field")));
int size = randomIntBetween(1, 10); int size = randomIntBetween(1, 10);
SearchSourceBuilder sourceBuilder = SourceGenerator.sourceBuilder(container, null, size); SearchSourceBuilder sourceBuilder = SourceGenerator.sourceBuilder(container, null, size);
Builder aggBuilder = sourceBuilder.aggregations(); Builder aggBuilder = sourceBuilder.aggregations();
@ -114,7 +114,7 @@ public class SourceGeneratorTests extends ESTestCase {
public void testNoSortIfAgg() { public void testNoSortIfAgg() {
QueryContainer container = new QueryContainer() QueryContainer container = new QueryContainer()
.addGroups(singletonList(new GroupByColumnKey("group_id", "group_column"))) .addGroups(singletonList(new GroupByValue("group_id", "group_column")))
.addAgg("group_id", new AvgAgg("agg_id", "avg_column")); .addAgg("group_id", new AvgAgg("agg_id", "avg_column"));
SearchSourceBuilder sourceBuilder = SourceGenerator.sourceBuilder(container, null, randomIntBetween(1, 10)); SearchSourceBuilder sourceBuilder = SourceGenerator.sourceBuilder(container, null, randomIntBetween(1, 10));
assertNull(sourceBuilder.sorts()); assertNull(sourceBuilder.sorts());

View File

@ -0,0 +1,48 @@
/*
* 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;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.expression.Expression.TypeResolution;
import org.elasticsearch.xpack.sql.expression.literal.IntervalYearMonth;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Mul;
import org.elasticsearch.xpack.sql.type.DataType;
import java.time.Period;
import static org.elasticsearch.xpack.sql.tree.Location.EMPTY;
public class TyperResolutionTests extends ESTestCase {
public void testMulNumeric() {
Mul m = new Mul(EMPTY, L(1), L(2));
assertEquals(TypeResolution.TYPE_RESOLVED, m.typeResolved());
}
public void testMulIntervalAndNumber() {
Mul m = new Mul(EMPTY, L(1), randomYearInterval());
assertEquals(TypeResolution.TYPE_RESOLVED, m.typeResolved());
}
public void testMulNumberAndInterval() {
Mul m = new Mul(EMPTY, randomYearInterval(), L(1));
assertEquals(TypeResolution.TYPE_RESOLVED, m.typeResolved());
}
public void testMulTypeResolution() throws Exception {
Mul mul = new Mul(EMPTY, randomYearInterval(), randomYearInterval());
assertTrue(mul.typeResolved().unresolved());
}
private static Literal randomYearInterval() {
return Literal.of(EMPTY, new IntervalYearMonth(Period.ofMonths(randomInt(123)), DataType.INTERVAL_YEAR_TO_MONTH));
}
private static Literal L(Object value) {
return Literal.of(EMPTY, value);
}
}

View File

@ -157,6 +157,22 @@ public class BinaryArithmeticTests extends ESTestCase {
assertEquals(L(now.minus(t)), L(x)); assertEquals(L(now.minus(t)), L(x));
} }
public void testMulIntervalNumber() throws Exception {
Literal l = interval(Duration.ofHours(2), INTERVAL_HOUR);
IntervalDayTime interval = mul(l, -1);
assertEquals(INTERVAL_HOUR, interval.dataType());
Duration p = interval.interval();
assertEquals(Duration.ofHours(2).negated(), p);
}
public void testMulNumberInterval() throws Exception {
Literal r = interval(Period.ofYears(1), INTERVAL_YEAR);
IntervalYearMonth interval = mul(-2, r);
assertEquals(INTERVAL_YEAR, interval.dataType());
Period p = interval.interval();
assertEquals(Period.ofYears(2).negated(), p);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static <T> T add(Object l, Object r) { private static <T> T add(Object l, Object r) {
Add add = new Add(EMPTY, L(l), L(r)); Add add = new Add(EMPTY, L(l), L(r));
@ -171,6 +187,12 @@ public class BinaryArithmeticTests extends ESTestCase {
return (T) sub.fold(); return (T) sub.fold();
} }
@SuppressWarnings("unchecked")
private static <T> T mul(Object l, Object r) {
Mul mul = new Mul(EMPTY, L(l), L(r));
assertTrue(mul.foldable());
return (T) mul.fold();
}
private static Literal L(Object value) { private static Literal L(Object value) {
return Literal.of(EMPTY, value); return Literal.of(EMPTY, value);

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.optimizer; package org.elasticsearch.xpack.sql.optimizer;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.analysis.analyzer.Analyzer.PruneSubqueryAliases;
import org.elasticsearch.xpack.sql.expression.Alias; import org.elasticsearch.xpack.sql.expression.Alias;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions; import org.elasticsearch.xpack.sql.expression.Expressions;
@ -71,7 +72,6 @@ import org.elasticsearch.xpack.sql.optimizer.Optimizer.ConstantFolding;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.FoldNull; import org.elasticsearch.xpack.sql.optimizer.Optimizer.FoldNull;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.PropagateEquals; import org.elasticsearch.xpack.sql.optimizer.Optimizer.PropagateEquals;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneDuplicateFunctions; import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneDuplicateFunctions;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneSubqueryAliases;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.ReplaceFoldableAttributes; import org.elasticsearch.xpack.sql.optimizer.Optimizer.ReplaceFoldableAttributes;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.SimplifyConditional; import org.elasticsearch.xpack.sql.optimizer.Optimizer.SimplifyConditional;
import org.elasticsearch.xpack.sql.plan.logical.Filter; import org.elasticsearch.xpack.sql.plan.logical.Filter;

View File

@ -14,7 +14,9 @@ import org.elasticsearch.xpack.sql.analysis.index.EsIndex;
import org.elasticsearch.xpack.sql.analysis.index.IndexResolution; import org.elasticsearch.xpack.sql.analysis.index.IndexResolution;
import org.elasticsearch.xpack.sql.analysis.index.MappingException; import org.elasticsearch.xpack.sql.analysis.index.MappingException;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry; import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry;
import org.elasticsearch.xpack.sql.expression.function.grouping.Histogram;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation; import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate; import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.parser.SqlParser; import org.elasticsearch.xpack.sql.parser.SqlParser;
@ -24,7 +26,6 @@ import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.logical.Project; import org.elasticsearch.xpack.sql.plan.logical.Project;
import org.elasticsearch.xpack.sql.planner.QueryTranslator.QueryTranslation; import org.elasticsearch.xpack.sql.planner.QueryTranslator.QueryTranslation;
import org.elasticsearch.xpack.sql.querydsl.agg.AggFilter; import org.elasticsearch.xpack.sql.querydsl.agg.AggFilter;
import org.elasticsearch.xpack.sql.querydsl.agg.GroupByScriptKey;
import org.elasticsearch.xpack.sql.querydsl.query.ExistsQuery; import org.elasticsearch.xpack.sql.querydsl.query.ExistsQuery;
import org.elasticsearch.xpack.sql.querydsl.query.NotQuery; import org.elasticsearch.xpack.sql.querydsl.query.NotQuery;
import org.elasticsearch.xpack.sql.querydsl.query.Query; import org.elasticsearch.xpack.sql.querydsl.query.Query;
@ -33,12 +34,14 @@ import org.elasticsearch.xpack.sql.querydsl.query.ScriptQuery;
import org.elasticsearch.xpack.sql.querydsl.query.TermQuery; import org.elasticsearch.xpack.sql.querydsl.query.TermQuery;
import org.elasticsearch.xpack.sql.querydsl.query.TermsQuery; import org.elasticsearch.xpack.sql.querydsl.query.TermsQuery;
import org.elasticsearch.xpack.sql.stats.Metrics; import org.elasticsearch.xpack.sql.stats.Metrics;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.EsField; import org.elasticsearch.xpack.sql.type.EsField;
import org.elasticsearch.xpack.sql.type.TypesTests; import org.elasticsearch.xpack.sql.type.TypesTests;
import org.elasticsearch.xpack.sql.util.DateUtils; import org.elasticsearch.xpack.sql.util.DateUtils;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -403,7 +406,7 @@ public class QueryTranslatorTests extends ESTestCase {
assertFalse(condition.foldable()); assertFalse(condition.foldable());
QueryTranslator.GroupingContext groupingContext = QueryTranslator.groupBy(((Aggregate) p).groupings()); QueryTranslator.GroupingContext groupingContext = QueryTranslator.groupBy(((Aggregate) p).groupings());
assertNotNull(groupingContext); assertNotNull(groupingContext);
ScriptTemplate scriptTemplate = ((GroupByScriptKey) groupingContext.tail).script(); ScriptTemplate scriptTemplate = groupingContext.tail.script();
assertEquals("InternalSqlScriptUtils.coalesce([InternalSqlScriptUtils.docValue(doc,params.v0),params.v1])", assertEquals("InternalSqlScriptUtils.coalesce([InternalSqlScriptUtils.docValue(doc,params.v0),params.v1])",
scriptTemplate.toString()); scriptTemplate.toString());
assertEquals("[{v=int}, {v=10}]", scriptTemplate.params().toString()); assertEquals("[{v=int}, {v=10}]", scriptTemplate.params().toString());
@ -416,9 +419,39 @@ public class QueryTranslatorTests extends ESTestCase {
assertFalse(condition.foldable()); assertFalse(condition.foldable());
QueryTranslator.GroupingContext groupingContext = QueryTranslator.groupBy(((Aggregate) p).groupings()); QueryTranslator.GroupingContext groupingContext = QueryTranslator.groupBy(((Aggregate) p).groupings());
assertNotNull(groupingContext); assertNotNull(groupingContext);
ScriptTemplate scriptTemplate = ((GroupByScriptKey) groupingContext.tail).script(); ScriptTemplate scriptTemplate = groupingContext.tail.script();
assertEquals("InternalSqlScriptUtils.nullif(InternalSqlScriptUtils.docValue(doc,params.v0),params.v1)", assertEquals("InternalSqlScriptUtils.nullif(InternalSqlScriptUtils.docValue(doc,params.v0),params.v1)",
scriptTemplate.toString()); scriptTemplate.toString());
assertEquals("[{v=int}, {v=10}]", scriptTemplate.params().toString()); assertEquals("[{v=int}, {v=10}]", scriptTemplate.params().toString());
} }
public void testGroupByDateHistogram() {
LogicalPlan p = plan("SELECT MAX(int) FROM test GROUP BY HISTOGRAM(int, 1000)");
assertTrue(p instanceof Aggregate);
Aggregate a = (Aggregate) p;
List<Expression> groupings = a.groupings();
assertEquals(1, groupings.size());
Expression exp = groupings.get(0);
assertEquals(Histogram.class, exp.getClass());
Histogram h = (Histogram) exp;
assertEquals(1000, h.interval().fold());
Expression field = h.field();
assertEquals(FieldAttribute.class, field.getClass());
assertEquals(DataType.INTEGER, field.dataType());
}
public void testGroupByHistogram() {
LogicalPlan p = plan("SELECT MAX(int) FROM test GROUP BY HISTOGRAM(date, INTERVAL 2 YEARS)");
assertTrue(p instanceof Aggregate);
Aggregate a = (Aggregate) p;
List<Expression> groupings = a.groupings();
assertEquals(1, groupings.size());
Expression exp = groupings.get(0);
assertEquals(Histogram.class, exp.getClass());
Histogram h = (Histogram) exp;
assertEquals("+2-0", h.interval().fold().toString());
Expression field = h.field();
assertEquals(FieldAttribute.class, field.getClass());
assertEquals(DataType.DATE, field.dataType());
}
} }

View File

@ -13,6 +13,7 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.FieldAttribute; import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.Literal; import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.LiteralTests;
import org.elasticsearch.xpack.sql.expression.UnresolvedAttributeTests; import org.elasticsearch.xpack.sql.expression.UnresolvedAttributeTests;
import org.elasticsearch.xpack.sql.expression.function.Function; import org.elasticsearch.xpack.sql.expression.function.Function;
import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunction; import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunction;
@ -21,6 +22,7 @@ import org.elasticsearch.xpack.sql.expression.function.aggregate.InnerAggregate;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentile; import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentile;
import org.elasticsearch.xpack.sql.expression.function.aggregate.PercentileRanks; import org.elasticsearch.xpack.sql.expression.function.aggregate.PercentileRanks;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentiles; import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentiles;
import org.elasticsearch.xpack.sql.expression.function.grouping.Histogram;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.CurrentDateTime; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.CurrentDateTime;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.AggExtractorInput; import org.elasticsearch.xpack.sql.expression.gen.pipeline.AggExtractorInput;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.BinaryPipesTests; import org.elasticsearch.xpack.sql.expression.gen.pipeline.BinaryPipesTests;
@ -455,6 +457,10 @@ public class NodeSubclassTests<T extends B, B extends Node<B>> extends ESTestCas
if (argClass == char.class) { if (argClass == char.class) {
return randomFrom('\\', '|', '/', '`'); return randomFrom('\\', '|', '/', '`');
} }
} else if (toBuildClass == Histogram.class) {
if (argClass == Expression.class) {
return LiteralTests.randomLiteral();
}
} else if (toBuildClass == CurrentDateTime.class) { } else if (toBuildClass == CurrentDateTime.class) {
if (argClass == Expression.class) { if (argClass == Expression.class) {
return Literal.of(LocationTests.randomLocation(), randomInt(9)); return Literal.of(LocationTests.randomLocation(), randomInt(9));