diff --git a/docs/reference/sql/functions/conditional.asciidoc b/docs/reference/sql/functions/conditional.asciidoc index cf15504bbe379..cfce98d803cb3 100644 --- a/docs/reference/sql/functions/conditional.asciidoc +++ b/docs/reference/sql/functions/conditional.asciidoc @@ -223,6 +223,52 @@ include-tagged::{sql-specs}/docs/docs.csv-spec[ifNullReturnFirst] include-tagged::{sql-specs}/docs/docs.csv-spec[ifNullReturnSecond] ---- +[[sql-functions-conditional-iif]] +==== `IFF` + +.Synopsis: +[source, sql] +---- +IIF(expression<1>, expression<2>, [expression<3>]) +---- + +*Input*: + +<1> boolean condition to check + +<2> return value if the boolean condition evaluates to `true` + +<3> return value if the boolean condition evaluates `false`; optional + +*Output*: 2nd expression if 1st expression (condition) evaluates to `true`. If it evaluates to `false` +return 3rd expression. If 3rd expression is not provided return `null`. + +.Description + +Conditional function that implements the standard _IF THEN ELSE _ +logic of programming languages. If the 3rd expression is not provided and the condition evaluates to `false`, +`null` is returned. + + +["source","sql",subs="attributes,callouts,macros"] +---- +include-tagged::{sql-specs}/docs/docs.csv-spec[iifWithDefaultValue] +---- + +["source","sql",subs="attributes,callouts,macros"] +---- +include-tagged::{sql-specs}/docs/docs.csv-spec[iifWithoutDefaultValue] +---- + +[TIP] +================= +*IIF* functions can be combined to implement more complex logic simulating the <> +expression. E.g.: + +[source, sql] +IIF(a = 1, 'one', IIF(a = 2, 'two', IIF(a = 3, 'three', 'many'))) +================= + [[sql-functions-conditional-isnull]] ==== `ISNULL` diff --git a/docs/reference/sql/functions/index.asciidoc b/docs/reference/sql/functions/index.asciidoc index 831a9d0a4e75b..382adeecea4ed 100644 --- a/docs/reference/sql/functions/index.asciidoc +++ b/docs/reference/sql/functions/index.asciidoc @@ -131,6 +131,7 @@ ** <> ** <> ** <> +** <> ** <> ** <> ** <> 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 73ef70a96a4ed..f60f686cb8a3d 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 @@ -29,6 +29,7 @@ CASE |CONDITIONAL COALESCE |CONDITIONAL GREATEST |CONDITIONAL IFNULL |CONDITIONAL +IIF |CONDITIONAL ISNULL |CONDITIONAL LEAST |CONDITIONAL NULLIF |CONDITIONAL diff --git a/x-pack/plugin/sql/qa/src/main/resources/conditionals.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/conditionals.csv-spec index 2a4eaa501a191..a5d7411caf6ea 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/conditionals.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/conditionals.csv-spec @@ -179,3 +179,142 @@ count | gender | languages 11 | M | 3 11 | M | 4 ; + + +iifField +SELECT emp_no, IIF(emp_no - 10000 < 10, 'First 10', 'Second 10') as "iif_result" FROM test_emp WHERE emp_no >= 10005 +ORDER BY emp_no LIMIT 10; + + emp_no | iif_result +--------+----------- +10005 | First 10 +10006 | First 10 +10007 | First 10 +10008 | First 10 +10009 | First 10 +10010 | Second 10 +10011 | Second 10 +10012 | Second 10 +10013 | Second 10 +10014 | Second 10 +; + +iifFieldWithoutAlias +SELECT emp_no, IIF(emp_no - 10000 < 10, emp_no, emp_no % 10) FROM test_emp WHERE emp_no >= 10005 +ORDER BY emp_no LIMIT 10; + + emp_no | IIF(emp_no - 10000 < 10, emp_no, emp_no % 10) +--------+---------------------------------------------- +10005 | 10005 +10006 | 10006 +10007 | 10007 +10008 | 10008 +10009 | 10009 +10010 | 0 +10011 | 1 +10012 | 2 +10013 | 3 +10014 | 4 +; + +iifFieldNoElse +SELECT emp_no, IIF(emp_no - 10000 < 10, 'First 10') as "iif_result" FROM test_emp WHERE emp_no >= 10005 +ORDER BY emp_no LIMIT 10; + + emp_no | iif_result +--------+---------- +10005 | First 10 +10006 | First 10 +10007 | First 10 +10008 | First 10 +10009 | First 10 +10010 | null +10011 | null +10012 | null +10013 | null +10014 | null +; + +iifWhere +SELECT last_name FROM test_emp WHERE IIF(LENGTH(last_name) < 7, 'ShortName') IS NOT NULL ORDER BY emp_no LIMIT 10; + + last_name +----------- +Simmel +Peac +Sluis +Terkki +Genin +Peha +Erde +Famili +Pettey +Heyers +; + +iifOrderBy +SELECT last_name FROM test_emp ORDER BY IIF(languages >= 3, 'first', 'second'), emp_no LIMIT 10; + + last_name +----------- +Simmel +Bamford +Koblick +Preusig +Zielinski +Piveteau +Sluis +Bridgland +Genin +Nooteboom +; + +iifGroupBy +schema::count:l|lang_skills:s +SELECT count(*) AS count, IIF(NVL(languages, 0) <= 1 , 'zero-to-one', 'multilingual') as lang_skills FROM test_emp +GROUP BY lang_skills ORDER BY 2; + + count | lang_skills +---------------+--------------- +75 |multilingual +25 |zero-to-one +; + +iifGroupByComplexNested +schema::count:l|lang_skills:s +SELECT count(*) AS count, +IIF(NVL(languages, 0) = 0, 'zero', + IIF(languages = 1, 'one', + IIF(languages = 2, 'bilingual', + IIF(languages = 3, 'trilingual', 'multilingual')))) as lang_skills FROM test_emp GROUP BY lang_skills ORDER BY 2; + + count | lang_skills +---------------+--------------- +19 |bilingual +39 |multilingual +15 |one +17 |trilingual +10 |zero +; + +iifGroupByAndHaving +schema::count:l|gender:s|languages:byte +SELECT count(*) AS count, gender, languages FROM test_emp +GROUP BY 2, 3 HAVING IIF(count(*) > 10, 'many', 'a few') = 'many' +ORDER BY 2, 3; + + count | gender | languages +---------------+---------------+--------------- +11 |M |2 +11 |M |3 +11 |M |4 +; + +iifWithConvertAndGroupBy +SELECT CONVERT(IIF(languages > 1, IIF(languages = 3, '3')), SQL_BIGINT) AS cond FROM test_emp GROUP BY cond ORDER BY cond DESC; + + cond:l +------- +3 +null +; diff --git a/x-pack/plugin/sql/qa/src/main/resources/docs/docs.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/docs/docs.csv-spec index bc7180de4d619..2a9804dfd5d56 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/docs/docs.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/docs/docs.csv-spec @@ -206,6 +206,7 @@ CASE |CONDITIONAL COALESCE |CONDITIONAL GREATEST |CONDITIONAL IFNULL |CONDITIONAL +IIF |CONDITIONAL ISNULL |CONDITIONAL LEAST |CONDITIONAL NULLIF |CONDITIONAL @@ -2095,6 +2096,29 @@ elastic ; +iifWithDefaultValue +schema::result1:s|result2:s +// tag::iifWithDefaultValue +SELECT IIF(1 < 2, 'TRUE', 'FALSE') AS result1, IIF(1 > 2, 'TRUE', 'FALSE') AS result2; + + result1 | result2 +---------------+--------------- +TRUE |FALSE +// end::iifWithDefaultValue +; + +iifWithoutDefaultValue +schema::result1:s|result2:s +// tag::iifWithoutDefaultValue +SELECT IIF(1 < 2, 'TRUE') AS result1, IIF(1 > 2 , 'TRUE') AS result2; + + result1 | result2 +---------------+--------------- +TRUE |null +// end::iifWithoutDefaultValue +; + + ifNullReturnSecond // tag::ifNullReturnSecond SELECT IFNULL(null, 'search') AS "ifnull"; 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 883dd0e076a05..0e9f07ef2132c 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 @@ -97,6 +97,7 @@ import org.elasticsearch.xpack.sql.expression.predicate.conditional.Case; 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.Iif; 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; @@ -172,6 +173,7 @@ private void defineDefaultFunctions() { // Conditional addToMap(def(Case.class, Case::new, "CASE"), def(Coalesce.class, Coalesce::new, "COALESCE"), + def(Iif.class, Iif::new, "IIF"), def(IfNull.class, IfNull::new, "IFNULL", "ISNULL", "NVL"), def(NullIf.class, NullIf::new, "NULLIF"), def(Greatest.class, Greatest::new, "GREATEST"), @@ -544,10 +546,10 @@ private interface FunctionBuilder { static FunctionDefinition def(Class function, ThreeParametersFunctionBuilder ctorRef, String... names) { FunctionBuilder builder = (source, children, distinct, cfg) -> { - boolean isLocateFunction = function.isAssignableFrom(Locate.class); - if (isLocateFunction && (children.size() > 3 || children.size() < 2)) { + boolean hasMinimumTwo = function.isAssignableFrom(Locate.class) || function.isAssignableFrom(Iif.class); + if (hasMinimumTwo && (children.size() > 3 || children.size() < 2)) { throw new SqlIllegalArgumentException("expects two or three arguments"); - } else if (!isLocateFunction && children.size() != 3) { + } else if (!hasMinimumTwo && children.size() != 3) { throw new SqlIllegalArgumentException("expects exactly three arguments"); } if (distinct) { diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Case.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Case.java index 772e75f414a27..59ec2c38d00b9 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Case.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Case.java @@ -30,28 +30,28 @@ public class Case extends ConditionalFunction { private final List conditions; - private final Expression defaultElse; + private final Expression elseResult; @SuppressWarnings("unchecked") public Case(Source source, List expressions) { super(source, expressions); this.conditions = (List) (List) expressions.subList(0, expressions.size() - 1); - this.defaultElse = expressions.get(expressions.size() - 1); + this.elseResult = expressions.get(expressions.size() - 1); } public List conditions() { return conditions; } - public Expression defaultElse() { - return defaultElse; + public Expression elseResult() { + return elseResult; } @Override public DataType dataType() { if (dataType == null) { if (conditions.isEmpty()) { - dataType = defaultElse().dataType(); + dataType = elseResult().dataType(); } else { dataType = DataType.NULL; @@ -83,7 +83,7 @@ protected TypeResolution resolveType() { } } if (expectedResultDataType == null) { - expectedResultDataType = defaultElse().dataType(); + expectedResultDataType = elseResult().dataType(); } for (IfConditional conditional : conditions) { @@ -102,12 +102,12 @@ protected TypeResolution resolveType() { } } - if (DataTypes.areTypesCompatible(expectedResultDataType, defaultElse.dataType()) == false) { + if (DataTypes.areTypesCompatible(expectedResultDataType, elseResult.dataType()) == false) { return new TypeResolution(format(null, "ELSE clause of [{}] must be [{}], found value [{}] type [{}]", - defaultElse.sourceText(), + elseResult.sourceText(), expectedResultDataType.typeName, - Expressions.name(defaultElse), - defaultElse.dataType().typeName)); + Expressions.name(elseResult), + elseResult.dataType().typeName)); } return TypeResolution.TYPE_RESOLVED; @@ -119,7 +119,7 @@ protected TypeResolution resolveType() { */ @Override public boolean foldable() { - return (conditions.isEmpty() && defaultElse.foldable()) || + return (conditions.isEmpty() && elseResult.foldable()) || (conditions.size() == 1 && conditions.get(0).condition().foldable() && conditions.get(0).result().foldable()); } @@ -128,7 +128,7 @@ public Object fold() { if (conditions.isEmpty() == false && conditions.get(0).condition().fold() == Boolean.TRUE) { return conditions.get(0).result().fold(); } - return defaultElse.fold(); + return elseResult.fold(); } @Override @@ -138,7 +138,7 @@ protected Pipe makePipe() { pipes.add(Expressions.pipe(ifConditional.condition())); pipes.add(Expressions.pipe(ifConditional.result())); } - pipes.add(Expressions.pipe(defaultElse)); + pipes.add(Expressions.pipe(elseResult)); return new CasePipe(source(), this, pipes); } @@ -149,7 +149,7 @@ public ScriptTemplate asScript() { templates.add(asScript(ifConditional.condition())); templates.add(asScript(ifConditional.result())); } - templates.add(asScript(defaultElse)); + templates.add(asScript(elseResult)); StringJoiner template = new StringJoiner(",", "{sql}.caseFunction([", "])"); ParamsBuilder params = paramsBuilder(); diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Iif.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Iif.java new file mode 100644 index 0000000000000..49faeb1233a73 --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Iif.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.sql.expression.predicate.conditional; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.Expressions; +import org.elasticsearch.xpack.sql.expression.Literal; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.tree.Source; +import org.elasticsearch.xpack.sql.type.DataType; +import org.elasticsearch.xpack.sql.type.DataTypes; + +import java.util.Arrays; +import java.util.List; + +import static org.elasticsearch.common.logging.LoggerMessageFormat.format; +import static org.elasticsearch.xpack.sql.expression.TypeResolutions.isBoolean; + +public class Iif extends Case { + + public Iif(Source source, Expression condition, Expression thenResult, Expression elseResult) { + super(source, Arrays.asList(new IfConditional(source, condition, thenResult), elseResult != null ? elseResult : Literal.NULL)); + } + + private Iif(Source source, List expressions) { + super(source, expressions); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Iif::new, conditions().get(0).condition(), conditions().get(0).result(), elseResult()); + } + + @Override + public Expression replaceChildren(List newChildren) { + return new Iif(source(), newChildren); + } + + @Override + protected TypeResolution resolveType() { + TypeResolution conditionTypeResolution = isBoolean(conditions().get(0).condition(), sourceText(), Expressions.ParamOrdinal.FIRST); + if (conditionTypeResolution.unresolved()) { + return conditionTypeResolution; + } + + DataType resultDataType = conditions().get(0).dataType(); + if (DataTypes.areTypesCompatible(resultDataType, elseResult().dataType()) == false) { + return new TypeResolution(format(null, "third argument of [{}] must be [{}], found value [{}] type [{}]", + sourceText(), + resultDataType.typeName, + Expressions.name(elseResult()), + elseResult().dataType().typeName)); + } + return TypeResolution.TYPE_RESOLVED; + } +} 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 78cc16470ca56..72371ab9617d5 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 @@ -1257,7 +1257,7 @@ protected Expression rule(Expression e) { } if (newConditions.size() < c.children().size()) { - return c.replaceChildren(combine(newConditions, c.defaultElse())); + return c.replaceChildren(combine(newConditions, c.elseResult())); } } diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java index 24e993a2d8337..dcf8dad5ecb79 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java @@ -618,6 +618,16 @@ public void testCaseWithDifferentResultAndDefaultValueDataTypesAndNullTypesSkipp error("SELECT CASE WHEN int > 20 THEN null WHEN int > 10 THEN null WHEN int > 5 THEN 'foo' ELSE date END FROM test")); } + public void testIifWithNonBooleanConditionExpression() { + assertEquals("1:8: first argument of [IIF(int, 'one', 'zero')] must be [boolean], found value [int] type [integer]", + error("SELECT IIF(int, 'one', 'zero') FROM test")); + } + + public void testIifWithDifferentResultAndDefaultValueDataTypes() { + assertEquals("1:8: third argument of [IIF(int > 20, 'foo', date)] must be [keyword], found value [date] type [datetime]", + error("SELECT IIF(int > 20, 'foo', date) FROM test")); + } + public void testAggsInWhere() { assertEquals("1:33: Cannot use WHERE filtering on aggregate function [MAX(int)], use HAVING instead", error("SELECT MAX(int) FROM test WHERE MAX(int) > 10 GROUP BY bool")); diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CaseTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CaseTests.java index efc86bb47d24d..807be397f91b6 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CaseTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CaseTests.java @@ -94,7 +94,7 @@ private List mutateChildren(Case c) { } } } - expressions.add(c.defaultElse()); + expressions.add(c.elseResult()); return expressions; } } diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IifTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IifTests.java new file mode 100644 index 0000000000000..054bf2c879f7c --- /dev/null +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IifTests.java @@ -0,0 +1,87 @@ +/* + * 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.function.scalar.FunctionTestUtils; +import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.Equals; +import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase; +import org.elasticsearch.xpack.sql.tree.NodeSubclassTests; +import org.elasticsearch.xpack.sql.tree.Source; +import org.elasticsearch.xpack.sql.tree.SourceTests; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomIntLiteral; +import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral; +import static org.elasticsearch.xpack.sql.tree.SourceTests.randomSource; + +/** + * Needed to override tests in {@link NodeSubclassTests} as If is special since its children are not usual + * expressions but {@link IfConditional}s. + */ +public class IifTests extends AbstractNodeTestCase { + + public static Iif randomIif() { + return new Iif(randomSource(), new Equals(randomSource(), randomStringLiteral(), randomStringLiteral()), + randomIntLiteral(), randomIntLiteral()); + } + + @Override + protected Iif randomInstance() { + return randomIif(); + } + + @Override + protected Iif mutate(Iif instance) { + Iif iif = randomIif(); + List mutatedChildren = mutateChildren(iif); + return new Iif(iif.source(), mutatedChildren.get(0), mutatedChildren.get(1), mutatedChildren.get(2)); + } + + @Override + protected Iif copy(Iif instance) { + return new Iif(instance.source(), instance.conditions().get(0).condition(), instance.conditions().get(0).result(), + instance.elseResult()); + } + + @Override + public void testTransform() { + Iif iif = randomIif(); + + Source newSource = randomValueOtherThan(iif.source(), SourceTests::randomSource); + assertEquals(new Iif(iif.source(), iif.conditions().get(0).condition(), iif.conditions().get(0).result(), iif.elseResult()), + iif.transformPropertiesOnly(p -> Objects.equals(p, iif.source()) ? newSource: p, Object.class)); + + String newName = randomValueOtherThan(iif.name(), () -> randomAlphaOfLength(5)); + assertEquals(new Iif(iif.source(), iif.conditions().get(0).condition(), iif.conditions().get(0).result(), iif.elseResult()), + iif.transformPropertiesOnly(p -> Objects.equals(p, iif.name()) ? newName : p, Object.class)); + } + + @Override + public void testReplaceChildren() { + Iif iif = randomIif(); + + List newChildren = mutateChildren(iif); + assertEquals(new Iif(iif.source(), newChildren.get(0), newChildren.get(1), newChildren.get(2)), + iif.replaceChildren(Arrays.asList(new IfConditional(iif.source(), newChildren.get(0), newChildren.get(1)), + newChildren.get(2)))); + } + + private List mutateChildren(Iif iif) { + List expressions = new ArrayList<>(3); + Equals eq = (Equals) iif.conditions().get(0).condition(); + expressions.add(new Equals(randomSource(), + randomValueOtherThan(eq.left(), FunctionTestUtils::randomStringLiteral), + randomValueOtherThan(eq.right(), FunctionTestUtils::randomStringLiteral))); + expressions.add(randomValueOtherThan(iif.conditions().get(0).result(), FunctionTestUtils::randomIntLiteral)); + expressions.add(randomValueOtherThan(iif.elseResult(), FunctionTestUtils::randomIntLiteral)); + return expressions; + } +} 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 f77088c3fdfc1..b2e5eebe5ea5f 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 @@ -48,6 +48,7 @@ import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce; import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalFunction; import org.elasticsearch.xpack.sql.expression.predicate.conditional.Greatest; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.Iif; import org.elasticsearch.xpack.sql.expression.predicate.conditional.IfConditional; import org.elasticsearch.xpack.sql.expression.predicate.conditional.IfNull; import org.elasticsearch.xpack.sql.expression.predicate.conditional.Least; @@ -685,6 +686,28 @@ public void testSimplifyCaseConditionsFoldCompletely() { assertEquals("foo2", c.fold()); } + public void testSimplifyIif_ConditionTrue() { + SimplifyCase rule = new SimplifyCase(); + Iif iif = new Iif(EMPTY, new Equals(EMPTY, ONE, ONE), Literal.of(EMPTY, "foo"), Literal.of(EMPTY, "bar")); + Expression e = rule.rule(iif); + assertEquals(Iif.class, e.getClass()); + iif = (Iif) e; + assertEquals(1, iif.conditions().size()); + assertTrue(iif.foldable()); + assertEquals("foo", iif.fold()); + } + + public void testSimplifyIif_ConditionFalse() { + SimplifyCase rule = new SimplifyCase(); + Iif iif = new Iif(EMPTY, new Equals(EMPTY, ONE, TWO), Literal.of(EMPTY, "foo"), Literal.of(EMPTY, "bar")); + Expression e = rule.rule(iif); + assertEquals(Iif.class, e.getClass()); + iif = (Iif) e; + assertEquals(0, iif.conditions().size()); + assertTrue(iif.foldable()); + assertEquals("bar", iif.fold()); + } + // // Logical simplifications // diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/ExpressionTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/ExpressionTests.java index a8e5df8e07caa..aff8d7b8fd774 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/ExpressionTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/ExpressionTests.java @@ -465,7 +465,7 @@ public void testCaseWithoutOperand() { assertEquals("WHEN a = 1 THEN 'one'", ifc.sourceText()); assertThat(ifc.condition().toString(), startsWith("Equals[?a,1]#")); assertEquals("'one'=one", ifc.result().toString()); - assertEquals(Literal.NULL, c.defaultElse()); + assertEquals(Literal.NULL, c.elseResult()); expr = parser.createExpression( "CASE WHEN a = 1 THEN 'one'" + @@ -478,7 +478,7 @@ public void testCaseWithoutOperand() { assertEquals(2, c.conditions().size()); ifc = c.conditions().get(0); assertEquals("WHEN a = 1 THEN 'one'", ifc.sourceText()); - assertEquals("'many'=many", c.defaultElse().toString()); + assertEquals("'many'=many", c.elseResult().toString()); } public void testCaseWithOperand() { @@ -495,7 +495,7 @@ public void testCaseWithOperand() { assertEquals("WHEN 1 THEN 'one'", ifc.sourceText()); assertThat(ifc.condition().toString(), startsWith("Equals[?a,1]#")); assertEquals("'one'=one", ifc.result().toString()); - assertEquals(Literal.NULL, c.defaultElse()); + assertEquals(Literal.NULL, c.elseResult()); expr = parser.createExpression( "CASE a WHEN 1 THEN 'one'" + @@ -507,6 +507,6 @@ public void testCaseWithOperand() { assertEquals(2, c.conditions().size()); ifc = c.conditions().get(0); assertEquals("WHEN 1 THEN 'one'", ifc.sourceText()); - assertEquals("'many'=many", c.defaultElse().toString()); + assertEquals("'many'=many", c.elseResult().toString()); } } diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java index ca3c3a92ce6d6..0543e65d4ae46 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java @@ -602,6 +602,20 @@ public void testTranslateCase_GroupBy_Painless() { assertEquals("[{v=int}, {v=10}, {v=foo}, {v=int}, {v=20}, {v=bar}, {v=default}]", scriptTemplate.params().toString()); } + public void testTranslateIif_GroupBy_Painless() { + LogicalPlan p = plan("SELECT IIF(int > 20, 'foo', 'bar') FROM test GROUP BY 1"); + assertTrue(p instanceof Aggregate); + Expression condition = ((Aggregate) p).groupings().get(0); + assertFalse(condition.foldable()); + QueryTranslator.GroupingContext groupingContext = QueryTranslator.groupBy(((Aggregate) p).groupings()); + assertNotNull(groupingContext); + ScriptTemplate scriptTemplate = groupingContext.tail.script(); + assertEquals("InternalSqlScriptUtils.caseFunction([InternalSqlScriptUtils.gt(" + + "InternalSqlScriptUtils.docValue(doc,params.v0),params.v1),params.v2,params.v3])", + scriptTemplate.toString()); + assertEquals("[{v=int}, {v=20}, {v=foo}, {v=bar}]", scriptTemplate.params().toString()); + } + public void testGroupByDateHistogram() { LogicalPlan p = plan("SELECT MAX(int) FROM test GROUP BY HISTOGRAM(int, 1000)"); assertTrue(p instanceof Aggregate); diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/tree/NodeSubclassTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/tree/NodeSubclassTests.java index cad06da0a824e..ddcbe27906e6c 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/tree/NodeSubclassTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/tree/NodeSubclassTests.java @@ -28,6 +28,7 @@ import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe; import org.elasticsearch.xpack.sql.expression.gen.processor.ConstantProcessor; import org.elasticsearch.xpack.sql.expression.gen.processor.Processor; +import org.elasticsearch.xpack.sql.expression.predicate.conditional.Iif; import org.elasticsearch.xpack.sql.expression.predicate.conditional.IfConditional; import org.elasticsearch.xpack.sql.expression.predicate.conditional.IfNull; import org.elasticsearch.xpack.sql.expression.predicate.fulltext.FullTextPredicate; @@ -92,8 +93,8 @@ */ public class NodeSubclassTests> extends ESTestCase { - private static final List> CLASSES_WITH_MIN_TWO_CHILDREN = Arrays.asList(IfConditional.class, IfNull.class, - In.class, InPipe.class, Percentile.class, Percentiles.class, PercentileRanks.class); + private static final List> CLASSES_WITH_MIN_TWO_CHILDREN = Arrays.asList(Iif.class, IfConditional.class, + IfNull.class, In.class, InPipe.class, Percentile.class, Percentiles.class, PercentileRanks.class); private final Class subclass;