diff --git a/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/CsvSpecIT.java b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/CsvSpecIT.java index fc68dea0aa4..2d52709b561 100644 --- a/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/CsvSpecIT.java +++ b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/CsvSpecIT.java @@ -20,10 +20,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.List; -import java.util.Locale; import java.util.Properties; -import static java.lang.String.format; import static org.elasticsearch.xpack.sql.jdbc.framework.JdbcAssert.assertResultSets; /** @@ -50,7 +48,9 @@ public class CsvSpecIT extends SpecBaseIntegrationTestCase { CsvSpecParser parser = new CsvSpecParser(); return CollectionUtils.combine( readScriptSpec("/command.csv-spec", parser), - readScriptSpec("/fulltext.csv-spec", parser)); + readScriptSpec("/fulltext.csv-spec", parser), + readScriptSpec("/agg.csv-spec", parser) + ); } public CsvSpecIT(String groupName, String testName, Integer lineNumber, Path source, CsvTestCase testCase) { @@ -62,7 +62,7 @@ public class CsvSpecIT extends SpecBaseIntegrationTestCase { try { assertMatchesCsv(testCase.query, testName, testCase.expectedResults); } catch (AssertionError ae) { - throw reworkException(new AssertionError(errorMessage(ae), ae.getCause())); + throw reworkException(ae); } } @@ -86,22 +86,20 @@ public class CsvSpecIT extends SpecBaseIntegrationTestCase { .executeQuery("SELECT * FROM " + csvTableName); // trigger data loading for type inference expected.beforeFirst(); - Statement statement = es.createStatement(); - //statement.setFetchSize(randomInt(10)); - // NOCOMMIT: hook up pagination - // NOCOMMIT sometimes accept the default fetch size. I believe it is 0 now which breaks things. - statement.setFetchSize(1000); - ResultSet actual = statement.executeQuery(query); + ResultSet actual = executeJdbcQuery(es, query); assertResultSets(expected, actual); } } - String errorMessage(Throwable th) { - return format(Locale.ROOT, "test%s@%s:%d failed\n\"%s\"\n%s", testName, source.getFileName().toString(), lineNumber, - testCase.query, th.getMessage()); + private ResultSet executeJdbcQuery(Connection con, String query) throws SQLException { + Statement statement = con.createStatement(); + //statement.setFetchSize(randomInt(10)); + // NOCOMMIT: hook up pagination + statement.setFetchSize(1000); + return statement.executeQuery(query); } - private static class CsvSpecParser implements Parser { + protected static class CsvSpecParser implements Parser { private final StringBuilder data = new StringBuilder(); private CsvTestCase testCase; @@ -137,7 +135,7 @@ public class CsvSpecIT extends SpecBaseIntegrationTestCase { } } - private static class CsvTestCase { + protected static class CsvTestCase { String query; String expectedResults; } diff --git a/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/DebugCsvSpec.java b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/DebugCsvSpec.java new file mode 100644 index 00000000000..cce97a95aa8 --- /dev/null +++ b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/DebugCsvSpec.java @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.sql.jdbc; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.xpack.sql.jdbc.framework.JdbcTestUtils; + +import java.nio.file.Path; +import java.util.List; + +public abstract class DebugCsvSpec extends CsvSpecIT { + + @ParametersFactory(shuffle = false, argumentFormatting = SqlSpecIT.PARAM_FORMATTING) // NOCOMMIT are we sure?! + public static List readScriptSpec() throws Exception { + JdbcTestUtils.sqlLogging(); + + CsvSpecParser parser = new CsvSpecParser(); + return readScriptSpec("/debug.csv-spec", parser); + } + + public DebugCsvSpec(String groupName, String testName, Integer lineNumber, Path source, CsvTestCase testCase) { + super(groupName, testName, lineNumber, source, testCase); + } + + // @Override + // public void assertResults(ResultSet expected, ResultSet actual) throws SQLException { + // Logger logger = Loggers.getLogger("org.elasticsearch.xpack.sql.test"); + // Loggers.setLevel(logger, "INFO"); + // + // JdbcTestUtils.resultSetToLogger(logger, actual); + // } +} \ No newline at end of file diff --git a/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/SqlSpecIT.java b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/SqlSpecIT.java index 3758bd79b95..2fdb26ef522 100644 --- a/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/SqlSpecIT.java +++ b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/SqlSpecIT.java @@ -39,7 +39,7 @@ public class SqlSpecIT extends SpecBaseIntegrationTestCase { // example for enabling logging //JdbcTestUtils.sqlLogging(); - SqlSpecParser parser = new SqlSpecParser(); + Parser parser = parser(); return CollectionUtils.combine( readScriptSpec("/select.sql-spec", parser), readScriptSpec("/filter.sql-spec", parser), @@ -63,6 +63,10 @@ public class SqlSpecIT extends SpecBaseIntegrationTestCase { } } + static SqlSpecParser parser() { + return new SqlSpecParser(); + } + public SqlSpecIT(String groupName, String testName, Integer lineNumber, Path source, String query) { super(groupName, testName, lineNumber, source); this.query = query; @@ -74,17 +78,17 @@ public class SqlSpecIT extends SpecBaseIntegrationTestCase { Connection es = esJdbc()) { ResultSet expected, actual; try { - expected = executeQuery(h2); - actual = executeQuery(es); + expected = executeJdbcQuery(h2); + actual = executeJdbcQuery(es); assertResultSets(expected, actual); } catch (AssertionError ae) { - throw reworkException(new AssertionError(errorMessage(ae), ae.getCause())); + throw reworkException(ae); } } } - private ResultSet executeQuery(Connection con) throws SQLException { + private ResultSet executeJdbcQuery(Connection con) throws SQLException { Statement statement = con.createStatement(); //statement.setFetchSize(randomInt(10)); // NOCOMMIT: hook up pagination diff --git a/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/framework/JdbcAssert.java b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/framework/JdbcAssert.java index 621a8cb6f39..04fca88ef79 100644 --- a/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/framework/JdbcAssert.java +++ b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/framework/JdbcAssert.java @@ -10,10 +10,13 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; +import java.util.ArrayList; import java.util.Calendar; +import java.util.List; import java.util.Locale; import java.util.TimeZone; +import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -29,7 +32,22 @@ public class JdbcAssert { ResultSetMetaData expectedMeta = expected.getMetaData(); ResultSetMetaData actualMeta = actual.getMetaData(); - assertEquals("Different number of columns returned", expectedMeta.getColumnCount(), actualMeta.getColumnCount()); + if (expectedMeta.getColumnCount() != actualMeta.getColumnCount()) { + List expectedCols = new ArrayList<>(); + for (int i = 1; i <= expectedMeta.getColumnCount(); i++) { + expectedCols.add(expectedMeta.getColumnName(i)); + + } + + List actualCols = new ArrayList<>(); + for (int i = 1; i <= actualMeta.getColumnCount(); i++) { + actualCols.add(actualMeta.getColumnName(i)); + } + + assertEquals(format(Locale.ROOT, "Different number of columns returned (expected %d but was %d);", + expectedMeta.getColumnCount(), actualMeta.getColumnCount()), + expectedCols.toString(), actualCols.toString()); + } for (int column = 1; column <= expectedMeta.getColumnCount(); column++) { String expectedName = expectedMeta.getColumnName(column); diff --git a/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/framework/JdbcTestUtils.java b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/framework/JdbcTestUtils.java index ead38955b0d..d3087c78641 100644 --- a/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/framework/JdbcTestUtils.java +++ b/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/framework/JdbcTestUtils.java @@ -5,9 +5,13 @@ */ package org.elasticsearch.xpack.sql.jdbc.framework; +import org.apache.logging.log4j.Logger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.xpack.sql.util.CollectionUtils; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; import java.util.Map; import java.util.Map.Entry; @@ -28,4 +32,84 @@ public abstract class JdbcTestUtils { Loggers.setLevel(Loggers.getLogger(entry.getKey()), entry.getValue()); } } -} + + public static void printResultSet(ResultSet set) throws Exception { + Logger logger = Loggers.getLogger("org.elasticsearch.xpack.sql.test"); + Loggers.setLevel(logger, "INFO"); + + ResultSetMetaData metaData = set.getMetaData(); + // header + StringBuilder sb = new StringBuilder(); + + int colSize = 15; + for (int column = 1; column <= metaData.getColumnCount(); column++) { + String colName = metaData.getColumnName(column); + + int size = colName.length(); + if (column > 1) { + sb.append("|"); + size++; + } + sb.append(colName); + for (int i = size; i < colSize; i++) { + sb.append(" "); + } + } + + logger.info(sb.toString()); + } + + private static final int MAX_WIDTH = 20; + + public static void resultSetToLogger(Logger log, ResultSet rs) throws SQLException { + ResultSetMetaData metaData = rs.getMetaData(); + StringBuilder sb = new StringBuilder(); + StringBuilder column = new StringBuilder(); + + int columns = metaData.getColumnCount(); + for (int i = 1; i <= columns; i++) { + if (i > 1) { + sb.append(" | "); + } + column.setLength(0); + column.append(metaData.getColumnName(i)); + column.append("("); + column.append(metaData.getColumnTypeName(i)); + column.append(")"); + + sb.append(trimOrPad(column)); + } + + int l = sb.length(); + sb.append("\n"); + for (int i = 0; i < l; i++) { + sb.append("="); + } + log.info(sb); + + while (rs.next()) { + sb.setLength(0); + for (int i = 1; i <= columns; i++) { + column.setLength(0); + if (i > 1) { + sb.append(" | "); + } + sb.append(trimOrPad(column.append(rs.getString(i)))); + } + log.info(sb); + } + } + + private static StringBuilder trimOrPad(StringBuilder buffer) { + if (buffer.length() > MAX_WIDTH) { + buffer.setLength(MAX_WIDTH - 1); + buffer.append("~"); + } + else { + for (int i = buffer.length(); i < MAX_WIDTH; i++) { + buffer.append(" "); + } + } + return buffer; + } +} \ No newline at end of file diff --git a/sql/jdbc/src/test/resources/agg.csv-spec b/sql/jdbc/src/test/resources/agg.csv-spec new file mode 100644 index 00000000000..11d471c7fd4 --- /dev/null +++ b/sql/jdbc/src/test/resources/agg.csv-spec @@ -0,0 +1,67 @@ +// +// Aggs not supported by H2 / traditional SQL stores +// + +singlePercentileWithoutComma +SELECT gender, PERCENTILE(emp_no, 97) p1 FROM test_emp GROUP BY gender; + +gender | p1 +M | 10095.6112 +F | 10099.1936 +; + +singlePercentileWithComma +SELECT gender, PERCENTILE(emp_no, 97.76) p1 FROM test_emp GROUP BY gender; + +gender | p1 +M | 10095.6112 +F | 10099.1936 +; + +multiplePercentilesOneWithCommaOneWithout +SELECT gender, PERCENTILE(emp_no, 92.45) p1, PERCENTILE(emp_no, 91) p2 FROM test_emp GROUP BY gender; + +gender | p1 | p2 +M | 10090.319 | 10087.68 +F | 10095.128 | 10093.52 +; + +multiplePercentilesWithoutComma +SELECT gender, PERCENTILE(emp_no, 91) p1, PERCENTILE(emp_no, 89) p2 FROM test_emp GROUP BY gender; + +gender | p1 | p2 +M | 10087.68 | 10085.18 +F | 10093.52 | 10092.08 +; + +multiplePercentilesWithComma +SELECT gender, PERCENTILE(emp_no, 85.7) p1, PERCENTILE(emp_no, 94.3) p2 FROM test_emp GROUP BY gender; + +gender | p1 | p2 +M | 10083.134 | 10091.932 +F | 10088.852 | 10097.792 +; + +percentileRank +SELECT gender, PERCENTILE_RANK(emp_no, 10025) rank FROM test_emp GROUP BY gender; + +gender | rank +M | 23.41269841269841 +F | 26.351351351351347 +; + +multiplePercentileRanks +SELECT gender, PERCENTILE_RANK(emp_no, 10030.0) rank1, PERCENTILE_RANK(emp_no, 10025) rank2 FROM test_emp GROUP BY gender; + +gender | rank1 | rank2 +M | 29.365079365079367 | 23.41269841269841 +F | 29.93762993762994 | 26.351351351351347 +; + +multiplePercentilesAndPercentileRank +SELECT gender, PERCENTILE(emp_no, 97.76) p1, PERCENTILE(emp_no, 93.3) p2, PERCENTILE_RANK(emp_no, 10025) rank FROM test_emp GROUP BY gender; + +gender | p1 | p2 | rank +M | 10095.6112 | 10090.846 | 23.41269841269841 +F | 10099.1936 | 10096.351999999999 | 26.351351351351347 +; \ No newline at end of file diff --git a/sql/jdbc/src/test/resources/debug.csv-spec b/sql/jdbc/src/test/resources/debug.csv-spec index fa25ee25ded..19aaf3b2827 100644 --- a/sql/jdbc/src/test/resources/debug.csv-spec +++ b/sql/jdbc/src/test/resources/debug.csv-spec @@ -3,51 +3,9 @@ // debug -SHOW FUNCTIONS; +SELECT gender, PERCENTILE(emp_no, 97.76) p1, PERCENTILE(emp_no, 93.3) p2, PERCENTILE_RANK(emp_no, 10025) rank FROM test_emp GROUP BY gender; - name | type -AVG |AGGREGATE -COUNT |AGGREGATE -MAX |AGGREGATE -MIN |AGGREGATE -SUM |AGGREGATE -DAY_OF_MONTH |SCALAR -DAY |SCALAR -DOM |SCALAR -DAY_OF_WEEK |SCALAR -DOW |SCALAR -DAY_OF_YEAR |SCALAR -DOY |SCALAR -HOUR_OF_DAY |SCALAR -HOUR |SCALAR -MINUTE_OF_DAY |SCALAR -MINUTE_OF_HOUR |SCALAR -MINUTE |SCALAR -SECOND_OF_MINUTE|SCALAR -SECOND |SCALAR -MONTH_OF_YEAR |SCALAR -MONTH |SCALAR -YEAR |SCALAR -ABS |SCALAR -ACOS |SCALAR -ASIN |SCALAR -ATAN |SCALAR -CBRT |SCALAR -CEIL |SCALAR -COS |SCALAR -COSH |SCALAR -DEGREES |SCALAR -E |SCALAR -EXP |SCALAR -EXPM1 |SCALAR -FLOOR |SCALAR -LOG |SCALAR -LOG10 |SCALAR -PI |SCALAR -RADIANS |SCALAR -ROUND |SCALAR -SIN |SCALAR -SINH |SCALAR -SQRT |SCALAR -TAN |SCALAR +gender | p1 | p2 | rank +M | 10095.6112 | 10090.846 | 23.41269841269841 +F | 10099.1936 | 10096.351999999999 | 26.351351351351347 ; \ No newline at end of file diff --git a/sql/server/src/main/antlr/SqlBase.tokens b/sql/server/src/main/antlr/SqlBase.tokens index a8a04ee483f..48934c80e9d 100644 --- a/sql/server/src/main/antlr/SqlBase.tokens +++ b/sql/server/src/main/antlr/SqlBase.tokens @@ -1,203 +1,203 @@ -T__0=1 -T__1=2 -T__2=3 -T__3=4 -SELECT=5 -FROM=6 -AS=7 -ALL=8 -WHEN=9 -THEN=10 -ANY=11 -DISTINCT=12 -WHERE=13 -GROUP=14 -BY=15 -GROUPING=16 -SETS=17 -ORDER=18 -HAVING=19 -LIMIT=20 -OR=21 -AND=22 -IN=23 -NOT=24 -NO=25 -EXISTS=26 -BETWEEN=27 -LIKE=28 -RLIKE=29 -IS=30 -NULL=31 -TRUE=32 -FALSE=33 -LAST=34 -ASC=35 -DESC=36 -FOR=37 -INTEGER=38 -JOIN=39 -CROSS=40 -OUTER=41 -INNER=42 -LEFT=43 -RIGHT=44 -FULL=45 -NATURAL=46 -USING=47 -ON=48 -WITH=49 -TABLE=50 -INTO=51 -DESCRIBE=52 -OPTION=53 -EXPLAIN=54 -ANALYZE=55 -FORMAT=56 -TYPE=57 -TEXT=58 -VERIFY=59 -GRAPHVIZ=60 -LOGICAL=61 -PHYSICAL=62 -SHOW=63 -TABLES=64 -COLUMNS=65 -COLUMN=66 -FUNCTIONS=67 -TO=68 -DEBUG=69 -PLAN=70 -PARSED=71 -ANALYZED=72 -OPTIMIZED=73 -MAPPED=74 -EXECUTABLE=75 -USE=76 -SET=77 -RESET=78 -SESSION=79 -SCHEMAS=80 -EXTRACT=81 -QUERY=82 -MATCH=83 -CAST=84 -EQ=85 -NEQ=86 -LT=87 -LTE=88 -GT=89 -GTE=90 -PLUS=91 -MINUS=92 -ASTERISK=93 -SLASH=94 -PERCENT=95 -CONCAT=96 -STRING=97 -INTEGER_VALUE=98 -DECIMAL_VALUE=99 -IDENTIFIER=100 -DIGIT_IDENTIFIER=101 -QUOTED_IDENTIFIER=102 -BACKQUOTED_IDENTIFIER=103 -SIMPLE_COMMENT=104 -BRACKETED_COMMENT=105 -WS=106 -UNRECOGNIZED=107 -DELIMITER=108 -'('=1 -')'=2 -','=3 -'.'=4 -'SELECT'=5 -'FROM'=6 -'AS'=7 -'ALL'=8 -'WHEN'=9 -'THEN'=10 -'ANY'=11 -'DISTINCT'=12 -'WHERE'=13 -'GROUP'=14 -'BY'=15 -'GROUPING'=16 -'SETS'=17 -'ORDER'=18 -'HAVING'=19 -'LIMIT'=20 -'OR'=21 -'AND'=22 -'IN'=23 -'NOT'=24 -'NO'=25 -'EXISTS'=26 -'BETWEEN'=27 -'LIKE'=28 -'RLIKE'=29 -'IS'=30 -'NULL'=31 -'TRUE'=32 -'FALSE'=33 -'LAST'=34 -'ASC'=35 -'DESC'=36 -'FOR'=37 -'INTEGER'=38 -'JOIN'=39 -'CROSS'=40 -'OUTER'=41 -'INNER'=42 -'LEFT'=43 -'RIGHT'=44 -'FULL'=45 -'NATURAL'=46 -'USING'=47 -'ON'=48 -'WITH'=49 -'TABLE'=50 -'INTO'=51 -'DESCRIBE'=52 -'OPTION'=53 -'EXPLAIN'=54 -'ANALYZE'=55 -'FORMAT'=56 -'TYPE'=57 -'TEXT'=58 -'VERIFY'=59 -'GRAPHVIZ'=60 -'LOGICAL'=61 -'PHYSICAL'=62 -'SHOW'=63 -'TABLES'=64 -'COLUMNS'=65 -'COLUMN'=66 -'FUNCTIONS'=67 -'TO'=68 -'DEBUG'=69 -'PLAN'=70 -'PARSED'=71 -'ANALYZED'=72 -'OPTIMIZED'=73 -'MAPPED'=74 -'EXECUTABLE'=75 -'USE'=76 -'SET'=77 -'RESET'=78 -'SESSION'=79 -'SCHEMAS'=80 -'EXTRACT'=81 -'QUERY'=82 -'MATCH'=83 -'CAST'=84 -'='=85 -'<'=87 -'<='=88 -'>'=89 -'>='=90 -'+'=91 -'-'=92 -'*'=93 -'/'=94 -'%'=95 -'||'=96 +T__0=1 +T__1=2 +T__2=3 +T__3=4 +SELECT=5 +FROM=6 +AS=7 +ALL=8 +WHEN=9 +THEN=10 +ANY=11 +DISTINCT=12 +WHERE=13 +GROUP=14 +BY=15 +GROUPING=16 +SETS=17 +ORDER=18 +HAVING=19 +LIMIT=20 +OR=21 +AND=22 +IN=23 +NOT=24 +NO=25 +EXISTS=26 +BETWEEN=27 +LIKE=28 +RLIKE=29 +IS=30 +NULL=31 +TRUE=32 +FALSE=33 +LAST=34 +ASC=35 +DESC=36 +FOR=37 +INTEGER=38 +JOIN=39 +CROSS=40 +OUTER=41 +INNER=42 +LEFT=43 +RIGHT=44 +FULL=45 +NATURAL=46 +USING=47 +ON=48 +WITH=49 +TABLE=50 +INTO=51 +DESCRIBE=52 +OPTION=53 +EXPLAIN=54 +ANALYZE=55 +FORMAT=56 +TYPE=57 +TEXT=58 +VERIFY=59 +GRAPHVIZ=60 +LOGICAL=61 +PHYSICAL=62 +SHOW=63 +TABLES=64 +COLUMNS=65 +COLUMN=66 +FUNCTIONS=67 +TO=68 +DEBUG=69 +PLAN=70 +PARSED=71 +ANALYZED=72 +OPTIMIZED=73 +MAPPED=74 +EXECUTABLE=75 +USE=76 +SET=77 +RESET=78 +SESSION=79 +SCHEMAS=80 +EXTRACT=81 +QUERY=82 +MATCH=83 +CAST=84 +EQ=85 +NEQ=86 +LT=87 +LTE=88 +GT=89 +GTE=90 +PLUS=91 +MINUS=92 +ASTERISK=93 +SLASH=94 +PERCENT=95 +CONCAT=96 +STRING=97 +INTEGER_VALUE=98 +DECIMAL_VALUE=99 +IDENTIFIER=100 +DIGIT_IDENTIFIER=101 +QUOTED_IDENTIFIER=102 +BACKQUOTED_IDENTIFIER=103 +SIMPLE_COMMENT=104 +BRACKETED_COMMENT=105 +WS=106 +UNRECOGNIZED=107 +DELIMITER=108 +'('=1 +')'=2 +','=3 +'.'=4 +'SELECT'=5 +'FROM'=6 +'AS'=7 +'ALL'=8 +'WHEN'=9 +'THEN'=10 +'ANY'=11 +'DISTINCT'=12 +'WHERE'=13 +'GROUP'=14 +'BY'=15 +'GROUPING'=16 +'SETS'=17 +'ORDER'=18 +'HAVING'=19 +'LIMIT'=20 +'OR'=21 +'AND'=22 +'IN'=23 +'NOT'=24 +'NO'=25 +'EXISTS'=26 +'BETWEEN'=27 +'LIKE'=28 +'RLIKE'=29 +'IS'=30 +'NULL'=31 +'TRUE'=32 +'FALSE'=33 +'LAST'=34 +'ASC'=35 +'DESC'=36 +'FOR'=37 +'INTEGER'=38 +'JOIN'=39 +'CROSS'=40 +'OUTER'=41 +'INNER'=42 +'LEFT'=43 +'RIGHT'=44 +'FULL'=45 +'NATURAL'=46 +'USING'=47 +'ON'=48 +'WITH'=49 +'TABLE'=50 +'INTO'=51 +'DESCRIBE'=52 +'OPTION'=53 +'EXPLAIN'=54 +'ANALYZE'=55 +'FORMAT'=56 +'TYPE'=57 +'TEXT'=58 +'VERIFY'=59 +'GRAPHVIZ'=60 +'LOGICAL'=61 +'PHYSICAL'=62 +'SHOW'=63 +'TABLES'=64 +'COLUMNS'=65 +'COLUMN'=66 +'FUNCTIONS'=67 +'TO'=68 +'DEBUG'=69 +'PLAN'=70 +'PARSED'=71 +'ANALYZED'=72 +'OPTIMIZED'=73 +'MAPPED'=74 +'EXECUTABLE'=75 +'USE'=76 +'SET'=77 +'RESET'=78 +'SESSION'=79 +'SCHEMAS'=80 +'EXTRACT'=81 +'QUERY'=82 +'MATCH'=83 +'CAST'=84 +'='=85 +'<'=87 +'<='=88 +'>'=89 +'>='=90 +'+'=91 +'-'=92 +'*'=93 +'/'=94 +'%'=95 +'||'=96 diff --git a/sql/server/src/main/antlr/SqlBaseLexer.tokens b/sql/server/src/main/antlr/SqlBaseLexer.tokens index 6397c938231..e43f77c4530 100644 --- a/sql/server/src/main/antlr/SqlBaseLexer.tokens +++ b/sql/server/src/main/antlr/SqlBaseLexer.tokens @@ -1,202 +1,202 @@ -T__0=1 -T__1=2 -T__2=3 -T__3=4 -SELECT=5 -FROM=6 -AS=7 -ALL=8 -WHEN=9 -THEN=10 -ANY=11 -DISTINCT=12 -WHERE=13 -GROUP=14 -BY=15 -GROUPING=16 -SETS=17 -ORDER=18 -HAVING=19 -LIMIT=20 -OR=21 -AND=22 -IN=23 -NOT=24 -NO=25 -EXISTS=26 -BETWEEN=27 -LIKE=28 -RLIKE=29 -IS=30 -NULL=31 -TRUE=32 -FALSE=33 -LAST=34 -ASC=35 -DESC=36 -FOR=37 -INTEGER=38 -JOIN=39 -CROSS=40 -OUTER=41 -INNER=42 -LEFT=43 -RIGHT=44 -FULL=45 -NATURAL=46 -USING=47 -ON=48 -WITH=49 -TABLE=50 -INTO=51 -DESCRIBE=52 -OPTION=53 -EXPLAIN=54 -ANALYZE=55 -FORMAT=56 -TYPE=57 -TEXT=58 -VERIFY=59 -GRAPHVIZ=60 -LOGICAL=61 -PHYSICAL=62 -SHOW=63 -TABLES=64 -COLUMNS=65 -COLUMN=66 -FUNCTIONS=67 -TO=68 -DEBUG=69 -PLAN=70 -PARSED=71 -ANALYZED=72 -OPTIMIZED=73 -MAPPED=74 -EXECUTABLE=75 -USE=76 -SET=77 -RESET=78 -SESSION=79 -SCHEMAS=80 -EXTRACT=81 -QUERY=82 -MATCH=83 -CAST=84 -EQ=85 -NEQ=86 -LT=87 -LTE=88 -GT=89 -GTE=90 -PLUS=91 -MINUS=92 -ASTERISK=93 -SLASH=94 -PERCENT=95 -CONCAT=96 -STRING=97 -INTEGER_VALUE=98 -DECIMAL_VALUE=99 -IDENTIFIER=100 -DIGIT_IDENTIFIER=101 -QUOTED_IDENTIFIER=102 -BACKQUOTED_IDENTIFIER=103 -SIMPLE_COMMENT=104 -BRACKETED_COMMENT=105 -WS=106 -UNRECOGNIZED=107 -'('=1 -')'=2 -','=3 -'.'=4 -'SELECT'=5 -'FROM'=6 -'AS'=7 -'ALL'=8 -'WHEN'=9 -'THEN'=10 -'ANY'=11 -'DISTINCT'=12 -'WHERE'=13 -'GROUP'=14 -'BY'=15 -'GROUPING'=16 -'SETS'=17 -'ORDER'=18 -'HAVING'=19 -'LIMIT'=20 -'OR'=21 -'AND'=22 -'IN'=23 -'NOT'=24 -'NO'=25 -'EXISTS'=26 -'BETWEEN'=27 -'LIKE'=28 -'RLIKE'=29 -'IS'=30 -'NULL'=31 -'TRUE'=32 -'FALSE'=33 -'LAST'=34 -'ASC'=35 -'DESC'=36 -'FOR'=37 -'INTEGER'=38 -'JOIN'=39 -'CROSS'=40 -'OUTER'=41 -'INNER'=42 -'LEFT'=43 -'RIGHT'=44 -'FULL'=45 -'NATURAL'=46 -'USING'=47 -'ON'=48 -'WITH'=49 -'TABLE'=50 -'INTO'=51 -'DESCRIBE'=52 -'OPTION'=53 -'EXPLAIN'=54 -'ANALYZE'=55 -'FORMAT'=56 -'TYPE'=57 -'TEXT'=58 -'VERIFY'=59 -'GRAPHVIZ'=60 -'LOGICAL'=61 -'PHYSICAL'=62 -'SHOW'=63 -'TABLES'=64 -'COLUMNS'=65 -'COLUMN'=66 -'FUNCTIONS'=67 -'TO'=68 -'DEBUG'=69 -'PLAN'=70 -'PARSED'=71 -'ANALYZED'=72 -'OPTIMIZED'=73 -'MAPPED'=74 -'EXECUTABLE'=75 -'USE'=76 -'SET'=77 -'RESET'=78 -'SESSION'=79 -'SCHEMAS'=80 -'EXTRACT'=81 -'QUERY'=82 -'MATCH'=83 -'CAST'=84 -'='=85 -'<'=87 -'<='=88 -'>'=89 -'>='=90 -'+'=91 -'-'=92 -'*'=93 -'/'=94 -'%'=95 -'||'=96 +T__0=1 +T__1=2 +T__2=3 +T__3=4 +SELECT=5 +FROM=6 +AS=7 +ALL=8 +WHEN=9 +THEN=10 +ANY=11 +DISTINCT=12 +WHERE=13 +GROUP=14 +BY=15 +GROUPING=16 +SETS=17 +ORDER=18 +HAVING=19 +LIMIT=20 +OR=21 +AND=22 +IN=23 +NOT=24 +NO=25 +EXISTS=26 +BETWEEN=27 +LIKE=28 +RLIKE=29 +IS=30 +NULL=31 +TRUE=32 +FALSE=33 +LAST=34 +ASC=35 +DESC=36 +FOR=37 +INTEGER=38 +JOIN=39 +CROSS=40 +OUTER=41 +INNER=42 +LEFT=43 +RIGHT=44 +FULL=45 +NATURAL=46 +USING=47 +ON=48 +WITH=49 +TABLE=50 +INTO=51 +DESCRIBE=52 +OPTION=53 +EXPLAIN=54 +ANALYZE=55 +FORMAT=56 +TYPE=57 +TEXT=58 +VERIFY=59 +GRAPHVIZ=60 +LOGICAL=61 +PHYSICAL=62 +SHOW=63 +TABLES=64 +COLUMNS=65 +COLUMN=66 +FUNCTIONS=67 +TO=68 +DEBUG=69 +PLAN=70 +PARSED=71 +ANALYZED=72 +OPTIMIZED=73 +MAPPED=74 +EXECUTABLE=75 +USE=76 +SET=77 +RESET=78 +SESSION=79 +SCHEMAS=80 +EXTRACT=81 +QUERY=82 +MATCH=83 +CAST=84 +EQ=85 +NEQ=86 +LT=87 +LTE=88 +GT=89 +GTE=90 +PLUS=91 +MINUS=92 +ASTERISK=93 +SLASH=94 +PERCENT=95 +CONCAT=96 +STRING=97 +INTEGER_VALUE=98 +DECIMAL_VALUE=99 +IDENTIFIER=100 +DIGIT_IDENTIFIER=101 +QUOTED_IDENTIFIER=102 +BACKQUOTED_IDENTIFIER=103 +SIMPLE_COMMENT=104 +BRACKETED_COMMENT=105 +WS=106 +UNRECOGNIZED=107 +'('=1 +')'=2 +','=3 +'.'=4 +'SELECT'=5 +'FROM'=6 +'AS'=7 +'ALL'=8 +'WHEN'=9 +'THEN'=10 +'ANY'=11 +'DISTINCT'=12 +'WHERE'=13 +'GROUP'=14 +'BY'=15 +'GROUPING'=16 +'SETS'=17 +'ORDER'=18 +'HAVING'=19 +'LIMIT'=20 +'OR'=21 +'AND'=22 +'IN'=23 +'NOT'=24 +'NO'=25 +'EXISTS'=26 +'BETWEEN'=27 +'LIKE'=28 +'RLIKE'=29 +'IS'=30 +'NULL'=31 +'TRUE'=32 +'FALSE'=33 +'LAST'=34 +'ASC'=35 +'DESC'=36 +'FOR'=37 +'INTEGER'=38 +'JOIN'=39 +'CROSS'=40 +'OUTER'=41 +'INNER'=42 +'LEFT'=43 +'RIGHT'=44 +'FULL'=45 +'NATURAL'=46 +'USING'=47 +'ON'=48 +'WITH'=49 +'TABLE'=50 +'INTO'=51 +'DESCRIBE'=52 +'OPTION'=53 +'EXPLAIN'=54 +'ANALYZE'=55 +'FORMAT'=56 +'TYPE'=57 +'TEXT'=58 +'VERIFY'=59 +'GRAPHVIZ'=60 +'LOGICAL'=61 +'PHYSICAL'=62 +'SHOW'=63 +'TABLES'=64 +'COLUMNS'=65 +'COLUMN'=66 +'FUNCTIONS'=67 +'TO'=68 +'DEBUG'=69 +'PLAN'=70 +'PARSED'=71 +'ANALYZED'=72 +'OPTIMIZED'=73 +'MAPPED'=74 +'EXECUTABLE'=75 +'USE'=76 +'SET'=77 +'RESET'=78 +'SESSION'=79 +'SCHEMAS'=80 +'EXTRACT'=81 +'QUERY'=82 +'MATCH'=83 +'CAST'=84 +'='=85 +'<'=87 +'<='=88 +'>'=89 +'>='=90 +'+'=91 +'-'=92 +'*'=93 +'/'=94 +'%'=95 +'||'=96 diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Analyzer.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Analyzer.java index 331ea514098..9f70d114e56 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Analyzer.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Analyzer.java @@ -823,7 +823,7 @@ public class Analyzer extends RuleExecutor { if (f instanceof Count) { Count c = (Count) f; if (!c.distinct()) { - if (c.argument() instanceof Literal && c.argument().dataType().isInteger()) { + if (c.field() instanceof Literal && c.field().dataType().isInteger()) { return true; } } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Expression.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Expression.java index cd2f52144f4..46504697fee 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Expression.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Expression.java @@ -5,9 +5,6 @@ */ package org.elasticsearch.xpack.sql.expression; -import java.util.List; -import java.util.Locale; - import org.elasticsearch.xpack.sql.capabilities.Resolvable; import org.elasticsearch.xpack.sql.capabilities.Resolvables; import org.elasticsearch.xpack.sql.tree.Location; @@ -16,6 +13,9 @@ import org.elasticsearch.xpack.sql.tree.NodeUtils; import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.util.StringUtils; +import java.util.List; +import java.util.Locale; + import static java.lang.String.format; public abstract class Expression extends Node implements Resolvable { @@ -78,7 +78,7 @@ public abstract class Expression extends Node implements Resolvable return lazyChildrenResolved; } - public TypeResolution typeResolved() { + public final TypeResolution typeResolved() { if (lazyTypeResolution == null) { lazyTypeResolution = resolveType(); } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Expressions.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Expressions.java index 126ecf03013..6aa02ca802c 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Expressions.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Expressions.java @@ -40,6 +40,15 @@ public abstract class Expressions { return false; } + public static boolean nullable(List exps) { + for (Expression exp : exps) { + if (!exp.nullable()) { + return false; + } + } + return true; + } + public static AttributeSet references(List exps) { if (exps.isEmpty()) { return AttributeSet.EMPTY; diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Foldables.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Foldables.java new file mode 100644 index 00000000000..c286bd9f897 --- /dev/null +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Foldables.java @@ -0,0 +1,64 @@ +/* + * 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.xpack.sql.SqlIllegalArgumentException; +import org.elasticsearch.xpack.sql.type.DataType; +import org.elasticsearch.xpack.sql.type.DataTypeConvertion; +import org.elasticsearch.xpack.sql.type.DataTypes; + +import java.util.ArrayList; +import java.util.List; + +public abstract class Foldables { + + public static T valueOf(Expression e, DataType to) { + if (e.foldable()) { + return DataTypeConvertion.convert(e.fold(), e.dataType(), to); + } + throw new SqlIllegalArgumentException("Cannot determine value for %s", e); + } + + public static Object valueOf(Expression e) { + if (e.foldable()) { + return e.fold(); + } + throw new SqlIllegalArgumentException("Cannot determine value for %s", e); + } + + public static String stringValueOf(Expression e) { + return valueOf(e, DataTypes.KEYWORD); + } + + public static Integer intValueOf(Expression e) { + return valueOf(e, DataTypes.INTEGER); + } + + public static Long longValueOf(Expression e) { + return valueOf(e, DataTypes.LONG); + } + + public static double doubleValueOf(Expression e) { + return valueOf(e, DataTypes.DOUBLE); + } + + public static List valuesOf(List list, DataType to) { + List l = new ArrayList<>(); + for (Expression e : list) { + if (e.foldable()) { + l.add(DataTypeConvertion.convert(e.fold(), e.dataType(), to)); + } + else { + throw new SqlIllegalArgumentException("Cannot determine value for %s", e); + } + } + return l; + } + + public static List doubleValuesOf(List list) { + return valuesOf(list, DataTypes.DOUBLE); + } +} \ No newline at end of file diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/LeafExpression.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/LeafExpression.java index a70a358ed1f..3f8978725de 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/LeafExpression.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/LeafExpression.java @@ -18,9 +18,4 @@ public abstract class LeafExpression extends Expression { public AttributeSet references() { return AttributeSet.EMPTY; } - - @Override - public int hashCode() { - throw new UnsupportedOperationException(); - } } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Literal.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Literal.java index 3e941bac152..4d9e1d87ce3 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Literal.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/Literal.java @@ -5,14 +5,13 @@ */ package org.elasticsearch.xpack.sql.expression; -import java.util.Collections; -import java.util.Objects; - import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataTypes; -public class Literal extends Expression { +import java.util.Objects; + +public class Literal extends LeafExpression { private final Object value; private final DataType dataType; @@ -21,7 +20,7 @@ public class Literal extends Expression { public static final Literal FALSE = Literal.of(Location.EMPTY, Boolean.FALSE); public Literal(Location location, Object value, DataType dataType) { - super(location, Collections.emptyList()); + super(location); this.value = value; this.dataType = dataType; } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/AbstractFunctionRegistry.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/AbstractFunctionRegistry.java index 69738f91d1b..0ff42bdcb35 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/AbstractFunctionRegistry.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/AbstractFunctionRegistry.java @@ -7,22 +7,18 @@ package org.elasticsearch.xpack.sql.expression.function; import org.elasticsearch.common.Strings; import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; -import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.function.aware.DistinctAware; import org.elasticsearch.xpack.sql.expression.function.aware.TimeZoneAware; import org.elasticsearch.xpack.sql.parser.ParsingException; import org.elasticsearch.xpack.sql.session.SqlSettings; -import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Node; import org.elasticsearch.xpack.sql.tree.NodeUtils; import org.elasticsearch.xpack.sql.tree.NodeUtils.NodeInfo; import org.elasticsearch.xpack.sql.util.Assert; import org.elasticsearch.xpack.sql.util.StringUtils; -import org.joda.time.DateTimeZone; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -98,46 +94,61 @@ abstract class AbstractFunctionRegistry implements FunctionRegistry { return StringUtils.camelCaseToUnderscore(name); } + // + // Instantiates a function through reflection. + // Picks up the constructor by expecting to be of type (Location,Expression) or (Location,List) depending on the size of given children, parameters. + // If the function has certain 'aware'-ness (based on the interface implemented), the appropriate types are added to the signature + @SuppressWarnings("rawtypes") private static Function createInstance(Class clazz, UnresolvedFunction ur, SqlSettings settings) { NodeInfo info = NodeUtils.info((Class) clazz); - Class exp = ur.children().size() == 1 ? Expression.class : List.class; - Object expVal = exp == Expression.class ? ur.children().get(0) : ur.children(); + Class[] pTypes = info.ctr.getParameterTypes(); - boolean noExpression = false; boolean distinctAware = DistinctAware.class.isAssignableFrom(clazz); boolean timezoneAware = TimeZoneAware.class.isAssignableFrom(clazz); + + // constructor types - location - distinct? - timezone? + int expectedParamCount = pTypes.length - (1 + (distinctAware ? 1 : 0) + (timezoneAware ? 1 : 0)); // check constructor signature - + if (ur.children().size() != expectedParamCount) { + List expected = new ArrayList<>(); + + for (int i = 1; i < expectedParamCount; i++) { + expected.add(pTypes[i].getSimpleName()); + } + + throw new ParsingException(ur.location(), "Invalid number of arguments given to function [%s], expected %d argument(s):%s but received %d:%s", + ur.name(), expected.size(), expected.toString(), ur.children().size(), ur.children()); + } // validate distinct ctor if (!distinctAware && ur.distinct()) { throw new ParsingException(ur.location(), "Function [%s] does not support DISTINCT yet it was specified", ur.name()); } - - List ctorSignature = new ArrayList<>(); - ctorSignature.add(Location.class); - - // might be a constant function - if (expVal instanceof List && ((List) expVal).isEmpty()) { - noExpression = Arrays.equals(new Class[] { Location.class }, info.ctr.getParameterTypes()); - } - else { - ctorSignature.add(exp); - } - // aware stuff - if (distinctAware) { - ctorSignature.add(boolean.class); - } - if (timezoneAware) { - ctorSignature.add(DateTimeZone.class); - } - - // validate - Assert.isTrue(Arrays.equals(ctorSignature.toArray(new Class[ctorSignature.size()]), info.ctr.getParameterTypes()), - "No constructor with signature %s found for [%s]", ctorSignature, clazz.getTypeName()); + // List ctorSignature = new ArrayList<>(); + // ctorSignature.add(Location.class); + // + // // might be a constant function + // if (expVal instanceof List && ((List) expVal).isEmpty()) { + // noExpression = Arrays.equals(new Class[] { Location.class }, info.ctr.getParameterTypes()); + // } + // else { + // ctorSignature.add(exp); + // } + // + // // aware stuff + // if (distinctAware) { + // ctorSignature.add(boolean.class); + // } + // if (timezoneAware) { + // ctorSignature.add(DateTimeZone.class); + // } + // + // // validate + // Assert.isTrue(Arrays.equals(ctorSignature.toArray(new Class[ctorSignature.size()]), info.ctr.getParameterTypes()), + // "No constructor with signature %s found for [%s], found %s instead", ctorSignature, clazz.getTypeName(), info.ctr); // now add the actual values try { @@ -147,15 +158,15 @@ abstract class AbstractFunctionRegistry implements FunctionRegistry { args.add(ur.location()); // has multiple arguments - if (!noExpression) { - args.add(expVal); - if (distinctAware) { - args.add(ur.distinct()); - } - if (timezoneAware) { - args.add(settings.timeZone()); - } - } + args.addAll(ur.children()); + + if (distinctAware) { + args.add(ur.distinct()); + } + if (timezoneAware) { + args.add(settings.timeZone()); + } + return (Function) info.ctr.newInstance(args.toArray()); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new SqlIllegalArgumentException(ex, "Cannot create instance of function %s", ur.name()); diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/DefaultFunctionRegistry.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/DefaultFunctionRegistry.java index 2b237e20eb5..0eadd622013 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/DefaultFunctionRegistry.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/DefaultFunctionRegistry.java @@ -13,6 +13,8 @@ import org.elasticsearch.xpack.sql.expression.function.aggregate.Kurtosis; import org.elasticsearch.xpack.sql.expression.function.aggregate.Max; import org.elasticsearch.xpack.sql.expression.function.aggregate.Mean; import org.elasticsearch.xpack.sql.expression.function.aggregate.Min; +import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentile; +import org.elasticsearch.xpack.sql.expression.function.aggregate.PercentileRank; import org.elasticsearch.xpack.sql.expression.function.aggregate.Skewness; import org.elasticsearch.xpack.sql.expression.function.aggregate.StddevPop; import org.elasticsearch.xpack.sql.expression.function.aggregate.Sum; @@ -92,8 +94,11 @@ public class DefaultFunctionRegistry extends AbstractFunctionRegistry { VarPop.class, SumOfSquares.class, Skewness.class, - Kurtosis.class - // TODO: add multi arg functions like Covariance, Correlate, Percentiles and percentiles rank + Kurtosis.class, + Percentile.class, + PercentileRank.class + // TODO: add multi arg functions like Covariance, Correlate + ); } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/AggregateFunction.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/AggregateFunction.java index e9d37f0cd94..46554e6925d 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/AggregateFunction.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/AggregateFunction.java @@ -8,20 +8,34 @@ package org.elasticsearch.xpack.sql.expression.function.aggregate; import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.function.Function; import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.util.CollectionUtils; +import java.util.List; + +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; public abstract class AggregateFunction extends Function { - private final Expression argument; + private final Expression field; + private final List arguments; - AggregateFunction(Location location, Expression child) { - super(location, singletonList(child)); - this.argument = child; + AggregateFunction(Location location, Expression field) { + this(location, field, emptyList()); } - public Expression argument() { - return argument; + AggregateFunction(Location location, Expression field, List arguments) { + super(location, CollectionUtils.combine(singletonList(field), arguments)); + this.field = field; + this.arguments = arguments; + } + + public Expression field() { + return field; + } + + public List arguments() { + return arguments; } public String functionId() { diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/CompoundAggregate.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/CompoundNumericAggregate.java similarity index 64% rename from sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/CompoundAggregate.java rename to sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/CompoundNumericAggregate.java index ee511a15c54..3ff2ae0c44e 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/CompoundAggregate.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/CompoundNumericAggregate.java @@ -8,11 +8,17 @@ package org.elasticsearch.xpack.sql.expression.function.aggregate; import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.tree.Location; +import java.util.List; + // marker type for compound aggregates, that is aggregate that provide multiple values (like Stats or Matrix) // and thus cannot be used directly in SQL and are mainly for internal use -public abstract class CompoundAggregate extends NumericAggregate { +public abstract class CompoundNumericAggregate extends NumericAggregate { - public CompoundAggregate(Location location, Expression argument) { - super(location, argument); + CompoundNumericAggregate(Location location, Expression field, List arguments) { + super(location, field, arguments); + } + + CompoundNumericAggregate(Location location, Expression field) { + super(location, field); } } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Count.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Count.java index e86dda0c1ea..cb04a92c6d8 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Count.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Count.java @@ -12,12 +12,12 @@ import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataTypes; -public class Count extends AggregateFunction implements DistinctAware { +public class Count extends NumericAggregate implements DistinctAware { private final boolean distinct; - public Count(Location location, Expression argument, boolean distinct) { - super(location, argument); + public Count(Location location, Expression field, boolean distinct) { + super(location, field); this.distinct = distinct; } @@ -30,13 +30,12 @@ public class Count extends AggregateFunction implements DistinctAware { return DataTypes.LONG; } - @Override public String functionId() { String functionId = id().toString(); // if count works against a given expression, use its id (to identify the group) - if (argument() instanceof NamedExpression) { - functionId = ((NamedExpression) argument()).id().toString(); + if (field() instanceof NamedExpression) { + functionId = ((NamedExpression) field()).id().toString(); } return functionId; } @@ -45,4 +44,4 @@ public class Count extends AggregateFunction implements DistinctAware { public AggregateFunctionAttribute toAttribute() { return new AggregateFunctionAttribute(location(), name(), dataType(), id(), functionId(), "_count"); } -} +} \ No newline at end of file diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/EnclosedAgg.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/EnclosedAgg.java index 4cfe6862ca1..146cc68ba14 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/EnclosedAgg.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/EnclosedAgg.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.sql.expression.function.aggregate; +// Agg 'enclosed' by another agg. Used for agg that return multiple embedded aggs (like MatrixStats) public interface EnclosedAgg { String innerName(); diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/ExtendedStats.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/ExtendedStats.java index ab737e4cd51..aded773608a 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/ExtendedStats.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/ExtendedStats.java @@ -8,9 +8,9 @@ package org.elasticsearch.xpack.sql.expression.function.aggregate; import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.tree.Location; -public class ExtendedStats extends CompoundAggregate { +public class ExtendedStats extends CompoundNumericAggregate { - public ExtendedStats(Location location, Expression argument) { - super(location, argument); + public ExtendedStats(Location location, Expression field) { + super(location, field); } } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/InnerAggregate.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/InnerAggregate.java index c4f5cbd9661..04a5d83de65 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/InnerAggregate.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/InnerAggregate.java @@ -13,17 +13,17 @@ import org.elasticsearch.xpack.sql.type.DataType; public class InnerAggregate extends AggregateFunction { private final AggregateFunction inner; - private final CompoundAggregate outer; + private final CompoundNumericAggregate outer; private final String innerId; - // used when the result needs to be extracted from a map (like in MatrixAggs) + // used when the result needs to be extracted from a map (like in MatrixAggs or Percentiles) private final Expression innerKey; - public InnerAggregate(AggregateFunction inner, CompoundAggregate outer) { + public InnerAggregate(AggregateFunction inner, CompoundNumericAggregate outer) { this(inner, outer, null); } - public InnerAggregate(AggregateFunction inner, CompoundAggregate outer, Expression innerKey) { - super(inner.location(), outer.argument()); + public InnerAggregate(AggregateFunction inner, CompoundNumericAggregate outer, Expression innerKey) { + super(inner.location(), outer.field(), outer.arguments()); this.inner = inner; this.outer = outer; this.innerId = ((EnclosedAgg) inner).innerName(); @@ -34,7 +34,7 @@ public class InnerAggregate extends AggregateFunction { return inner; } - public CompoundAggregate outer() { + public CompoundNumericAggregate outer() { return outer; } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/MatrixStats.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/MatrixStats.java index e5c9e63f5db..9f5c5750c99 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/MatrixStats.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/MatrixStats.java @@ -8,9 +8,9 @@ package org.elasticsearch.xpack.sql.expression.function.aggregate; import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.tree.Location; -public class MatrixStats extends CompoundAggregate { +public class MatrixStats extends CompoundNumericAggregate { - public MatrixStats(Location location, Expression argument) { - super(location, argument); + public MatrixStats(Location location, Expression field) { + super(location, field); } } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Max.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Max.java index fd3f46564ae..7d819fcea41 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Max.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Max.java @@ -17,7 +17,7 @@ public class Max extends NumericAggregate implements EnclosedAgg { @Override public DataType dataType() { - return argument().dataType(); + return field().dataType(); } @Override diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Min.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Min.java index 772fd4b9cb3..51600f7af4b 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Min.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Min.java @@ -17,7 +17,7 @@ public class Min extends NumericAggregate implements EnclosedAgg { @Override public DataType dataType() { - return argument().dataType(); + return field().dataType(); } @Override diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/NumericAggregate.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/NumericAggregate.java index d79835c1379..db976fa4422 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/NumericAggregate.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/NumericAggregate.java @@ -11,17 +11,24 @@ import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataTypes; -abstract class NumericAggregate extends AggregateFunction { +import java.util.List; - NumericAggregate(Location location, Expression argument) { - super(location, argument); +class NumericAggregate extends AggregateFunction { + + NumericAggregate(Location location, Expression field, List arguments) { + super(location, field, arguments); + } + + NumericAggregate(Location location, Expression field) { + super(location, field); } @Override protected TypeResolution resolveType() { - return argument().dataType().isNumeric() ? - TypeResolution.TYPE_RESOLVED : - new TypeResolution("Function '%s' cannot be applied on a non-numeric expression ('%s' of type '%s')", functionName(), Expressions.name(argument()), argument().dataType().esName()); + return field().dataType().isNumeric() ? TypeResolution.TYPE_RESOLVED : new TypeResolution( + "Function '%s' cannot be applied on a non-numeric expression ('%s' of type '%s')", functionName(), + Expressions.name(field()), field().dataType().esName()); + } @Override diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/NumericAggregateFunction.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/NumericAggregateFunction.java deleted file mode 100644 index bfcf5c597a1..00000000000 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/NumericAggregateFunction.java +++ /dev/null @@ -1,30 +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.expression.function.aggregate; - -import org.elasticsearch.xpack.sql.expression.Expression; -import org.elasticsearch.xpack.sql.expression.Expressions; -import org.elasticsearch.xpack.sql.tree.Location; -import org.elasticsearch.xpack.sql.type.DataType; - -abstract class NumericAggregateFunction extends AggregateFunction { - - NumericAggregateFunction(Location location, Expression argument) { - super(location, argument); - } - - @Override - protected TypeResolution resolveType() { - return argument().dataType().isNumeric() ? - TypeResolution.TYPE_RESOLVED : - new TypeResolution("Function '%s' cannot be applied on a non-numeric expression ('%s' of type '%s')", functionName(), Expressions.name(argument()), argument().dataType().esName()); - } - - @Override - public DataType dataType() { - return argument().dataType(); - } -} diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Percentile.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Percentile.java new file mode 100644 index 00000000000..89493ac681d --- /dev/null +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Percentile.java @@ -0,0 +1,69 @@ +/* + * 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.aggregate; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.Expressions; +import org.elasticsearch.xpack.sql.expression.Foldables; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.type.DataType; +import org.elasticsearch.xpack.sql.type.DataTypes; + +import java.util.Objects; + +import static java.util.Collections.singletonList; + +public class Percentile extends AggregateFunction implements EnclosedAgg { + + private final Expression percent; + + public Percentile(Location location, Expression field, Expression percent) { + super(location, field, singletonList(percent)); + this.percent = percent; + } + + @Override + protected TypeResolution resolveType() { + TypeResolution resolution = field().dataType().isNumeric() ? TypeResolution.TYPE_RESOLVED : + new TypeResolution("Function '%s' cannot be applied on a non-numeric expression ('%s' of type '%s')", + functionName(), Expressions.name(field()), field().dataType().esName()); + + if (TypeResolution.TYPE_RESOLVED.equals(resolution)) { + resolution = percent().dataType().isNumeric() ? TypeResolution.TYPE_RESOLVED : + new TypeResolution("Percentile#percent argument cannot be non-numeric (type is'%s')", percent().dataType().esName()); + } + return resolution; + } + + public Expression percent() { + return percent; + } + + @Override + public DataType dataType() { + return DataTypes.DOUBLE; + } + + @Override + public String innerName() { + return "[" + Double.toString(Foldables.doubleValueOf(percent)) + "]"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + Percentile other = (Percentile) obj; + return Objects.equals(field(), other.field()) + && Objects.equals(percent, other.percent); + } +} \ No newline at end of file diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/PercentileRank.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/PercentileRank.java new file mode 100644 index 00000000000..b14557eb3a0 --- /dev/null +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/PercentileRank.java @@ -0,0 +1,69 @@ +/* + * 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.aggregate; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.Expressions; +import org.elasticsearch.xpack.sql.expression.Foldables; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.type.DataType; +import org.elasticsearch.xpack.sql.type.DataTypes; + +import java.util.Objects; + +import static java.util.Collections.singletonList; + +public class PercentileRank extends AggregateFunction implements EnclosedAgg { + + private final Expression value; + + public PercentileRank(Location location, Expression field, Expression value) { + super(location, field, singletonList(value)); + this.value = value; + } + + @Override + protected TypeResolution resolveType() { + TypeResolution resolution = field().dataType().isNumeric() ? TypeResolution.TYPE_RESOLVED : + new TypeResolution("Function '%s' cannot be applied on a non-numeric expression ('%s' of type '%s')", + functionName(), Expressions.name(field()), field().dataType().esName()); + + if (TypeResolution.TYPE_RESOLVED.equals(resolution)) { + resolution = value.dataType().isNumeric() ? TypeResolution.TYPE_RESOLVED : + new TypeResolution("PercentileRank#value argument cannot be non-numeric (type is'%s')", value.dataType().esName()); + } + return resolution; + } + + public Expression value() { + return value; + } + + @Override + public DataType dataType() { + return DataTypes.DOUBLE; + } + + @Override + public String innerName() { + return "[" + Double.toString(Foldables.doubleValueOf(value)) + "]"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + PercentileRank other = (PercentileRank) obj; + return Objects.equals(field(), other.field()) + && Objects.equals(value, other.value); + } +} \ No newline at end of file diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/PercentileRanks.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/PercentileRanks.java new file mode 100644 index 00000000000..d87959985c6 --- /dev/null +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/PercentileRanks.java @@ -0,0 +1,41 @@ +/* + * 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.aggregate; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.tree.Location; + +import java.util.List; +import java.util.Objects; + +public class PercentileRanks extends CompoundNumericAggregate { + + private final List values; + + public PercentileRanks(Location location, Expression field, List values) { + super(location, field, values); + this.values = values; + } + + public List values() { + return values; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + PercentileRanks other = (PercentileRanks) obj; + return Objects.equals(field(), other.field()) + && Objects.equals(values, other.values); + } +} \ No newline at end of file diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Percentiles.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Percentiles.java new file mode 100644 index 00000000000..e49531e944c --- /dev/null +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Percentiles.java @@ -0,0 +1,41 @@ +/* + * 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.aggregate; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.tree.Location; + +import java.util.List; +import java.util.Objects; + +public class Percentiles extends CompoundNumericAggregate { + + private final List percents; + + public Percentiles(Location location, Expression field, List percents) { + super(location, field, percents); + this.percents = percents; + } + + public List percents() { + return percents; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + Percentiles other = (Percentiles) obj; + return Objects.equals(field(), other.field()) + && Objects.equals(percents, other.percents); + } +} \ No newline at end of file diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Stats.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Stats.java index cf4fa3ef178..87f1f70fc5d 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Stats.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Stats.java @@ -8,10 +8,10 @@ package org.elasticsearch.xpack.sql.expression.function.aggregate; import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.tree.Location; -public class Stats extends CompoundAggregate { +public class Stats extends CompoundNumericAggregate { - public Stats(Location location, Expression argument) { - super(location, argument); + public Stats(Location location, Expression field) { + super(location, field); } public static boolean isTypeCompatible(Expression e) { diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Sum.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Sum.java index 99bdb6418fd..7bd91d6b23a 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Sum.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/expression/function/aggregate/Sum.java @@ -17,7 +17,7 @@ public class Sum extends NumericAggregate implements EnclosedAgg { @Override public DataType dataType() { - return argument().dataType(); + return field().dataType(); } @Override diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java index dec086de909..dd03d0218d2 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java @@ -27,6 +27,10 @@ import org.elasticsearch.xpack.sql.expression.function.aggregate.ExtendedStatsEn import org.elasticsearch.xpack.sql.expression.function.aggregate.InnerAggregate; import org.elasticsearch.xpack.sql.expression.function.aggregate.MatrixStats; import org.elasticsearch.xpack.sql.expression.function.aggregate.MatrixStatsEnclosed; +import org.elasticsearch.xpack.sql.expression.function.aggregate.Percentile; +import org.elasticsearch.xpack.sql.expression.function.aggregate.PercentileRank; +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.Stats; import org.elasticsearch.xpack.sql.expression.function.scalar.Cast; import org.elasticsearch.xpack.sql.expression.predicate.And; @@ -50,7 +54,6 @@ import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias; import org.elasticsearch.xpack.sql.rule.Rule; import org.elasticsearch.xpack.sql.rule.RuleExecutor; import org.elasticsearch.xpack.sql.session.EmptyExecutable; -import org.elasticsearch.xpack.sql.util.CollectionUtils; import java.util.ArrayList; import java.util.Arrays; @@ -71,6 +74,8 @@ import static org.elasticsearch.xpack.sql.expression.predicate.Predicates.inComm import static org.elasticsearch.xpack.sql.expression.predicate.Predicates.splitAnd; import static org.elasticsearch.xpack.sql.expression.predicate.Predicates.splitOr; import static org.elasticsearch.xpack.sql.expression.predicate.Predicates.subtract; +import static org.elasticsearch.xpack.sql.util.CollectionUtils.combine; + public class Optimizer extends RuleExecutor { @@ -101,7 +106,7 @@ public class Optimizer extends RuleExecutor { new CombineAggsToMatrixStats(), new CombineAggsToExtendedStats(), new CombineAggsToStats(), - new PromoteStatsToExtendedStats() + new PromoteStatsToExtendedStats(), new CombineAggsToPercentiles(), new CombineAggsToPercentileRanks() ); Batch cleanup = new Batch("Operator Optimization", @@ -254,7 +259,7 @@ public class Optimizer extends RuleExecutor { if (e instanceof MatrixStatsEnclosed) { AggregateFunction f = (AggregateFunction) e; - Expression argument = f.argument(); + Expression argument = f.field(); MatrixStats matrixStats = seen.get(argument); if (matrixStats == null) { @@ -262,7 +267,7 @@ public class Optimizer extends RuleExecutor { seen.put(argument, matrixStats); } - InnerAggregate ia = new InnerAggregate(f, matrixStats, f.argument()); + InnerAggregate ia = new InnerAggregate(f, matrixStats, f.field()); promotedIds.putIfAbsent(f.functionId(), ia.toAttribute()); return ia; } @@ -276,7 +281,6 @@ public class Optimizer extends RuleExecutor { @Override public LogicalPlan apply(LogicalPlan p) { Map promotedFunctionIds = new LinkedHashMap<>(); - Map seen = new LinkedHashMap<>(); p = p.transformExpressionsUp(e -> rule(e, seen, promotedFunctionIds)); // update old agg attributes @@ -292,7 +296,7 @@ public class Optimizer extends RuleExecutor { if (e instanceof ExtendedStatsEnclosed) { AggregateFunction f = (AggregateFunction) e; - Expression argument = f.argument(); + Expression argument = f.field(); ExtendedStats extendedStats = seen.get(argument); if (extendedStats == null) { @@ -343,7 +347,7 @@ public class Optimizer extends RuleExecutor { if (Stats.isTypeCompatible(e)) { AggregateFunction f = (AggregateFunction) e; - Expression argument = f.argument(); + Expression argument = f.field(); Counter counter = seen.get(argument); if (counter == null) { @@ -365,7 +369,7 @@ public class Optimizer extends RuleExecutor { if (Stats.isTypeCompatible(e)) { AggregateFunction f = (AggregateFunction) e; - Expression argument = f.argument(); + Expression argument = f.field(); Counter counter = seen.get(argument); // if the stat has at least two different functions for it, promote it as stat @@ -412,7 +416,7 @@ public class Optimizer extends RuleExecutor { InnerAggregate ia = (InnerAggregate) e; if (ia.outer() instanceof ExtendedStats) { ExtendedStats extStats = (ExtendedStats) ia.outer(); - seen.putIfAbsent(extStats.argument(), extStats); + seen.putIfAbsent(extStats.field(), extStats); } } } @@ -422,8 +426,8 @@ public class Optimizer extends RuleExecutor { InnerAggregate ia = (InnerAggregate) e; if (ia.outer() instanceof Stats) { Stats stats = (Stats) ia.outer(); - ExtendedStats ext = seen.get(stats.argument()); - if (ext != null && stats.argument().equals(ext.argument())) { + ExtendedStats ext = seen.get(stats.field()); + if (ext != null && stats.field().equals(ext.field())) { return new InnerAggregate(ia.inner(), ext); } } @@ -433,6 +437,119 @@ public class Optimizer extends RuleExecutor { } } + static class CombineAggsToPercentiles extends Rule { + + @Override + public LogicalPlan apply(LogicalPlan p) { + // percentile per field/expression + Map> percentsPerField = new LinkedHashMap<>(); + + // count gather the percents for each field + p.forEachExpressionsUp(e -> count(e, percentsPerField)); + + Map percentilesPerField = new LinkedHashMap<>(); + // create a Percentile agg for each field (and its associated percents) + percentsPerField.forEach((k, v) -> { + percentilesPerField.put(k, new Percentiles(v.iterator().next().location(), k, new ArrayList<>(v))); + }); + + // now replace the agg with pointer to the main ones + Map promotedFunctionIds = new LinkedHashMap<>(); + p = p.transformExpressionsUp(e -> rule(e, percentilesPerField, promotedFunctionIds)); + // finally update all the function references as well + return p.transformExpressionsDown(e -> CombineAggsToStats.updateFunctionAttrs(e, promotedFunctionIds)); + } + + private void count(Expression e, Map> percentsPerField) { + if (e instanceof Percentile) { + Percentile p = (Percentile) e; + Expression field = p.field(); + Set percentiles = percentsPerField.get(field); + + if (percentiles == null) { + percentiles = new LinkedHashSet<>(); + percentsPerField.put(field, percentiles); + } + + percentiles.add(p.percent()); + } + } + + protected Expression rule(Expression e, Map percentilesPerField, Map promotedIds) { + if (e instanceof Percentile) { + Percentile p = (Percentile) e; + Percentiles percentiles = percentilesPerField.get(p.field()); + + InnerAggregate ia = new InnerAggregate(p, percentiles); + promotedIds.putIfAbsent(p.functionId(), ia.toAttribute()); + return ia; + } + + return e; + } + + @Override + protected LogicalPlan rule(LogicalPlan e) { + return e; + } + } + + static class CombineAggsToPercentileRanks extends Rule { + + @Override + public LogicalPlan apply(LogicalPlan p) { + // percentile per field/expression + Map> valuesPerField = new LinkedHashMap<>(); + + // count gather the percents for each field + p.forEachExpressionsUp(e -> count(e, valuesPerField)); + + Map ranksPerField = new LinkedHashMap<>(); + // create a PercentileRanks agg for each field (and its associated values) + valuesPerField.forEach((k, v) -> { + ranksPerField.put(k, new PercentileRanks(v.iterator().next().location(), k, new ArrayList<>(v))); + }); + + // now replace the agg with pointer to the main ones + Map promotedFunctionIds = new LinkedHashMap<>(); + p = p.transformExpressionsUp(e -> rule(e, ranksPerField, promotedFunctionIds)); + // finally update all the function references as well + return p.transformExpressionsDown(e -> CombineAggsToStats.updateFunctionAttrs(e, promotedFunctionIds)); + } + + private void count(Expression e, Map> ranksPerField) { + if (e instanceof PercentileRank) { + PercentileRank p = (PercentileRank) e; + Expression field = p.field(); + Set percentiles = ranksPerField.get(field); + + if (percentiles == null) { + percentiles = new LinkedHashSet<>(); + ranksPerField.put(field, percentiles); + } + + percentiles.add(p.value()); + } + } + + protected Expression rule(Expression e, Map ranksPerField, Map promotedIds) { + if (e instanceof PercentileRank) { + PercentileRank p = (PercentileRank) e; + PercentileRanks ranks = ranksPerField.get(p.field()); + + InnerAggregate ia = new InnerAggregate(p, ranks); + promotedIds.putIfAbsent(p.functionId(), ia.toAttribute()); + return ia; + } + + return e; + } + + @Override + protected LogicalPlan rule(LogicalPlan e) { + return e; + } + } static class PruneFilters extends OptimizerRule { @@ -783,7 +900,7 @@ public class Optimizer extends RuleExecutor { // (a || b || c || ... ) && (a || b || d || ... ) => ((c || ...) && (d || ...)) || a || b Expression combineLeft = combineOr(lDiff); Expression combineRight = combineOr(rDiff); - return combineOr(CollectionUtils.combine(common, new And(combineLeft.location(), combineLeft, combineRight))); + return combineOr(combine(common, new And(combineLeft.location(), combineLeft, combineRight))); } if (bc instanceof Or) { @@ -821,7 +938,7 @@ public class Optimizer extends RuleExecutor { // (a || b || c || ... ) && (a || b || d || ... ) => ((c || ...) && (d || ...)) || a || b Expression combineLeft = combineAnd(lDiff); Expression combineRight = combineAnd(rDiff); - return combineAnd(CollectionUtils.combine(common, new Or(combineLeft.location(), combineLeft, combineRight))); + return combineAnd(combine(common, new Or(combineLeft.location(), combineLeft, combineRight))); } // TODO: eliminate conjunction/disjunction diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/parser/ExpressionBuilder.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/parser/ExpressionBuilder.java index 344cc6dabf5..847b5dc481c 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/parser/ExpressionBuilder.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/parser/ExpressionBuilder.java @@ -90,14 +90,6 @@ abstract class ExpressionBuilder extends IdentifierBuilder { return expression(ctx.expression()); } - - @Override - // Star will be resolved by the analyzer - currently just put a placeholder - public Expression visitStar(StarContext ctx) { - return new UnresolvedStar(source(ctx), ctx.qualifier != null ? visitColumnExpression(ctx.qualifier) : null); - } - - @Override public Expression visitSelectExpression(SelectExpressionContext ctx) { Expression exp = expression(ctx.expression()); @@ -108,6 +100,11 @@ abstract class ExpressionBuilder extends IdentifierBuilder { return exp; } + @Override + public Expression visitStar(StarContext ctx) { + return new UnresolvedStar(source(ctx), ctx.qualifier != null ? visitColumnExpression(ctx.qualifier) : null); + } + @Override public Object visitDereference(DereferenceContext ctx) { String fieldName = visitIdentifier(ctx.fieldName); @@ -286,6 +283,7 @@ abstract class ExpressionBuilder extends IdentifierBuilder { if (ctx.setQuantifier() != null) { isDistinct = (ctx.setQuantifier().DISTINCT() != null); } + return new UnresolvedFunction(source(ctx), name, isDistinct, expressions(ctx.expression())); } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java index d5722ad4fc6..f2686c76332 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java @@ -10,6 +10,7 @@ import org.elasticsearch.xpack.sql.expression.Alias; 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.Foldables; import org.elasticsearch.xpack.sql.expression.NamedExpression; import org.elasticsearch.xpack.sql.expression.NestedFieldAttribute; import org.elasticsearch.xpack.sql.expression.Order; @@ -17,7 +18,7 @@ import org.elasticsearch.xpack.sql.expression.RootFieldAttribute; import org.elasticsearch.xpack.sql.expression.function.Function; import org.elasticsearch.xpack.sql.expression.function.Functions; import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunction; -import org.elasticsearch.xpack.sql.expression.function.aggregate.CompoundAggregate; +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.InnerAggregate; import org.elasticsearch.xpack.sql.expression.function.scalar.ColumnProcessor; @@ -234,7 +235,7 @@ class QueryFolder extends RuleExecutor { Map aliases = new LinkedHashMap<>(); // tracker for compound aggs seen in a group - Map compoundAggMap = new LinkedHashMap<>(); + Map compoundAggMap = new LinkedHashMap<>(); // followed by actual aggregates for (NamedExpression ne : a.aggregates()) { @@ -338,7 +339,7 @@ class QueryFolder extends RuleExecutor { return queryC.addAggRef(aggInfo.propertyPath(), proc); } - private QueryContainer addFunction(GroupingAgg parentAgg, AggregateFunction f, ColumnProcessor proc, Map compoundAggMap, QueryContainer queryC) { + private QueryContainer addFunction(GroupingAgg parentAgg, AggregateFunction f, ColumnProcessor proc, Map compoundAggMap, QueryContainer queryC) { String functionId = f.functionId(); // handle count as a special case agg if (f instanceof Count) { @@ -354,7 +355,7 @@ class QueryFolder extends RuleExecutor { if (f instanceof InnerAggregate) { InnerAggregate ia = (InnerAggregate) f; - CompoundAggregate outer = ia.outer(); + CompoundNumericAggregate outer = ia.outer(); String cAggPath = compoundAggMap.get(outer); // the compound agg hasn't been seen before so initialize it @@ -449,7 +450,7 @@ class QueryFolder extends RuleExecutor { protected PhysicalPlan rule(LimitExec plan) { if (plan.child() instanceof EsQueryExec) { EsQueryExec exec = (EsQueryExec) plan.child(); - int limit = Integer.valueOf(QueryTranslator.valueOf(plan.limit())); + int limit = Foldables.intValueOf(plan.limit()); int currentSize = exec.queryContainer().limit(); int newSize = currentSize < 0 ? limit : Math.min(currentSize, limit); return exec.with(exec.queryContainer().withLimit(newSize)); diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java index 0d5ec193663..c9cdcfc1f6a 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java @@ -20,12 +20,14 @@ import org.elasticsearch.xpack.sql.expression.function.Functions; import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunction; import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunctionAttribute; import org.elasticsearch.xpack.sql.expression.function.aggregate.Avg; -import org.elasticsearch.xpack.sql.expression.function.aggregate.CompoundAggregate; +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.ExtendedStats; import org.elasticsearch.xpack.sql.expression.function.aggregate.MatrixStats; import org.elasticsearch.xpack.sql.expression.function.aggregate.Max; import org.elasticsearch.xpack.sql.expression.function.aggregate.Min; +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.Stats; import org.elasticsearch.xpack.sql.expression.function.aggregate.Sum; import org.elasticsearch.xpack.sql.expression.function.scalar.ColumnProcessor; @@ -62,6 +64,8 @@ import org.elasticsearch.xpack.sql.querydsl.agg.MatrixStatsAgg; import org.elasticsearch.xpack.sql.querydsl.agg.MaxAgg; import org.elasticsearch.xpack.sql.querydsl.agg.MinAgg; import org.elasticsearch.xpack.sql.querydsl.agg.OrAggFilter; +import org.elasticsearch.xpack.sql.querydsl.agg.PercentileRanksAgg; +import org.elasticsearch.xpack.sql.querydsl.agg.PercentilesAgg; import org.elasticsearch.xpack.sql.querydsl.agg.StatsAgg; import org.elasticsearch.xpack.sql.querydsl.agg.SumAgg; import org.elasticsearch.xpack.sql.querydsl.query.AndQuery; @@ -93,6 +97,9 @@ import java.util.Map.Entry; import static java.lang.String.format; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; +import static org.elasticsearch.xpack.sql.expression.Foldables.doubleValuesOf; +import static org.elasticsearch.xpack.sql.expression.Foldables.stringValueOf; +import static org.elasticsearch.xpack.sql.expression.Foldables.valueOf; import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder; import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate.formatTemplate; @@ -117,6 +124,8 @@ abstract class QueryTranslator { new StatsAggs(), new ExtendedStatsAggs(), new MatrixStatsAggs(), + new PercentilesAggs(), + new PercentileRanksAggs(), new DistinctCounts(), new DateTimes() ); @@ -203,8 +212,8 @@ abstract class QueryTranslator { if (groupPath != null) { GroupingAgg matchingGroup = null; // group found - finding the dedicated agg - if (f.argument() instanceof NamedExpression) { - matchingGroup = groupMap.get(((NamedExpression) f.argument()).id()); + if (f.field() instanceof NamedExpression) { + matchingGroup = groupMap.get(((NamedExpression) f.field()).id()); } // return matching group or the tail (last group) return matchingGroup != null ? matchingGroup : tail; @@ -358,11 +367,6 @@ abstract class QueryTranslator { return new NotQuery(query.location(), query); } - @SuppressWarnings("unchecked") - static T valueOf(Expression e) { - return (T) ((Literal) e).value(); - } - static String nameOf(Expression e) { if (e instanceof DateTimeFunction) { return nameOf(((DateTimeFunction) e).argument()); @@ -370,6 +374,9 @@ abstract class QueryTranslator { if (e instanceof NamedExpression) { return ((NamedExpression) e).name(); } + if (e instanceof Literal) { + return String.valueOf(e.fold()); + } throw new SqlIllegalArgumentException("Cannot determine name for %s", e); } @@ -394,7 +401,7 @@ abstract class QueryTranslator { } static String field(AggregateFunction af) { - Expression arg = af.argument(); + Expression arg = af.field(); if (arg instanceof RootFieldAttribute) { return ((RootFieldAttribute) arg).name(); } @@ -420,7 +427,7 @@ abstract class QueryTranslator { target = nameOf(analyzed ? fa : fa.notAnalyzedAttribute()); } - String pattern = sqlToEsPatternMatching(valueOf(e.right())); + String pattern = sqlToEsPatternMatching(stringValueOf(e.right())); if (e instanceof Like) { if (analyzed) { q = new QueryStringQuery(e.location(), pattern, target); @@ -435,7 +442,7 @@ abstract class QueryTranslator { q = new QueryStringQuery(e.location(), "/" + pattern + "/", target); } else { - q = new RegexQuery(e.location(), nameOf(e.left()), sqlToEsPatternMatching(valueOf(e.right()))); + q = new RegexQuery(e.location(), nameOf(e.left()), sqlToEsPatternMatching(stringValueOf(e.right()))); } } @@ -765,6 +772,22 @@ abstract class QueryTranslator { } } + static class PercentilesAggs extends CompoundAggTranslator { + + @Override + protected LeafAgg toAgg(String id, String path, Percentiles p) { + return new PercentilesAgg(id, path, field(p), doubleValuesOf(p.percents())); + } + } + + static class PercentileRanksAggs extends CompoundAggTranslator { + + @Override + protected LeafAgg toAgg(String id, String path, PercentileRanks p) { + return new PercentileRanksAgg(id, path, field(p), doubleValuesOf(p.values())); + } + } + static class DateTimes extends SingleValueAggTranslator { @Override @@ -796,7 +819,7 @@ abstract class QueryTranslator { protected abstract LeafAgg toAgg(String id, String path, F f); } - abstract static class CompoundAggTranslator extends AggTranslator { + abstract static class CompoundAggTranslator extends AggTranslator { @Override protected final LeafAgg asAgg(String id, String parent, C function) { diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/AggPath.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/AggPath.java index 0b82cfffc42..162111ed8fe 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/AggPath.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/AggPath.java @@ -41,7 +41,8 @@ public abstract class AggPath { } public static String metricValue(String aggPath, String valueName) { - return aggPath + VALUE_DELIMITER + valueName; + // handle aggPath inconsistency (for percentiles and percentileRanks) percentile[99.9] (valid) vs percentile.99.9 (invalid) + return valueName.startsWith("[") ? aggPath + valueName : aggPath + VALUE_DELIMITER + valueName; } public static String path(String parent, String child) { diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/PercentileRanksAgg.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/PercentileRanksAgg.java new file mode 100644 index 00000000000..5c05a9098e4 --- /dev/null +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/PercentileRanksAgg.java @@ -0,0 +1,34 @@ +/* + * 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.AggregationBuilder; + +import java.util.List; + +import static org.elasticsearch.search.aggregations.AggregationBuilders.percentileRanks; + +public class PercentileRanksAgg extends LeafAgg { + + private final List values; + + public PercentileRanksAgg(String id, String propertyPath, String fieldName, List values) { + super(id, propertyPath, fieldName); + this.values = values; + } + + public List percents() { + return values; + } + + @Override + AggregationBuilder toBuilder() { + // TODO: look at keyed + return percentileRanks(id()) + .field(fieldName()) + .values(values.stream().mapToDouble(Double::doubleValue).toArray()); + } +} diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/PercentilesAgg.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/PercentilesAgg.java new file mode 100644 index 00000000000..6a3ae123022 --- /dev/null +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/querydsl/agg/PercentilesAgg.java @@ -0,0 +1,34 @@ +/* + * 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.AggregationBuilder; + +import java.util.List; + +import static org.elasticsearch.search.aggregations.AggregationBuilders.percentiles; + +public class PercentilesAgg extends LeafAgg { + + private final List percents; + + public PercentilesAgg(String id, String propertyPath, String fieldName, List percents) { + super(id, propertyPath, fieldName); + this.percents = percents; + } + + public List percents() { + return percents; + } + + @Override + AggregationBuilder toBuilder() { + // TODO: look at keyed + return percentiles(id()) + .field(fieldName()) + .percentiles(percents.stream().mapToDouble(Double::doubleValue).toArray()); + } +} diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java index 6b51907b1bd..89c79133ddf 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java @@ -64,4 +64,4 @@ public interface DataType { default boolean same(DataType other) { return getClass() == other.getClass(); } -} +} \ No newline at end of file diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/type/DataTypes.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/type/DataTypes.java index 461e7412b80..d458152e57c 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/type/DataTypes.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/type/DataTypes.java @@ -5,12 +5,12 @@ */ package org.elasticsearch.xpack.sql.type; +import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; + import java.sql.JDBCType; import java.util.LinkedHashMap; import java.util.Map; -import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; - public abstract class DataTypes { public static final DataType NULL = new NullType(); @@ -104,6 +104,9 @@ public abstract class DataTypes { if (value instanceof Short) { return SHORT; } + if (value instanceof String) { + return KEYWORD; + } throw new SqlIllegalArgumentException("No idea what's the DataType for %s", value.getClass()); } diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/util/CollectionUtils.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/util/CollectionUtils.java index 491c8d89e5b..564e577c255 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/util/CollectionUtils.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/util/CollectionUtils.java @@ -74,7 +74,15 @@ public abstract class CollectionUtils { return map; } - public static List combine(Collection left, Collection right) { + @SuppressWarnings("unchecked") + public static List combine(List left, List right) { + if (right.isEmpty()) { + return (List) left; + } + if (left.isEmpty()) { + return (List) right; + } + List list = new ArrayList<>(left.size() + right.size()); if (!left.isEmpty()) { list.addAll(left);