From 44d6c74214ab784ad324e7b5ecbb56d18940be8c Mon Sep 17 00:00:00 2001 From: Marios Trivyzas Date: Mon, 26 Nov 2018 18:21:36 +0100 Subject: [PATCH] SQL: Implement GREATEST and LEAST functions (#35879) Add GREATEST(expr1, expr2, ... exprN) and LEAST(expr1, expr2, exprN) functions which are in the family of CONDITIONAL functions. Implementation follows PostgreSQL behaviour, so the functions return `NULL` when all of their arguments evaluate to `NULL`. Renamed `CoalescePipe` and `CoalesceProcessor` to `ConditionalPipe` and `ConditionalProcessor` respectively, to be able to reuse them for `Greatest` and `Least` evaluations. To achieve that `ConditionalOperation` has been added to differentiate between the functionalities at execution time. Closes: #35878 --- .../sql/functions/conditional.asciidoc | 84 +++++++++++++++ .../xpack/sql/qa/cli/ShowTestCase.java | 12 ++- .../qa/src/main/resources/command.csv-spec | 4 +- .../sql/qa/src/main/resources/docs.csv-spec | 46 +++++++- .../sql/qa/src/main/resources/null.sql-spec | 22 +++- .../xpack/sql/expression/Foldables.java | 18 +++- .../expression/function/FunctionRegistry.java | 10 +- .../function/scalar/Processors.java | 4 +- .../whitelist/InternalSqlScriptUtils.java | 12 ++- .../ArbitraryConditionalFunction.java | 66 ++++++++++++ .../predicate/conditional/Coalesce.java | 57 +--------- .../predicate/conditional/CoalescePipe.java | 38 ------- .../conditional/CoalesceProcessor.java | 81 -------------- .../conditional/ConditionalFunction.java | 21 +++- .../conditional/ConditionalPipe.java | 57 ++++++++++ .../conditional/ConditionalProcessor.java | 101 ++++++++++++++++++ .../predicate/conditional/Conditionals.java | 84 +++++++++++++++ .../predicate/conditional/Greatest.java | 40 +++++++ .../predicate/conditional/IfNull.java | 8 +- .../predicate/conditional/Least.java | 40 +++++++ .../predicate/conditional/NullIf.java | 2 - .../operator/comparison/Comparisons.java | 4 +- .../xpack/sql/optimizer/Optimizer.java | 22 ++-- .../xpack/sql/planner/QueryFolder.java | 4 - .../xpack/sql/plugin/sql_whitelist.txt | 2 + .../xpack/sql/optimizer/OptimizerTests.java | 64 +++++++++-- 26 files changed, 681 insertions(+), 222 deletions(-) create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ArbitraryConditionalFunction.java delete mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalescePipe.java delete mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalesceProcessor.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalPipe.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalProcessor.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Conditionals.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Greatest.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Least.java diff --git a/docs/reference/sql/functions/conditional.asciidoc b/docs/reference/sql/functions/conditional.asciidoc index a440f9cbd9a3c..c4947c713e2be 100644 --- a/docs/reference/sql/functions/conditional.asciidoc +++ b/docs/reference/sql/functions/conditional.asciidoc @@ -190,3 +190,87 @@ include-tagged::{sql-specs}/docs.csv-spec[nullIfReturnFirst] ---- include-tagged::{sql-specs}/docs.csv-spec[nullIfReturnNull] ---- + + +[[sql-functions-conditional-greatest]] +==== `GREATEST` + +.Synopsis +[source, sql] +---- +GREATEST ( expression<1>, expression<2>, ... ) +---- + +*Input*: + +<1> 1st expression + +<2> 2nd expression + +... + +**N**th expression + +GREATEST can take an arbitrary number of arguments and +all of them must be of the same data type. + +*Output*: one of the expressions or `null` + +.Description + +Returns the argument that has the largest value which is not null. +If all arguments are null, then it returns `null`. + + + +["source","sql",subs="attributes,callouts,macros"] +---- +include-tagged::{sql-specs}/docs.csv-spec[greatestReturnNonNull] +---- + +["source","sql",subs="attributes,callouts,macros"] +---- +include-tagged::{sql-specs}/docs.csv-spec[greatestReturnNull] +---- + + +[[sql-functions-conditional-least]] +==== `LEAST` + +.Synopsis +[source, sql] +---- +LEAST ( expression<1>, expression<2>, ... ) +---- + +*Input*: + +<1> 1st expression + +<2> 2nd expression + +... + +**N**th expression + +LEAST can take an arbitrary number of arguments and +all of them must be of the same data type. + +*Output*: one of the expressions or `null` + +.Description + +Returns the argument that has the smallest value which is not null. +If all arguments are null, then it returns `null`. + + + +["source","sql",subs="attributes,callouts,macros"] +---- +include-tagged::{sql-specs}/docs.csv-spec[leastReturnNonNull] +---- + +["source","sql",subs="attributes,callouts,macros"] +---- +include-tagged::{sql-specs}/docs.csv-spec[leastReturnNull] +---- diff --git a/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ShowTestCase.java b/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ShowTestCase.java index 00fb96d01855c..ed3320567d0ea 100644 --- a/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ShowTestCase.java +++ b/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ShowTestCase.java @@ -13,11 +13,14 @@ import static org.hamcrest.Matchers.containsString; public abstract class ShowTestCase extends CliIntegrationTestCase { + + private static final String HEADER_SEPARATOR = "----------"; + public void testShowTables() throws IOException { index("test1", body -> body.field("test_field", "test_value")); index("test2", body -> body.field("test_field", "test_value")); assertThat(command("SHOW TABLES"), RegexMatcher.matches("\\s*name\\s*")); - assertThat(readLine(), containsString("----------")); + assertThat(readLine(), containsString(HEADER_SEPARATOR)); assertThat(readLine(), RegexMatcher.matches("\\s*test[12]\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*test[12]\\s*")); assertEquals("", readLine()); @@ -25,7 +28,7 @@ public void testShowTables() throws IOException { public void testShowFunctions() throws IOException { assertThat(command("SHOW FUNCTIONS"), RegexMatcher.matches("\\s*name\\s*\\|\\s*type\\s*")); - assertThat(readLine(), containsString("----------")); + assertThat(readLine(), containsString(HEADER_SEPARATOR)); assertThat(readLine(), RegexMatcher.matches("\\s*AVG\\s*\\|\\s*AGGREGATE\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*COUNT\\s*\\|\\s*AGGREGATE\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*MAX\\s*\\|\\s*AGGREGATE\\s*")); @@ -50,7 +53,8 @@ public void testShowFunctions() throws IOException { public void testShowFunctionsLikePrefix() throws IOException { assertThat(command("SHOW FUNCTIONS LIKE 'L%'"), RegexMatcher.matches("\\s*name\\s*\\|\\s*type\\s*")); - assertThat(readLine(), containsString("----------")); + assertThat(readLine(), containsString(HEADER_SEPARATOR)); + assertThat(readLine(), RegexMatcher.matches("\\s*LEAST\\s*\\|\\s*CONDITIONAL\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*LOG\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*LOG10\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*LCASE\\s*\\|\\s*SCALAR\\s*")); @@ -63,7 +67,7 @@ public void testShowFunctionsLikePrefix() throws IOException { public void testShowFunctionsLikeInfix() throws IOException { assertThat(command("SHOW FUNCTIONS LIKE '%DAY%'"), RegexMatcher.matches("\\s*name\\s*\\|\\s*type\\s*")); - assertThat(readLine(), containsString("----------")); + assertThat(readLine(), containsString(HEADER_SEPARATOR)); assertThat(readLine(), RegexMatcher.matches("\\s*DAY\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*DAYNAME\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*DAYOFMONTH\\s*\\|\\s*SCALAR\\s*")); diff --git a/x-pack/plugin/sql/qa/src/main/resources/command.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/command.csv-spec index a9e57d59f280f..d71d2da9b025d 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/command.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/command.csv-spec @@ -20,10 +20,12 @@ STDDEV_POP |AGGREGATE SUM_OF_SQUARES |AGGREGATE VAR_POP |AGGREGATE COALESCE |CONDITIONAL +GREATEST |CONDITIONAL IFNULL |CONDITIONAL ISNULL |CONDITIONAL -NVL |CONDITIONAL +LEAST |CONDITIONAL NULLIF |CONDITIONAL +NVL |CONDITIONAL DAY |SCALAR DAYNAME |SCALAR DAYOFMONTH |SCALAR diff --git a/x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec index 1202a26c6b5f0..10906390bfd35 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec @@ -197,10 +197,12 @@ STDDEV_POP |AGGREGATE SUM_OF_SQUARES |AGGREGATE VAR_POP |AGGREGATE COALESCE |CONDITIONAL +GREATEST |CONDITIONAL IFNULL |CONDITIONAL ISNULL |CONDITIONAL -NVL |CONDITIONAL +LEAST |CONDITIONAL NULLIF |CONDITIONAL +NVL |CONDITIONAL DAY |SCALAR DAYNAME |SCALAR DAYOFMONTH |SCALAR @@ -1620,6 +1622,48 @@ null // end::nullIfReturnNull ; +greatestReturnNonNull +// tag::greatestReturnNonNull +SELECT GREATEST(null, 1, 2) AS "greatest"; + + greatest +--------------- +2 +// end::greatestReturnNonNull +; + + +greatestReturnNull +// tag::greatestReturnNull +SELECT GREATEST(null, null, null, null) AS "greatest"; + + greatest +--------------- +null +// end::greatestReturnNull +; + +leastReturnNonNull +// tag::leastReturnNonNull +SELECT LEAST(null, 2, 1) AS "least"; + + least +--------------- +1 +// end::leastReturnNonNull +; + + +leastReturnNull +// tag::leastReturnNull +SELECT LEAST(null, null, null, null) AS "least"; + + least +--------------- +null +// end::leastReturnNull +; + nullEqualsCompareWithNull // tag::nullEqualsCompareWithNull SELECT 'elastic' <=> null AS "equals"; diff --git a/x-pack/plugin/sql/qa/src/main/resources/null.sql-spec b/x-pack/plugin/sql/qa/src/main/resources/null.sql-spec index 456ab626d1805..8da5d8c1e8b24 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/null.sql-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/null.sql-spec @@ -15,10 +15,28 @@ ifNullField SELECT IFNULL(null, ABS(emp_no) + 1) AS "ifnull" FROM test_emp ORDER BY emp_no LIMIT 5; nullIfField -SELECT NULLIF(emp_no - 2 + 3, ABS(emp_no) + 1) AS "nullif1", NULLIF(emp_no + 1, emp_no - 1) as "nullif2" FROM test_emp ORDER BY emp_no LIMIT 5; +SELECT NULLIF(emp_no - 2 + 3, ABS(emp_no) + 1) AS "nullif1", NULLIF(emp_no + 1, emp_no - 1) AS "nullif2" FROM test_emp ORDER BY emp_no LIMIT 5; nullIfWhere -SELECT NULLIF(10002, ABS(emp_no) + 1) AS c, emp_no FROM test_emp WHERE NULLIF(10003, ABS(emp_no) + 1) IS NOT NULL ORDER BY emp_no NULLS FIRST LIMIT 5; +SELECT NULLIF(10002, ABS(emp_no) + 1) AS c, emp_no FROM test_emp WHERE NULLIF(10003, ABS(emp_no) + 1) IS NOT NULL ORDER BY emp_no LIMIT 5; nullIfHaving SELECT NULLIF(10030, ABS(MAX(emp_no)) + 1) AS nif FROM test_emp GROUP BY languages HAVING nif IS NOT NULL ORDER BY languages; + +greatestField +SELECT GREATEST(emp_no - 1 + 3, ABS(emp_no) + 1) AS "greatest" FROM test_emp ORDER BY emp_no LIMIT 5; + +greatestWhere +SELECT emp_no FROM test_emp WHERE GREATEST(10005, ABS(emp_no) + 1, null, emp_no - 1 + 3) > 10008 ORDER BY emp_no LIMIT 5; + +greatestHaving +SELECT GREATEST(10096, ABS(MAX(emp_no)) + 1) AS gt FROM test_emp GROUP BY languages HAVING gt >= 10098 ORDER BY languages; + +leastField +SELECT LEAST(emp_no - 1 + 3, ABS(emp_no) + 1) AS "least" FROM test_emp ORDER BY emp_no LIMIT 5; + +leastWhere +SELECT emp_no FROM test_emp WHERE LEAST(10005, ABS(emp_no) + 1, null, emp_no - 1 + 3) > 10004 ORDER BY emp_no LIMIT 5; + +leastHaving +SELECT LEAST(10098, ABS(MAX(emp_no)) + 1) AS lt FROM test_emp GROUP BY languages HAVING lt >= 10095 ORDER BY languages; diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Foldables.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Foldables.java index 8c672ed162ee6..f0b8ab2f406b9 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Foldables.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Foldables.java @@ -10,7 +10,10 @@ import org.elasticsearch.xpack.sql.type.DataTypeConversion; import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; public abstract class Foldables { @@ -46,11 +49,18 @@ public static double doubleValueOf(Expression e) { } public static List valuesOf(List list, DataType to) { - List l = new ArrayList<>(list.size()); - for (Expression e : list) { - l.add(valueOf(e, to)); + return foldTo(list, to, new ArrayList<>(list.size())); + } + + public static Set valuesOfNoDuplicates(List list, DataType to) { + return foldTo(list, to, new LinkedHashSet<>(list.size())); + } + + private static > C foldTo(Collection expressions, DataType to, C values) { + for (Expression e : expressions) { + values.add(valueOf(e, to)); } - return l; + return values; } public static List doubleValuesOf(List list) { diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java index 3fc2db2e43183..c048cb7037fa0 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java @@ -83,7 +83,9 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.string.Substring; import org.elasticsearch.xpack.sql.expression.function.scalar.string.UCase; import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.Greatest; import org.elasticsearch.xpack.sql.expression.predicate.conditional.IfNull; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.Least; import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIf; import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Mod; import org.elasticsearch.xpack.sql.parser.ParsingException; @@ -157,9 +159,11 @@ private void defineDefaultFunctions() { def(Kurtosis.class, Kurtosis::new)); // Scalar functions // conditional - addToMap(def(Coalesce.class, Coalesce::new)); - addToMap(def(IfNull.class, IfNull::new, "ISNULL", "NVL")); - addToMap(def(NullIf.class, NullIf::new)); + addToMap(def(Coalesce.class, Coalesce::new), + def(IfNull.class, IfNull::new, "ISNULL", "NVL"), + def(NullIf.class, NullIf::new), + def(Greatest.class, Greatest::new), + def(Least.class, Least::new)); // Date addToMap(def(DayName.class, DayName::new, "DAYNAME"), def(DayOfMonth.class, DayOfMonth::new, "DAYOFMONTH", "DAY", "DOM"), diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Processors.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Processors.java index 4182791126da0..9a401273b2366 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Processors.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Processors.java @@ -25,7 +25,7 @@ import org.elasticsearch.xpack.sql.expression.gen.processor.ConstantProcessor; import org.elasticsearch.xpack.sql.expression.gen.processor.HitExtractorProcessor; import org.elasticsearch.xpack.sql.expression.gen.processor.Processor; -import org.elasticsearch.xpack.sql.expression.predicate.conditional.CoalesceProcessor; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor; import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIfProcessor; import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor; import org.elasticsearch.xpack.sql.expression.predicate.logical.NotProcessor; @@ -61,7 +61,7 @@ public static List getNamedWriteables() { entries.add(new Entry(Processor.class, NotProcessor.NAME, NotProcessor::new)); // null entries.add(new Entry(Processor.class, CheckNullProcessor.NAME, CheckNullProcessor::new)); - entries.add(new Entry(Processor.class, CoalesceProcessor.NAME, CoalesceProcessor::new)); + entries.add(new Entry(Processor.class, ConditionalProcessor.NAME, ConditionalProcessor::new)); entries.add(new Entry(Processor.class, NullIfProcessor.NAME, NullIfProcessor::new)); // arithmetic diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java index 924cbc09cb19b..54b3b5cb9851b 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java @@ -23,7 +23,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.string.SubstringFunctionProcessor; import org.elasticsearch.xpack.sql.expression.literal.IntervalDayTime; import org.elasticsearch.xpack.sql.expression.literal.IntervalYearMonth; -import org.elasticsearch.xpack.sql.expression.predicate.conditional.CoalesceProcessor; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation; import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIfProcessor; import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor.BinaryLogicOperation; import org.elasticsearch.xpack.sql.expression.predicate.logical.NotProcessor; @@ -144,7 +144,15 @@ public static Boolean in(Object value, List values) { // Null // public static Object coalesce(List expressions) { - return CoalesceProcessor.apply(expressions); + return ConditionalOperation.COALESCE.apply(expressions); + } + + public static Object greatest(List expressions) { + return ConditionalOperation.GREATEST.apply(expressions); + } + + public static Object least(List expressions) { + return ConditionalOperation.LEAST.apply(expressions); } public static Object nullif(Object left, Object right) { diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ArbitraryConditionalFunction.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ArbitraryConditionalFunction.java new file mode 100644 index 0000000000000..6d38037ec267a --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ArbitraryConditionalFunction.java @@ -0,0 +1,66 @@ +/* + * 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.predicate.conditional; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.Expressions; +import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe; +import org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder; +import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.type.DataTypeConversion; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder; + +/** + * Base class for conditional predicates with arbitrary number of arguments + */ +public abstract class ArbitraryConditionalFunction extends ConditionalFunction { + + private final ConditionalOperation operation; + + ArbitraryConditionalFunction(Location location, List fields, ConditionalOperation operation) { + super(location, fields); + this.operation = operation; + } + + @Override + protected TypeResolution resolveType() { + for (Expression e : children()) { + dataType = DataTypeConversion.commonType(dataType, e.dataType()); + } + return TypeResolution.TYPE_RESOLVED; + } + + @Override + protected Pipe makePipe() { + return new ConditionalPipe(location(), this, Expressions.pipe(children()), operation); + } + + @Override + public ScriptTemplate asScript() { + List templates = new ArrayList<>(); + for (Expression ex : children()) { + templates.add(asScript(ex)); + } + + StringJoiner template = new StringJoiner(",", "{sql}." + operation.scriptMethodName() +"([", "])"); + ParamsBuilder params = paramsBuilder(); + + for (ScriptTemplate scriptTemplate : templates) { + template.add(scriptTemplate.template()); + params.script(scriptTemplate.params()); + } + + return new ScriptTemplate(template.toString(), params.build(), dataType()); + } +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Coalesce.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Coalesce.java index 6b3e495217ea2..bd958f052d7ec 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Coalesce.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Coalesce.java @@ -7,27 +7,17 @@ package org.elasticsearch.xpack.sql.expression.predicate.conditional; import org.elasticsearch.xpack.sql.expression.Expression; -import org.elasticsearch.xpack.sql.expression.Expressions; -import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe; -import org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder; -import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate; 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.DataTypeConversion; -import java.util.ArrayList; import java.util.List; -import java.util.StringJoiner; -import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder; +import static org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation.COALESCE; -public class Coalesce extends ConditionalFunction { - - private DataType dataType = DataType.NULL; +public class Coalesce extends ArbitraryConditionalFunction { public Coalesce(Location location, List fields) { - super(location, fields); + super(location, fields, COALESCE); } @Override @@ -40,19 +30,6 @@ public Expression replaceChildren(List newChildren) { return new Coalesce(location(), newChildren); } - @Override - protected TypeResolution resolveType() { - for (Expression e : children()) { - dataType = DataTypeConversion.commonType(dataType, e.dataType()); - } - return TypeResolution.TYPE_RESOLVED; - } - - @Override - public DataType dataType() { - return dataType; - } - @Override public boolean foldable() { // if the first entry is foldable, so is coalesce @@ -62,37 +39,9 @@ public boolean foldable() { return (children.isEmpty() || (children.get(0).foldable() && children.get(0).fold() != null)); } - @Override - public boolean nullable() { - return false; - } - @Override public Object fold() { List children = children(); return children.isEmpty() ? null : children.get(0).fold(); } - - @Override - public ScriptTemplate asScript() { - List templates = new ArrayList<>(); - for (Expression ex : children()) { - templates.add(asScript(ex)); - } - - StringJoiner template = new StringJoiner(",", "{sql}.coalesce([", "])"); - ParamsBuilder params = paramsBuilder(); - - for (ScriptTemplate scriptTemplate : templates) { - template.add(scriptTemplate.template()); - params.script(scriptTemplate.params()); - } - - return new ScriptTemplate(template.toString(), params.build(), dataType); - } - - @Override - protected Pipe makePipe() { - return new CoalescePipe(location(), this, Expressions.pipe(children())); - } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalescePipe.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalescePipe.java deleted file mode 100644 index a19d13e02df92..0000000000000 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalescePipe.java +++ /dev/null @@ -1,38 +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.predicate.conditional; - -import org.elasticsearch.xpack.sql.expression.Expression; -import org.elasticsearch.xpack.sql.expression.gen.pipeline.MultiPipe; -import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe; -import org.elasticsearch.xpack.sql.expression.gen.processor.Processor; -import org.elasticsearch.xpack.sql.tree.Location; -import org.elasticsearch.xpack.sql.tree.NodeInfo; - -import java.util.List; - -public class CoalescePipe extends MultiPipe { - - public CoalescePipe(Location location, Expression expression, List children) { - super(location, expression, children); - } - - @Override - protected NodeInfo info() { - return NodeInfo.create(this, CoalescePipe::new, expression(), children()); - } - - @Override - public Pipe replaceChildren(List newChildren) { - return new CoalescePipe(location(), expression(), newChildren); - } - - @Override - public Processor asProcessor(List procs) { - return new CoalesceProcessor(procs); - } -} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalesceProcessor.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalesceProcessor.java deleted file mode 100644 index e23b43634238d..0000000000000 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalesceProcessor.java +++ /dev/null @@ -1,81 +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.predicate.conditional; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.xpack.sql.expression.gen.processor.Processor; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; - -public class CoalesceProcessor implements Processor { - - public static final String NAME = "nco"; - - private final List processsors; - - public CoalesceProcessor(List processors) { - this.processsors = processors; - } - - public CoalesceProcessor(StreamInput in) throws IOException { - processsors = in.readNamedWriteableList(Processor.class); - } - - @Override - public String getWriteableName() { - return NAME; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeNamedWriteableList(processsors); - } - - @Override - public Object process(Object input) { - for (Processor proc : processsors) { - Object result = proc.process(input); - if (result != null) { - return result; - } - } - return null; - } - - public static Object apply(List values) { - if (values == null || values.isEmpty()) { - return null; - } - - for (Object object : values) { - if (object != null) { - return object; - } - } - - return null; - } - - @Override - public int hashCode() { - return Objects.hash(processsors); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - CoalesceProcessor that = (CoalesceProcessor) o; - return Objects.equals(processsors, that.processsors); - } -} \ No newline at end of file diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalFunction.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalFunction.java index 2feff40643335..0ffe8621fb221 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalFunction.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalFunction.java @@ -7,8 +7,10 @@ package org.elasticsearch.xpack.sql.expression.predicate.conditional; import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.Expressions; import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction; import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.type.DataType; import java.util.List; @@ -17,7 +19,24 @@ */ public abstract class ConditionalFunction extends ScalarFunction { - protected ConditionalFunction(Location location, List fields) { + protected DataType dataType = DataType.NULL; + + ConditionalFunction(Location location, List fields) { super(location, fields); } + + @Override + public DataType dataType() { + return dataType; + } + + @Override + public boolean foldable() { + return Expressions.foldable(children()); + } + + @Override + public boolean nullable() { + return false; + } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalPipe.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalPipe.java new file mode 100644 index 0000000000000..0c45438d58285 --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalPipe.java @@ -0,0 +1,57 @@ +/* + * 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.predicate.conditional; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.gen.pipeline.MultiPipe; +import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe; +import org.elasticsearch.xpack.sql.expression.gen.processor.Processor; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; + +import java.util.List; +import java.util.Objects; + +public class ConditionalPipe extends MultiPipe { + + private final ConditionalOperation operation; + + public ConditionalPipe(Location location, Expression expression, List children, ConditionalOperation operation) { + super(location, expression, children); + this.operation = operation; + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, ConditionalPipe::new, expression(), children(), operation); + } + + @Override + public Pipe replaceChildren(List newChildren) { + return new ConditionalPipe(location(), expression(), newChildren, operation); + } + + @Override + public Processor asProcessor(List procs) { + return new ConditionalProcessor(procs, operation); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), operation); + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + ConditionalPipe other = (ConditionalPipe) obj; + return Objects.equals(operation, other.operation); + } + return false; + } +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalProcessor.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalProcessor.java new file mode 100644 index 0000000000000..82b3b4b5c3852 --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalProcessor.java @@ -0,0 +1,101 @@ +/* + * 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.predicate.conditional; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xpack.sql.expression.gen.processor.Processor; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class ConditionalProcessor implements Processor { + + public enum ConditionalOperation implements Function, Object> { + + COALESCE(Conditionals::coalesce, Conditionals::coalesceInput), + GREATEST(Conditionals::greatest, Conditionals::greatestInput), + LEAST(Conditionals::least, Conditionals::leastInput); + + + String scriptMethodName() { + return name().toLowerCase(Locale.ROOT); + } + + private final Function, Object> process; + private final BiFunction, Object, Object> inputProcess; + + ConditionalOperation(Function, Object> process, + BiFunction, Object, Object> inputProcess) { + this.process = process; + this.inputProcess = inputProcess; + } + + @Override + public Object apply(Collection objects) { + return process.apply(objects); + } + + Object applyOnInput(List processors, Object input) { + return inputProcess.apply(processors, input); + } + } + + public static final String NAME = "nco"; + + private final List processors; + private final ConditionalOperation operation; + + public ConditionalProcessor(List processors, ConditionalOperation operation) { + this.processors = processors; + this.operation = operation; + } + + public ConditionalProcessor(StreamInput in) throws IOException { + processors = in.readNamedWriteableList(Processor.class); + operation = in.readEnum(ConditionalOperation.class); + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeNamedWriteableList(processors); + out.writeEnum(operation); + } + + @Override + public Object process(Object input) { + return operation.applyOnInput(processors, input); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ConditionalProcessor that = (ConditionalProcessor) o; + return Objects.equals(processors, that.processors) && + operation == that.operation; + } + + @Override + public int hashCode() { + return Objects.hash(processors, operation); + } +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Conditionals.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Conditionals.java new file mode 100644 index 0000000000000..cf2576a3780eb --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Conditionals.java @@ -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.predicate.conditional; + +import org.elasticsearch.xpack.sql.expression.gen.processor.Processor; +import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.Comparisons; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.BiFunction; + +final class Conditionals { + + private Conditionals() {} + + static Object coalesce(Collection values) { + if (values == null || values.isEmpty()) { + return null; + } + + for (Object object : values) { + if (object != null) { + return object; + } + } + + return null; + } + + static Object coalesceInput(List processors, Object input) { + for (Processor proc : processors) { + Object result = proc.process(input); + if (result != null) { + return result; + } + } + return null; + } + + static Object greatest(Collection values) { + return extremum(values, Comparisons::gt); + } + + static Object greatestInput(Collection processors, Object input) { + List values = new ArrayList<>(processors.size()); + for (Processor processor : processors) { + values.add(processor.process(input)); + } + return greatest(values); + } + + static Object least(Collection values) { + return extremum(values, Comparisons::lt); + } + + static Object leastInput(List processors, Object input) { + List values = new ArrayList<>(processors.size()); + for (Processor processor : processors) { + values.add(processor.process(input)); + } + return least(values); + } + + private static Object extremum(Collection values, BiFunction comparison) { + if (values == null || values.isEmpty()) { + return null; + } + + Object result = null; + boolean isFirst = true; + for (Object value : values) { + if (isFirst || (result == null) || (comparison.apply(value, result) == Boolean.TRUE)) { + result = value; + } + isFirst = false; + } + return result; + } +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Greatest.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Greatest.java new file mode 100644 index 0000000000000..09104f136fe26 --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Greatest.java @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.predicate.conditional; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.Foldables; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; + +import static org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation.GREATEST; + +public class Greatest extends ArbitraryConditionalFunction { + + public Greatest(Location location, List fields) { + super(location, new ArrayList<>(new LinkedHashSet<>(fields)), GREATEST); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Greatest::new, children()); + } + + @Override + public Expression replaceChildren(List newChildren) { + return new Greatest(location(), newChildren); + } + + @Override + public Object fold() { + return GREATEST.apply(Foldables.valuesOfNoDuplicates(children(), dataType)); + } +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IfNull.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IfNull.java index 325b36c2f602b..9b129c75ce2e0 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IfNull.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IfNull.java @@ -19,12 +19,16 @@ public class IfNull extends Coalesce { public IfNull(Location location, Expression first, Expression second) { - super(location, Arrays.asList(first, second)); + this(location, Arrays.asList(first, second)); + } + + private IfNull(Location location, List expressions) { + super(location, expressions); } @Override public Expression replaceChildren(List newChildren) { - return new IfNull(location(), newChildren.get(0), newChildren.get(1)); + return new IfNull(location(), newChildren); } @Override diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Least.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Least.java new file mode 100644 index 0000000000000..8ddf8a39277ce --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Least.java @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.predicate.conditional; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.Foldables; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; + +import static org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation.LEAST; + +public class Least extends ArbitraryConditionalFunction { + + public Least(Location location, List fields) { + super(location, new ArrayList<>(new LinkedHashSet<>(fields)), LEAST); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Least::new, children()); + } + + @Override + public Expression replaceChildren(List newChildren) { + return new Least(location(), newChildren); + } + + @Override + public Object fold() { + return LEAST.apply(Foldables.valuesOfNoDuplicates(children(), dataType)); + } +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/NullIf.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/NullIf.java index d29974c58d51d..3d5b78182e662 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/NullIf.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/NullIf.java @@ -26,8 +26,6 @@ */ public class NullIf extends ConditionalFunction { - private DataType dataType; - public NullIf(Location location, Expression left, Expression right) { super(location, Arrays.asList(left, right)); } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/Comparisons.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/Comparisons.java index fcfc06d4d0144..37d6983d927b4 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/Comparisons.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/Comparisons.java @@ -32,7 +32,7 @@ static Boolean neq(Object l, Object r) { return i == null ? null : i.intValue() != 0; } - static Boolean lt(Object l, Object r) { + public static Boolean lt(Object l, Object r) { Integer i = compare(l, r); return i == null ? null : i.intValue() < 0; } @@ -42,7 +42,7 @@ static Boolean lte(Object l, Object r) { return i == null ? null : i.intValue() <= 0; } - static Boolean gt(Object l, Object r) { + public static Boolean gt(Object l, Object r) { Integer i = compare(l, r); return i == null ? null : i.intValue() > 0; } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java index ba68eb8afb3dd..e8ce98a8e2663 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java @@ -41,6 +41,7 @@ import org.elasticsearch.xpack.sql.expression.predicate.Negatable; import org.elasticsearch.xpack.sql.expression.predicate.Predicates; import org.elasticsearch.xpack.sql.expression.predicate.Range; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.ArbitraryConditionalFunction; import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce; import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIf; import org.elasticsearch.xpack.sql.expression.predicate.logical.And; @@ -132,7 +133,7 @@ protected Iterable.Batch> batches() { new ReplaceFoldableAttributes(), new FoldNull(), new ConstantFolding(), - new SimplifyCoalesce(), + new SimplifyConditional(), // boolean new BooleanSimplification(), new BooleanLiteralsOnTheRight(), @@ -1202,34 +1203,35 @@ protected Expression rule(Expression e) { } } - static class SimplifyCoalesce extends OptimizerExpressionRule { + static class SimplifyConditional extends OptimizerExpressionRule { - SimplifyCoalesce() { + SimplifyConditional() { super(TransformDirection.DOWN); } @Override protected Expression rule(Expression e) { - if (e instanceof Coalesce) { - Coalesce c = (Coalesce) e; + if (e instanceof ArbitraryConditionalFunction) { + ArbitraryConditionalFunction c = (ArbitraryConditionalFunction) e; - // find the first non-null foldable child (if any) and remove the rest - // while at it, exclude any nulls found + // exclude any nulls found List newChildren = new ArrayList<>(); - for (Expression child : c.children()) { if (Expressions.isNull(child) == false) { newChildren.add(child); - if (child.foldable()) { + + // For Coalesce find the first non-null foldable child (if any) and break early + if (e instanceof Coalesce && child.foldable()) { break; } } } if (newChildren.size() < c.children().size()) { - return new Coalesce(c.location(), newChildren); + return c.replaceChildren(newChildren); } } + return e; } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java index d7fb7870bf5af..57c2d4156af02 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java @@ -30,7 +30,6 @@ import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe; import org.elasticsearch.xpack.sql.expression.gen.pipeline.UnaryPipe; import org.elasticsearch.xpack.sql.expression.gen.processor.Processor; -import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.In; import org.elasticsearch.xpack.sql.plan.physical.AggregateExec; import org.elasticsearch.xpack.sql.plan.physical.EsQueryExec; import org.elasticsearch.xpack.sql.plan.physical.FilterExec; @@ -140,9 +139,6 @@ protected PhysicalPlan rule(ProjectExec project) { if (pj instanceof ScalarFunction) { ScalarFunction f = (ScalarFunction) pj; processors.put(f.toAttribute(), Expressions.pipe(f)); - } else if (pj instanceof In) { - In in = (In) pj; - processors.put(in.toAttribute(), Expressions.pipe(in)); } } } diff --git a/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt b/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt index 4689d95e8b9e9..a4bdfbad93477 100644 --- a/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt +++ b/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt @@ -47,6 +47,8 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS # Null # Object coalesce(java.util.List) + Object greatest(java.util.List) + Object least(java.util.List) Object nullif(Object, Object) # diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/optimizer/OptimizerTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/optimizer/OptimizerTests.java index d4997b6c421e2..c9046326e4f9e 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/optimizer/OptimizerTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/optimizer/OptimizerTests.java @@ -37,7 +37,9 @@ import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator; import org.elasticsearch.xpack.sql.expression.predicate.Range; import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.Greatest; import org.elasticsearch.xpack.sql.expression.predicate.conditional.IfNull; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.Least; import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIf; import org.elasticsearch.xpack.sql.expression.predicate.logical.And; import org.elasticsearch.xpack.sql.expression.predicate.logical.Not; @@ -71,7 +73,7 @@ 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.SimplifyCoalesce; +import org.elasticsearch.xpack.sql.optimizer.Optimizer.SimplifyConditional; import org.elasticsearch.xpack.sql.plan.logical.Filter; import org.elasticsearch.xpack.sql.plan.logical.LocalRelation; import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan; @@ -420,19 +422,19 @@ public void testGenericNullableExpression() { } public void testSimplifyCoalesceNulls() { - Expression e = new SimplifyCoalesce().rule(new Coalesce(EMPTY, asList(Literal.NULL, Literal.NULL))); + Expression e = new SimplifyConditional().rule(new Coalesce(EMPTY, asList(Literal.NULL, Literal.NULL))); assertEquals(Coalesce.class, e.getClass()); assertEquals(0, e.children().size()); } public void testSimplifyCoalesceRandomNulls() { - Expression e = new SimplifyCoalesce().rule(new Coalesce(EMPTY, randomListOfNulls())); + Expression e = new SimplifyConditional().rule(new Coalesce(EMPTY, randomListOfNulls())); assertEquals(Coalesce.class, e.getClass()); assertEquals(0, e.children().size()); } public void testSimplifyCoalesceRandomNullsWithValue() { - Expression e = new SimplifyCoalesce().rule(new Coalesce(EMPTY, + Expression e = new SimplifyConditional().rule(new Coalesce(EMPTY, CollectionUtils.combine( CollectionUtils.combine(randomListOfNulls(), Literal.TRUE, Literal.FALSE, Literal.TRUE), randomListOfNulls()))); @@ -445,7 +447,7 @@ private List randomListOfNulls() { } public void testSimplifyCoalesceFirstLiteral() { - Expression e = new SimplifyCoalesce() + Expression e = new SimplifyConditional() .rule(new Coalesce(EMPTY, Arrays.asList(Literal.NULL, Literal.TRUE, Literal.FALSE, new Abs(EMPTY, getFieldAttribute())))); assertEquals(Coalesce.class, e.getClass()); @@ -454,17 +456,19 @@ public void testSimplifyCoalesceFirstLiteral() { } public void testSimplifyIfNullNulls() { - Expression e = new SimplifyCoalesce().rule(new IfNull(EMPTY, Literal.NULL, Literal.NULL)); - assertEquals(Coalesce.class, e.getClass()); + Expression e = new SimplifyConditional().rule(new IfNull(EMPTY, Literal.NULL, Literal.NULL)); + assertEquals(IfNull.class, e.getClass()); assertEquals(0, e.children().size()); } public void testSimplifyIfNullWithNullAndValue() { - Expression e = new SimplifyCoalesce().rule(new IfNull(EMPTY, Literal.NULL, ONE)); + Expression e = new SimplifyConditional().rule(new IfNull(EMPTY, Literal.NULL, ONE)); + assertEquals(IfNull.class, e.getClass()); assertEquals(1, e.children().size()); assertEquals(ONE, e.children().get(0)); - e = new SimplifyCoalesce().rule(new IfNull(EMPTY, ONE, Literal.NULL)); + e = new SimplifyConditional().rule(new IfNull(EMPTY, ONE, Literal.NULL)); + assertEquals(IfNull.class, e.getClass()); assertEquals(1, e.children().size()); assertEquals(ONE, e.children().get(0)); } @@ -475,6 +479,48 @@ public void testFoldNullNotAppliedOnNullIf() { assertEquals(orig, f); } + public void testSimplifyGreatestNulls() { + Expression e = new SimplifyConditional().rule(new Greatest(EMPTY, asList(Literal.NULL, Literal.NULL))); + assertEquals(Greatest.class, e.getClass()); + assertEquals(0, e.children().size()); + } + + public void testSimplifyGreatestRandomNulls() { + Expression e = new SimplifyConditional().rule(new Greatest(EMPTY, randomListOfNulls())); + assertEquals(Greatest.class, e.getClass()); + assertEquals(0, e.children().size()); + } + + public void testSimplifyGreatestRandomNullsWithValue() { + Expression e = new SimplifyConditional().rule(new Greatest(EMPTY, + CollectionUtils.combine(CollectionUtils.combine(randomListOfNulls(), ONE, TWO, ONE), randomListOfNulls()))); + assertEquals(Greatest.class, e.getClass()); + assertEquals(2, e.children().size()); + assertEquals(ONE, e.children().get(0)); + assertEquals(TWO, e.children().get(1)); + } + + public void testSimplifyLeastNulls() { + Expression e = new SimplifyConditional().rule(new Least(EMPTY, asList(Literal.NULL, Literal.NULL))); + assertEquals(Least.class, e.getClass()); + assertEquals(0, e.children().size()); + } + + public void testSimplifyLeastRandomNulls() { + Expression e = new SimplifyConditional().rule(new Least(EMPTY, randomListOfNulls())); + assertEquals(Least.class, e.getClass()); + assertEquals(0, e.children().size()); + } + + public void testSimplifyLeastRandomNullsWithValue() { + Expression e = new SimplifyConditional().rule(new Least(EMPTY, + CollectionUtils.combine(CollectionUtils.combine(randomListOfNulls(), ONE, TWO, ONE), randomListOfNulls()))); + assertEquals(Least.class, e.getClass()); + assertEquals(2, e.children().size()); + assertEquals(ONE, e.children().get(0)); + assertEquals(TWO, e.children().get(1)); + } + // // Logical simplifications //