diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java index 34c69e97eeebe2..c470bc789d9e78 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java @@ -546,7 +546,9 @@ import org.apache.doris.nereids.trees.expressions.InPredicate; import org.apache.doris.nereids.trees.expressions.InSubquery; import org.apache.doris.nereids.trees.expressions.IntegralDivide; +import org.apache.doris.nereids.trees.expressions.IsFalse; import org.apache.doris.nereids.trees.expressions.IsNull; +import org.apache.doris.nereids.trees.expressions.IsTrue; import org.apache.doris.nereids.trees.expressions.LessThan; import org.apache.doris.nereids.trees.expressions.LessThanEqual; import org.apache.doris.nereids.trees.expressions.Like; @@ -1077,7 +1079,6 @@ import org.apache.doris.nereids.types.AggStateType; import org.apache.doris.nereids.types.ArrayType; import org.apache.doris.nereids.types.BigIntType; -import org.apache.doris.nereids.types.BooleanType; import org.apache.doris.nereids.types.DataType; import org.apache.doris.nereids.types.DateTimeType; import org.apache.doris.nereids.types.DateTimeV2Type; @@ -5067,12 +5068,10 @@ private Expression withPredicate(Expression valueExpression, PredicateContext ct outExpression = new IsNull(valueExpression); break; case DorisParser.TRUE: - outExpression = new Cast(valueExpression, - BooleanType.INSTANCE, true); + outExpression = new IsTrue(valueExpression); break; case DorisParser.FALSE: - outExpression = new Not(new Cast(valueExpression, - BooleanType.INSTANCE, true)); + outExpression = new IsFalse(valueExpression); break; case DorisParser.MATCH: case DorisParser.MATCH_ANY: diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java index 646e93cfc9a84a..57b4b3204a4e9c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java @@ -58,6 +58,9 @@ import org.apache.doris.nereids.trees.expressions.InPredicate; import org.apache.doris.nereids.trees.expressions.InSubquery; import org.apache.doris.nereids.trees.expressions.IntegralDivide; +import org.apache.doris.nereids.trees.expressions.IsFalse; +import org.apache.doris.nereids.trees.expressions.IsNull; +import org.apache.doris.nereids.trees.expressions.IsTrue; import org.apache.doris.nereids.trees.expressions.LessThanEqual; import org.apache.doris.nereids.trees.expressions.Match; import org.apache.doris.nereids.trees.expressions.Not; @@ -939,6 +942,24 @@ public Expression visitBetween(Between between, ExpressionRewriteContext context } } + @Override + public Expression visitIsTrue(IsTrue isTrue, ExpressionRewriteContext context) { + Expression child = isTrue.child().accept(this, context); + if (!child.getDataType().isBooleanType()) { + child = new Cast(child, BooleanType.INSTANCE); + } + return new And(child, new Not(new IsNull(child))); + } + + @Override + public Expression visitIsFalse(IsFalse isFalse, ExpressionRewriteContext context) { + Expression child = isFalse.child().accept(this, context); + if (!child.getDataType().isBooleanType()) { + child = new Cast(child, BooleanType.INSTANCE); + } + return new And(new Not(child), new Not(new IsNull(child))); + } + @Override public Expression visitInSubquery(InSubquery inSubquery, ExpressionRewriteContext context) { // analyze subquery diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsFalse.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsFalse.java new file mode 100644 index 00000000000000..fdd17c296fc583 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsFalse.java @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.nereids.trees.expressions; + +import org.apache.doris.nereids.exceptions.UnboundException; +import org.apache.doris.nereids.trees.expressions.functions.AlwaysNotNullable; +import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression; +import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; +import org.apache.doris.nereids.types.BooleanType; +import org.apache.doris.nereids.types.DataType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Objects; + +/** + * expr is false predicate. + */ +public class IsFalse extends Expression implements UnaryExpression, AlwaysNotNullable { + + public IsFalse(Expression e) { + super(ImmutableList.of(e)); + } + + private IsFalse(List children) { + super(children); + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitIsFalse(this, context); + } + + @Override + public IsFalse withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new IsFalse(children); + } + + @Override + public String computeToSql() throws UnboundException { + return child().toSql() + " IS FALSE"; + } + + @Override + public String toString() { + return child().toString() + " IS FALSE"; + } + + @Override + public String toDigest() { + return child().toDigest() + " IS FALSE"; + } + + @Override + public boolean equals(Object o) { + if (!super.equals(o)) { + return false; + } + IsFalse other = (IsFalse) o; + return Objects.equals(child(), other.child()); + } + + @Override + public int computeHashCode() { + return child().hashCode(); + } + + @Override + public DataType getDataType() throws UnboundException { + return BooleanType.INSTANCE; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsTrue.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsTrue.java new file mode 100644 index 00000000000000..366ab0056dc430 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/IsTrue.java @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.nereids.trees.expressions; + +import org.apache.doris.nereids.exceptions.UnboundException; +import org.apache.doris.nereids.trees.expressions.functions.AlwaysNotNullable; +import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression; +import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; +import org.apache.doris.nereids.types.BooleanType; +import org.apache.doris.nereids.types.DataType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Objects; + +/** + * expr is true predicate. + */ +public class IsTrue extends Expression implements UnaryExpression, AlwaysNotNullable { + + public IsTrue(Expression e) { + super(ImmutableList.of(e)); + } + + private IsTrue(List children) { + super(children); + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitIsTrue(this, context); + } + + @Override + public IsTrue withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new IsTrue(children); + } + + @Override + public String computeToSql() throws UnboundException { + return child().toSql() + " IS TRUE"; + } + + @Override + public String toString() { + return child().toString() + " IS TRUE"; + } + + @Override + public String toDigest() { + return child().toDigest() + " IS TRUE"; + } + + @Override + public boolean equals(Object o) { + if (!super.equals(o)) { + return false; + } + IsTrue other = (IsTrue) o; + return Objects.equals(child(), other.child()); + } + + @Override + public int computeHashCode() { + return child().hashCode(); + } + + @Override + public DataType getDataType() throws UnboundException { + return BooleanType.INSTANCE; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java index aeaea2e8db2524..a25b110dfb438a 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java @@ -52,7 +52,9 @@ import org.apache.doris.nereids.trees.expressions.InPredicate; import org.apache.doris.nereids.trees.expressions.InSubquery; import org.apache.doris.nereids.trees.expressions.IntegralDivide; +import org.apache.doris.nereids.trees.expressions.IsFalse; import org.apache.doris.nereids.trees.expressions.IsNull; +import org.apache.doris.nereids.trees.expressions.IsTrue; import org.apache.doris.nereids.trees.expressions.LessThan; import org.apache.doris.nereids.trees.expressions.LessThanEqual; import org.apache.doris.nereids.trees.expressions.MarkJoinSlotReference; @@ -448,6 +450,14 @@ public R visitIsNull(IsNull isNull, C context) { return visit(isNull, context); } + public R visitIsTrue(IsTrue isTrue, C context) { + return visit(isTrue, context); + } + + public R visitIsFalse(IsFalse isFalse, C context) { + return visit(isFalse, context); + } + public R visitInSubquery(InSubquery in, C context) { return visitSubqueryExpr(in, context); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java index c87981ed368391..82c7066d4c9c5c 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java @@ -34,7 +34,9 @@ import org.apache.doris.nereids.trees.expressions.Cast; import org.apache.doris.nereids.trees.expressions.EqualTo; import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.IsFalse; import org.apache.doris.nereids.trees.expressions.IsNull; +import org.apache.doris.nereids.trees.expressions.IsTrue; import org.apache.doris.nereids.trees.expressions.Not; import org.apache.doris.nereids.trees.expressions.OrderExpression; import org.apache.doris.nereids.trees.expressions.functions.generator.Unnest; @@ -1749,4 +1751,22 @@ public void testIsNullAndIsNotNullExpression() { Assertions.assertInstanceOf(Not.class, expression.child(0)); Assertions.assertInstanceOf(IsNull.class, expression.child(0).child(0)); } + + @Test + public void testIsTrueAndIsFalseExpression() { + NereidsParser nereidsParser = new NereidsParser(); + Expression expression = nereidsParser.parseExpression("X IS TRUE"); + Assertions.assertInstanceOf(IsTrue.class, expression); + + expression = nereidsParser.parseExpression("X IS FALSE"); + Assertions.assertInstanceOf(IsFalse.class, expression); + + expression = nereidsParser.parseExpression("X IS NOT TRUE"); + Assertions.assertInstanceOf(Not.class, expression); + Assertions.assertInstanceOf(IsTrue.class, expression.child(0)); + + expression = nereidsParser.parseExpression("X IS NOT FALSE"); + Assertions.assertInstanceOf(Not.class, expression); + Assertions.assertInstanceOf(IsFalse.class, expression.child(0)); + } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java index 052325cac35763..6c70a37aa0c572 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java @@ -21,14 +21,21 @@ import org.apache.doris.nereids.analyzer.UnboundFunction; import org.apache.doris.nereids.analyzer.UnboundSlot; import org.apache.doris.nereids.exceptions.AnalysisException; +import org.apache.doris.nereids.trees.expressions.And; import org.apache.doris.nereids.trees.expressions.BoundStar; +import org.apache.doris.nereids.trees.expressions.Cast; import org.apache.doris.nereids.trees.expressions.ExprId; import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.IsFalse; +import org.apache.doris.nereids.trees.expressions.IsNull; +import org.apache.doris.nereids.trees.expressions.IsTrue; +import org.apache.doris.nereids.trees.expressions.Not; import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal; import org.apache.doris.nereids.trees.expressions.literal.StringLiteral; import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral; import org.apache.doris.nereids.types.BigIntType; +import org.apache.doris.nereids.types.BooleanType; import com.google.common.collect.ImmutableList; import org.junit.jupiter.api.Assertions; @@ -96,4 +103,34 @@ public void testConstructUnboundFunctionArguments() { ); Assertions.assertEquals(expectedResult, result); } + + @Test + public void testAnalyzeIsTrueAndIsFalse() { + ExpressionAnalyzer analyzer = new ExpressionAnalyzer(null, new Scope(ImmutableList.of()), + null, true, true); + SlotReference slot = new SlotReference(new ExprId(1), "c1", BigIntType.INSTANCE, true, ImmutableList.of()); + + Expression isTrue = analyzer.analyze(new IsTrue(slot)); + Assertions.assertInstanceOf(And.class, isTrue); + Assertions.assertInstanceOf(Cast.class, isTrue.child(0)); + Assertions.assertEquals(BooleanType.INSTANCE, isTrue.child(0).getDataType()); + Assertions.assertInstanceOf(Not.class, isTrue.child(1)); + Assertions.assertInstanceOf(IsNull.class, isTrue.child(1).child(0)); + + Expression isFalse = analyzer.analyze(new IsFalse(slot)); + Assertions.assertInstanceOf(And.class, isFalse); + Assertions.assertInstanceOf(Not.class, isFalse.child(0)); + Assertions.assertInstanceOf(Cast.class, isFalse.child(0).child(0)); + Assertions.assertEquals(BooleanType.INSTANCE, isFalse.child(0).child(0).getDataType()); + Assertions.assertInstanceOf(Not.class, isFalse.child(1)); + Assertions.assertInstanceOf(IsNull.class, isFalse.child(1).child(0)); + + Expression isNotTrue = analyzer.analyze(new Not(new IsTrue(slot))); + Assertions.assertInstanceOf(Not.class, isNotTrue); + Assertions.assertInstanceOf(And.class, isNotTrue.child(0)); + + Expression isNotFalse = analyzer.analyze(new Not(new IsFalse(slot))); + Assertions.assertInstanceOf(Not.class, isNotFalse); + Assertions.assertInstanceOf(And.class, isNotFalse.child(0)); + } } diff --git a/regression-test/data/nereids_syntax_p0/sql/test_compare_expression.out b/regression-test/data/nereids_syntax_p0/sql/test_compare_expression.out index 7f4f2d231be53f..8ee4c5c164e0bd 100644 --- a/regression-test/data/nereids_syntax_p0/sql/test_compare_expression.out +++ b/regression-test/data/nereids_syntax_p0/sql/test_compare_expression.out @@ -45,5 +45,5 @@ true false true true true false false true -- !test_compare_expression_16 -- -\N \N \N \N +false true false true diff --git a/regression-test/data/query_p0/sql_functions/test_is_true_false_predicate.out b/regression-test/data/query_p0/sql_functions/test_is_true_false_predicate.out new file mode 100644 index 00000000000000..cc50d0409d2d63 --- /dev/null +++ b/regression-test/data/query_p0/sql_functions/test_is_true_false_predicate.out @@ -0,0 +1,23 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !is_true -- +1 + +-- !is_false -- +2 + +-- !is_not_true -- +2 +3 +4 + +-- !is_not_false -- +1 +3 +4 + +-- !truth_table -- +1 true true false false true true true false false true +2 false false true true false false false true true false +3 \N false false true true abc false false true true +4 \N false false true true \N false false true true + diff --git a/regression-test/suites/query_p0/sql_functions/test_is_true_false_predicate.groovy b/regression-test/suites/query_p0/sql_functions/test_is_true_false_predicate.groovy new file mode 100644 index 00000000000000..4ad05100d42486 --- /dev/null +++ b/regression-test/suites/query_p0/sql_functions/test_is_true_false_predicate.groovy @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +suite("test_is_true_false_predicate", "arrow_flight_sql") { + sql """drop table if exists test_is_true_false_predicate;""" + sql """ + create table test_is_true_false_predicate ( + id int null, + b boolean null, + c string null + ) + duplicate key (id) + distributed by hash(id) buckets 1 + properties("replication_num" = "1"); + """ + sql """insert into test_is_true_false_predicate values (1, true, 'true'), (2, false, 'false'), (3, null, 'abc'), (4, null, null);""" + + order_qt_is_true """ + select id from test_is_true_false_predicate where b is true order by id; + """ + order_qt_is_false """ + select id from test_is_true_false_predicate where b is false order by id; + """ + order_qt_is_not_true """ + select id from test_is_true_false_predicate where b is not true order by id; + """ + order_qt_is_not_false """ + select id from test_is_true_false_predicate where b is not false order by id; + """ + order_qt_truth_table """ + select id, b, b is true, b is false, b is not true, b is not false, c, c is true, c is false, c is not true, c is not false + from test_is_true_false_predicate order by id; + """ +}