diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 index d4c580dfc7..edf28c1d7e 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -141,7 +141,8 @@ expression ; booleanExpression - : valueExpression #predicated + : NOT booleanExpression #not + | valueExpression #predicated ; valueExpression diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundSlot.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundSlot.java index 230d015d3a..c0a20a529f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundSlot.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/UnboundSlot.java @@ -64,4 +64,16 @@ public class UnboundSlot extends Slot { public String toString() { return "'" + getName(); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UnboundSlot other = (UnboundSlot) o; + return nameParts.containsAll(other.getNameParts()); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/ParsingException.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/ParsingException.java new file mode 100644 index 0000000000..fb72872fdf --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/ParsingException.java @@ -0,0 +1,29 @@ +// 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.exceptions; + +/** + * sql parsing exception. + */ +public class ParsingException extends RuntimeException { + + public ParsingException(String message) { + super(message); + } + +} 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 5b20cfcb3d..77c827433e 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 @@ -23,6 +23,7 @@ import org.apache.doris.nereids.DorisParser.BooleanLiteralContext; import org.apache.doris.nereids.DorisParser.ColumnReferenceContext; import org.apache.doris.nereids.DorisParser.ComparisonContext; import org.apache.doris.nereids.DorisParser.DereferenceContext; +import org.apache.doris.nereids.DorisParser.ExpressionContext; import org.apache.doris.nereids.DorisParser.FromClauseContext; import org.apache.doris.nereids.DorisParser.IdentifierListContext; import org.apache.doris.nereids.DorisParser.IdentifierSeqContext; @@ -32,6 +33,7 @@ import org.apache.doris.nereids.DorisParser.JoinRelationContext; import org.apache.doris.nereids.DorisParser.MultipartIdentifierContext; import org.apache.doris.nereids.DorisParser.NamedExpressionContext; import org.apache.doris.nereids.DorisParser.NamedExpressionSeqContext; +import org.apache.doris.nereids.DorisParser.NotContext; import org.apache.doris.nereids.DorisParser.NullLiteralContext; import org.apache.doris.nereids.DorisParser.PredicatedContext; import org.apache.doris.nereids.DorisParser.QualifiedNameContext; @@ -149,6 +151,12 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor { return ParserUtils.withOrigin(ctx, f); } + @Override + public Expression visitExpression(ExpressionContext ctx) { + Supplier f = () -> (Expression) visit(ctx.booleanExpression()); + return ParserUtils.withOrigin(ctx, f); + } + @Override public List visitNamedExpressionSeq(NamedExpressionSeqContext ctx) { List expressions = Lists.newArrayList(); @@ -387,6 +395,19 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor { } } + /** + * Create a not expression. + * format: NOT Expression + * for example: + * not 1 + * not 1=1 + */ + @Override + public Expression visitNot(NotContext ctx) { + Expression child = expression(ctx.booleanExpression()); + return new Not(child); + } + /** * Create a predicated expression. A predicated expression is a normal expression with a * predicate attached to it, for example: diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/SqlParser.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/SqlParser.java index 9c734e1edc..b4179b7d1f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/SqlParser.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/SqlParser.java @@ -19,6 +19,9 @@ package org.apache.doris.nereids.parser; import org.apache.doris.nereids.DorisLexer; import org.apache.doris.nereids.DorisParser; +import org.apache.doris.nereids.exceptions.ParsingException; +import org.apache.doris.nereids.trees.TreeNode; +import org.apache.doris.nereids.trees.expressions.Expression; import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; import org.antlr.v4.runtime.CharStreams; @@ -27,6 +30,8 @@ import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.atn.PredictionMode; import org.antlr.v4.runtime.misc.ParseCancellationException; +import java.util.function.Function; + /** * Sql parser, convert sql DSL to logical plan. */ @@ -40,30 +45,42 @@ public class SqlParser { * @throws Exception throw exception when failed in parse stage */ public LogicalPlan parse(String sql) throws Exception { - DorisLexer lexer = new DorisLexer(new CaseInsensitiveStream(CharStreams.fromString(sql))); + return (LogicalPlan) parse(sql, DorisParser::singleStatement); + } - CommonTokenStream tokenStream = new CommonTokenStream(lexer); - DorisParser parser = new DorisParser(tokenStream); - - // parser.addParseListener(PostProcessor) - // parser.removeErrorListeners() - // parser.addErrorListener(ParseErrorListener) - - ParserRuleContext tree; + private TreeNode parse(String sql, Function parseFunction) { try { - // first, try parsing with potentially faster SLL mode - parser.getInterpreter().setPredictionMode(PredictionMode.SLL); - tree = parser.singleStatement(); - } catch (ParseCancellationException ex) { - // if we fail, parse with LL mode - tokenStream.seek(0); // rewind input stream - parser.reset(); + DorisLexer lexer = new DorisLexer(new CaseInsensitiveStream(CharStreams.fromString(sql))); + CommonTokenStream tokenStream = new CommonTokenStream(lexer); + DorisParser parser = new DorisParser(tokenStream); - parser.getInterpreter().setPredictionMode(PredictionMode.LL); - tree = parser.singleStatement(); + // parser.addParseListener(PostProcessor) + // parser.removeErrorListeners() + // parser.addErrorListener(ParseErrorListener) + + ParserRuleContext tree; + try { + // first, try parsing with potentially faster SLL mode + parser.getInterpreter().setPredictionMode(PredictionMode.SLL); + tree = parseFunction.apply(parser); + } catch (ParseCancellationException ex) { + // if we fail, parse with LL mode + tokenStream.seek(0); // rewind input stream + parser.reset(); + + parser.getInterpreter().setPredictionMode(PredictionMode.LL); + tree = parseFunction.apply(parser); + } + + LogicalPlanBuilder logicalPlanBuilder = new LogicalPlanBuilder(); + return (TreeNode) logicalPlanBuilder.visit(tree); + + } catch (StackOverflowError e) { + throw new ParsingException(e.getMessage()); } + } - LogicalPlanBuilder logicalPlanBuilder = new LogicalPlanBuilder(); - return (LogicalPlan) logicalPlanBuilder.visit(tree); + public Expression createExpression(String expression) { + return (Expression) parse(expression, DorisParser::expression); } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/AbstractExpressionRewriteRule.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/AbstractExpressionRewriteRule.java new file mode 100644 index 0000000000..741fa057bc --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/AbstractExpressionRewriteRule.java @@ -0,0 +1,37 @@ +// 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.rules.expression.rewrite; + +import org.apache.doris.nereids.trees.expressions.Expression; + +/** + * Base class of expression rewrite rule. + */ +public abstract class AbstractExpressionRewriteRule extends ExpressionVisitor + implements ExpressionRewriteRule { + + @Override + public Expression rewrite(Expression expr, ExpressionRewriteContext ctx) { + return (Expression) expr.accept(this, ctx); + } + + @Override + public Expression visitExpression(Expression expr, ExpressionRewriteContext ctx) { + return expr; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriteContext.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriteContext.java new file mode 100644 index 0000000000..13e2267f2b --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriteContext.java @@ -0,0 +1,24 @@ +// 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.rules.expression.rewrite; + +/** + * expression rewrite context. + */ +public class ExpressionRewriteContext { +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriteRule.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriteRule.java new file mode 100644 index 0000000000..c9ec2f92f9 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriteRule.java @@ -0,0 +1,27 @@ +// 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.rules.expression.rewrite; + +import org.apache.doris.nereids.trees.expressions.Expression; + +/** + * The interface of expression rewrite rule. + */ +public interface ExpressionRewriteRule { + Expression rewrite(Expression expr, ExpressionRewriteContext ctx); +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriter.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriter.java new file mode 100644 index 0000000000..6e2a060ddd --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriter.java @@ -0,0 +1,66 @@ +// 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.rules.expression.rewrite; + +import org.apache.doris.nereids.rules.expression.rewrite.rules.NormalizeExpressionRule; +import org.apache.doris.nereids.rules.expression.rewrite.rules.SimplifyNotExprRule; +import org.apache.doris.nereids.trees.expressions.Expression; + +import com.google.common.collect.Lists; + +import java.util.List; + +/** + * Expression rewrite entry, which contains all rewrite rules. + */ +public class ExpressionRewriter { + + public static final List REWRITE_RULES = Lists.newArrayList( + new SimplifyNotExprRule(), + new NormalizeExpressionRule() + ); + + private final ExpressionRewriteContext ctx; + private final List rules; + + public ExpressionRewriter(List rules) { + this.rules = rules; + this.ctx = new ExpressionRewriteContext(); + } + + public ExpressionRewriter(ExpressionRewriteRule rule) { + this.rules = Lists.newArrayList(rule); + this.ctx = new ExpressionRewriteContext(); + } + + /** + * Given an expression, returns a rewritten expression. + */ + public Expression rewrite(Expression root) { + Expression result = root; + for (ExpressionRewriteRule rule : rules) { + result = applyRule(result, rule); + } + return result; + } + + private Expression applyRule(Expression expr, ExpressionRewriteRule rule) { + return rule.rewrite(expr, ctx); + } + +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionVisitor.java new file mode 100644 index 0000000000..e3fb595ce4 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionVisitor.java @@ -0,0 +1,43 @@ +// 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.rules.expression.rewrite; + +import org.apache.doris.nereids.trees.expressions.ComparisonPredicate; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.Literal; +import org.apache.doris.nereids.trees.expressions.Not; + +/** + * Use the visitor pattern to iterate over all expressions for expression rewriting. + */ +public abstract class ExpressionVisitor { + + public abstract R visitExpression(Expression expr, C context); + + public R visitNot(Not expr, C context) { + return visitExpression(expr, context); + } + + public R visitComparisonPredicate(ComparisonPredicate expr, C context) { + return visitExpression(expr, context); + } + + public R visitLiteral(Literal expr, C context) { + return visitExpression(expr, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/RewriteHelper.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/RewriteHelper.java new file mode 100644 index 0000000000..6f6f1758b6 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/RewriteHelper.java @@ -0,0 +1,31 @@ +// 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.rules.expression.rewrite; + +import org.apache.doris.nereids.trees.expressions.Expression; + +/** + * Expression rewrite helper class. + */ +public class RewriteHelper { + + public static boolean isConstant(Expression expr) { + return expr.isConstant(); + } + +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/rules/NormalizeExpressionRule.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/rules/NormalizeExpressionRule.java new file mode 100644 index 0000000000..8da9c1e5f9 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/rules/NormalizeExpressionRule.java @@ -0,0 +1,63 @@ +// 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.rules.expression.rewrite.rules; + +import org.apache.doris.nereids.rules.expression.rewrite.AbstractExpressionRewriteRule; +import org.apache.doris.nereids.rules.expression.rewrite.ExpressionRewriteContext; +import org.apache.doris.nereids.rules.expression.rewrite.RewriteHelper; +import org.apache.doris.nereids.trees.NodeType; +import org.apache.doris.nereids.trees.expressions.ComparisonPredicate; +import org.apache.doris.nereids.trees.expressions.EqualTo; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.GreaterThan; +import org.apache.doris.nereids.trees.expressions.GreaterThanEqual; +import org.apache.doris.nereids.trees.expressions.LessThan; +import org.apache.doris.nereids.trees.expressions.LessThanEqual; + +/** + * Normalizes binary predicates of the form 'expr' op 'slot' so that the slot is on the left-hand side. + * For example: + * 5 > id -> id < 5 + */ +public class NormalizeExpressionRule extends AbstractExpressionRewriteRule { + + public static NormalizeExpressionRule INSTANCE = new NormalizeExpressionRule(); + + @Override + public Expression visitComparisonPredicate(ComparisonPredicate expr, ExpressionRewriteContext context) { + + if (RewriteHelper.isConstant(expr.left()) && !RewriteHelper.isConstant(expr.right())) { + NodeType exprType = expr.getType(); + switch (exprType) { + case EQUAL_TO: + return new EqualTo(expr.right(), expr.left()); + case GREATER_THAN: + return new LessThan(expr.right(), expr.left()); + case GREATER_THAN_EQUAL: + return new LessThanEqual(expr.right(), expr.left()); + case LESS_THAN: + return new GreaterThan(expr.right(), expr.left()); + case LESS_THAN_EQUAL: + return new GreaterThanEqual(expr.right(), expr.left()); + default: + return expr; + } + } + return expr; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/rules/SimplifyNotExprRule.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/rules/SimplifyNotExprRule.java new file mode 100644 index 0000000000..f8025e8f39 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rewrite/rules/SimplifyNotExprRule.java @@ -0,0 +1,80 @@ +// 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.rules.expression.rewrite.rules; + +import org.apache.doris.nereids.rules.expression.rewrite.AbstractExpressionRewriteRule; +import org.apache.doris.nereids.rules.expression.rewrite.ExpressionRewriteContext; +import org.apache.doris.nereids.trees.NodeType; +import org.apache.doris.nereids.trees.expressions.ComparisonPredicate; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.GreaterThan; +import org.apache.doris.nereids.trees.expressions.GreaterThanEqual; +import org.apache.doris.nereids.trees.expressions.LessThan; +import org.apache.doris.nereids.trees.expressions.LessThanEqual; +import org.apache.doris.nereids.trees.expressions.Not; + +/** + * Rewrite rule of NOT expression. + * For example: + * not a -> not a. + * not not a -> a. + * not not not a -> not a. + * not a > b -> a <= b. + * not a < b -> a >= b. + * not a >= b -> a < b. + * not a <= b -> a > b. + * not a=b -> not a=b. + */ +public class SimplifyNotExprRule extends AbstractExpressionRewriteRule { + + public static SimplifyNotExprRule INSTANCE = new SimplifyNotExprRule(); + + + @Override + public Expression visitNot(Not expr, ExpressionRewriteContext context) { + + Expression child = expr.child(); + + if (child instanceof ComparisonPredicate) { + ComparisonPredicate cp = (ComparisonPredicate) expr.child(); + Expression left = rewrite(cp.left(), context); + Expression right = rewrite(cp.right(), context); + NodeType type = cp.getType(); + switch (type) { + case GREATER_THAN: + return new LessThanEqual(left, right); + case GREATER_THAN_EQUAL: + return new LessThan(left, right); + case LESS_THAN: + return new GreaterThanEqual(left, right); + case LESS_THAN_EQUAL: + return new GreaterThan(left, right); + default: + return expr; + } + } + + if (child instanceof Not) { + Not son = (Not) child; + return rewrite(son.child(), context); + } + + return expr; + } + +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/NodeType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/NodeType.java index 19644e7930..1f22f3020f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/NodeType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/NodeType.java @@ -41,6 +41,7 @@ public enum NodeType { NULL_SAFE_EQUAL, NOT, ALIAS, + COMPOUND, // pattern PATTERN, diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/BinaryExpression.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/BinaryExpression.java index 9cd3d9abc4..b031de9d0f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/BinaryExpression.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/BinaryExpression.java @@ -28,4 +28,14 @@ public interface BinaryExpression< RIGHT_CHILD_TYPE extends Expression> extends BinaryNode { + @Override + default LEFT_CHILD_TYPE left() { + return child(0); + } + + @Override + default RIGHT_CHILD_TYPE right() { + return child(1); + } + } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ComparisonPredicate.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ComparisonPredicate.java index 4f3307d9ab..88fc03c2f9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ComparisonPredicate.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ComparisonPredicate.java @@ -18,10 +18,13 @@ package org.apache.doris.nereids.trees.expressions; import org.apache.doris.nereids.exceptions.UnboundException; +import org.apache.doris.nereids.rules.expression.rewrite.ExpressionVisitor; import org.apache.doris.nereids.trees.NodeType; import org.apache.doris.nereids.types.BooleanType; import org.apache.doris.nereids.types.DataType; +import java.util.Objects; + /** * Comparison predicate expression. * Such as: "=", "<", "<=", ">", ">=", "<=>" @@ -50,4 +53,26 @@ public class ComparisonPredicate R accept(ExpressionVisitor visitor, C context) { + return visitor.visitComparisonPredicate(this, context); + } + + @Override + public int hashCode() { + return Objects.hash(type, left(), right()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ComparisonPredicate other = (ComparisonPredicate) o; + return (type == other.getType()) && Objects.equals(left(), other.left()) + && Objects.equals(right(), other.right()); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/EqualTo.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/EqualTo.java index 20517d7523..3f66da9106 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/EqualTo.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/EqualTo.java @@ -19,6 +19,11 @@ package org.apache.doris.nereids.trees.expressions; import org.apache.doris.nereids.exceptions.UnboundException; import org.apache.doris.nereids.trees.NodeType; +import org.apache.doris.nereids.trees.TreeNode; + +import com.google.common.base.Preconditions; + +import java.util.List; /** * Equal to expression: a = b. @@ -39,4 +44,10 @@ public class EqualTo children) { + Preconditions.checkArgument(children.size() == 2); + return new EqualTo((Expression) children.get(0), (Expression) children.get(1)); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java index af3b59687c..b52c58b023 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java @@ -18,6 +18,7 @@ package org.apache.doris.nereids.trees.expressions; import org.apache.doris.nereids.exceptions.UnboundException; +import org.apache.doris.nereids.rules.expression.rewrite.ExpressionVisitor; import org.apache.doris.nereids.trees.AbstractTreeNode; import org.apache.doris.nereids.trees.NodeType; import org.apache.doris.nereids.trees.TreeNode; @@ -47,6 +48,10 @@ public abstract class Expression> throw new UnboundException("nullable"); } + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitExpression(this, context); + } + @Override public List children() { return (List) children; @@ -61,4 +66,16 @@ public abstract class Expression> public EXPR_TYPE newChildren(List children) { throw new RuntimeException(); } + + /** + * Whether the expression is a constant. + */ + public boolean isConstant() { + for (Expression child : children()) { + if (child.isConstant()) { + return true; + } + } + return false; + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/GreaterThan.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/GreaterThan.java index 99ce4cb7b9..12497ff94e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/GreaterThan.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/GreaterThan.java @@ -19,6 +19,11 @@ package org.apache.doris.nereids.trees.expressions; import org.apache.doris.nereids.exceptions.UnboundException; import org.apache.doris.nereids.trees.NodeType; +import org.apache.doris.nereids.trees.TreeNode; + +import com.google.common.base.Preconditions; + +import java.util.List; /** * Greater than expression: a > b. @@ -44,4 +49,10 @@ public class GreaterThan " + right() + ")"; } + + @Override + public GreaterThan newChildren(List children) { + Preconditions.checkArgument(children.size() == 2); + return new GreaterThan((Expression) children.get(0), (Expression) children.get(1)); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/GreaterThanEqual.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/GreaterThanEqual.java index 4e7a2f92dc..e20499f6c2 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/GreaterThanEqual.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/GreaterThanEqual.java @@ -19,6 +19,11 @@ package org.apache.doris.nereids.trees.expressions; import org.apache.doris.nereids.exceptions.UnboundException; import org.apache.doris.nereids.trees.NodeType; +import org.apache.doris.nereids.trees.TreeNode; + +import com.google.common.base.Preconditions; + +import java.util.List; /** * Greater than and equal expression: a >= b. @@ -44,4 +49,10 @@ public class GreaterThanEqual= " + right() + ")"; } + + @Override + public GreaterThanEqual newChildren(List children) { + Preconditions.checkArgument(children.size() == 2); + return new GreaterThanEqual((Expression) children.get(0), (Expression) children.get(1)); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/LessThan.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/LessThan.java index cfea4676de..3e97d42102 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/LessThan.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/LessThan.java @@ -19,6 +19,11 @@ package org.apache.doris.nereids.trees.expressions; import org.apache.doris.nereids.exceptions.UnboundException; import org.apache.doris.nereids.trees.NodeType; +import org.apache.doris.nereids.trees.TreeNode; + +import com.google.common.base.Preconditions; + +import java.util.List; /** * Less than expression: a < b. @@ -44,4 +49,10 @@ public class LessThan children) { + Preconditions.checkArgument(children.size() == 2); + return new LessThan((Expression) children.get(0), (Expression) children.get(1)); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/LessThanEqual.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/LessThanEqual.java index 5ba2c1a5bb..2a759a4973 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/LessThanEqual.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/LessThanEqual.java @@ -19,6 +19,11 @@ package org.apache.doris.nereids.trees.expressions; import org.apache.doris.nereids.exceptions.UnboundException; import org.apache.doris.nereids.trees.NodeType; +import org.apache.doris.nereids.trees.TreeNode; + +import com.google.common.base.Preconditions; + +import java.util.List; /** * Less than and equal expression: a <= b. @@ -44,4 +49,10 @@ public class LessThanEqual children) { + Preconditions.checkArgument(children.size() == 2); + return new LessThanEqual((Expression) children.get(0), (Expression) children.get(1)); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Literal.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Literal.java index 79e830d91d..244d523fb6 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Literal.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Literal.java @@ -20,6 +20,7 @@ package org.apache.doris.nereids.trees.expressions; import org.apache.doris.analysis.Expr; import org.apache.doris.analysis.IntLiteral; import org.apache.doris.nereids.exceptions.UnboundException; +import org.apache.doris.nereids.rules.expression.rewrite.ExpressionVisitor; import org.apache.doris.nereids.trees.NodeType; import org.apache.doris.nereids.types.BooleanType; import org.apache.doris.nereids.types.DataType; @@ -27,6 +28,8 @@ import org.apache.doris.nereids.types.IntegerType; import org.apache.doris.nereids.types.NullType; import org.apache.doris.nereids.types.StringType; +import java.util.Objects; + /** * All data type literal expression in Nereids. */ @@ -67,6 +70,9 @@ public class Literal extends Expression implements LeafExpression implements LeafExpression R accept(ExpressionVisitor visitor, C context) { + return visitor.visitLiteral(this, context); + } + @Override public String sql() { return null; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Literal other = (Literal) o; + return Objects.equals(value, other.getValue()); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } + @Override public String toString() { return value.toString(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/NamedExpression.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/NamedExpression.java index ae7f4d27f1..c3f796a066 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/NamedExpression.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/NamedExpression.java @@ -49,6 +49,11 @@ public abstract class NamedExpression extends Expression R accept(ExpressionVisitor visitor, C context) { + return visitor.visitNot(this, context); + } + + @Override + public Not newChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new Not((Expression) children.get(0)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Not other = (Not) o; + return Objects.equals(child(), other.child()); + } + @Override public String toString() { return "( not " + child() + ")"; diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/UnaryExpression.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/UnaryExpression.java index 991edda91c..ace6c53ba3 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/UnaryExpression.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/UnaryExpression.java @@ -25,4 +25,8 @@ import org.apache.doris.nereids.trees.UnaryNode; public interface UnaryExpression, CHILD_TYPE extends Expression> extends UnaryNode { + @Override + default CHILD_TYPE child() { + return child(0); + } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriteTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriteTest.java new file mode 100644 index 0000000000..5f06b4f68a --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rewrite/ExpressionRewriteTest.java @@ -0,0 +1,65 @@ +// 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.rules.expression.rewrite; + +import org.apache.doris.nereids.parser.SqlParser; +import org.apache.doris.nereids.rules.expression.rewrite.rules.NormalizeExpressionRule; +import org.apache.doris.nereids.rules.expression.rewrite.rules.SimplifyNotExprRule; +import org.apache.doris.nereids.trees.expressions.Expression; + +import org.junit.Assert; +import org.junit.Test; + +/** + * all expr rewrite rule test case. + */ +public class ExpressionRewriteTest { + private static final SqlParser PARSER = new SqlParser(); + private ExpressionRewriter rewriter; + + @Test + public void testNotRewrite() { + rewriter = new ExpressionRewriter(SimplifyNotExprRule.INSTANCE); + + assertRewrite("not x > y", "x <= y"); + assertRewrite("not x < y", "x >= y"); + assertRewrite("not x >= y", "x < y"); + assertRewrite("not x <= y", "x > y"); + assertRewrite("not x = y", "not x = y"); + assertRewrite("not not x > y", "x > y"); + assertRewrite("not not not x > y", "x <= y"); + } + + @Test + public void testNormalizeExpressionRewrite() { + rewriter = new ExpressionRewriter(NormalizeExpressionRule.INSTANCE); + + assertRewrite("2 > x", "x < 2"); + assertRewrite("2 >= x", "x <= 2"); + assertRewrite("2 < x", "x > 2"); + assertRewrite("2 <= x", "x >= 2"); + assertRewrite("2 = x", "x = 2"); + } + + private void assertRewrite(String expression, String expected) { + Expression needRewriteExpression = PARSER.createExpression(expression); + Expression expectedExpression = PARSER.createExpression(expected); + Expression rewrittenExpression = rewriter.rewrite(needRewriteExpression); + Assert.assertEquals(expectedExpression, rewrittenExpression); + } +}